Introduce inspection to detect vals might be marked as const
So #KT-20644 Fixed
This commit is contained in:
@@ -0,0 +1,6 @@
|
||||
<html>
|
||||
<body>
|
||||
This inspection reports object and top-level <code>val</code> that might be declared as <code>const</code>
|
||||
for better performance and Java interoperability.
|
||||
</body>
|
||||
</html>
|
||||
@@ -2712,6 +2712,15 @@
|
||||
cleanupTool="true"
|
||||
/>
|
||||
|
||||
<localInspection implementationClass="org.jetbrains.kotlin.idea.inspections.MayBeConstantInspection"
|
||||
displayName="Might be 'const'"
|
||||
groupPath="Kotlin"
|
||||
groupName="Style issues"
|
||||
enabledByDefault="true"
|
||||
level="WEAK WARNING"
|
||||
language="kotlin"
|
||||
/>
|
||||
|
||||
<referenceImporter implementation="org.jetbrains.kotlin.idea.quickfix.KotlinReferenceImporter"/>
|
||||
|
||||
<fileType.fileViewProviderFactory filetype="KJSM" implementationClass="com.intellij.psi.ClassFileViewProviderFactory"/>
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* Copyright 2010-2017 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.idea.inspections
|
||||
|
||||
import com.intellij.codeInspection.IntentionWrapper
|
||||
import com.intellij.codeInspection.ProblemHighlightType
|
||||
import com.intellij.codeInspection.ProblemsHolder
|
||||
import com.intellij.psi.PsiElementVisitor
|
||||
import org.jetbrains.kotlin.descriptors.VariableDescriptor
|
||||
import org.jetbrains.kotlin.idea.caches.resolve.analyze
|
||||
import org.jetbrains.kotlin.idea.quickfix.AddConstModifierFix
|
||||
import org.jetbrains.kotlin.lexer.KtTokens
|
||||
import org.jetbrains.kotlin.psi.KtClass
|
||||
import org.jetbrains.kotlin.psi.KtProperty
|
||||
import org.jetbrains.kotlin.psi.KtVisitorVoid
|
||||
import org.jetbrains.kotlin.psi.psiUtil.containingClassOrObject
|
||||
import org.jetbrains.kotlin.resolve.BindingContext
|
||||
import org.jetbrains.kotlin.resolve.constants.evaluate.ConstantExpressionEvaluator
|
||||
import org.jetbrains.kotlin.resolve.constants.evaluate.isStandaloneOnlyConstant
|
||||
import org.jetbrains.kotlin.resolve.jvm.annotations.hasJvmFieldAnnotation
|
||||
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
|
||||
|
||||
class MayBeConstantInspection : AbstractKotlinInspection() {
|
||||
override fun buildVisitor(holder: ProblemsHolder, isOnTheFly: Boolean): PsiElementVisitor {
|
||||
return object : KtVisitorVoid() {
|
||||
override fun visitProperty(property: KtProperty) {
|
||||
super.visitProperty(property)
|
||||
|
||||
if (property.isLocal || property.isVar) return
|
||||
val initializer = property.initializer ?: return
|
||||
if (property.hasModifier(KtTokens.CONST_KEYWORD)) return
|
||||
// Top-level or object only
|
||||
if (property.containingClassOrObject is KtClass) return
|
||||
|
||||
// For some reason constant evaluation does not work for property.analyze()
|
||||
val context = initializer.analyze(BodyResolveMode.PARTIAL)
|
||||
val propertyDescriptor = context[BindingContext.DECLARATION_TO_DESCRIPTOR, property] as? VariableDescriptor ?: return
|
||||
val withJvmField = propertyDescriptor.hasJvmFieldAnnotation()
|
||||
if (property.annotationEntries.isNotEmpty() && !withJvmField) return
|
||||
|
||||
val initializerValue = ConstantExpressionEvaluator.getConstant(
|
||||
initializer, context
|
||||
)?.toConstantValue(propertyDescriptor.type) ?: return
|
||||
if (initializerValue.isStandaloneOnlyConstant()) return
|
||||
|
||||
holder.registerProblem(
|
||||
property.nameIdentifier ?: property,
|
||||
if (withJvmField) "'const' might be used instead of '@JvmField'" else "Might be 'const'",
|
||||
ProblemHighlightType.GENERIC_ERROR_OR_WARNING,
|
||||
IntentionWrapper(AddConstModifierFix(property), property.containingFile)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
org.jetbrains.kotlin.idea.inspections.MayBeConstantInspection
|
||||
@@ -0,0 +1,9 @@
|
||||
// WITH_RUNTIME
|
||||
|
||||
const val y = 0
|
||||
|
||||
class Your {
|
||||
companion object {
|
||||
@JvmField val <caret>z = y + 3
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
// WITH_RUNTIME
|
||||
|
||||
const val y = 0
|
||||
|
||||
class Your {
|
||||
companion object {
|
||||
const val z = y + 3
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
class Your {
|
||||
companion object {
|
||||
val <caret>IMPORTANT = 2 * 2
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
class Your {
|
||||
companion object {
|
||||
const val IMPORTANT = 2 * 2
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
// PROBLEM: none
|
||||
// WITH_RUNTIME
|
||||
|
||||
const val <caret>x = 13
|
||||
@@ -0,0 +1,3 @@
|
||||
// PROBLEM: none
|
||||
|
||||
val <caret>withGetter get() = 42
|
||||
@@ -0,0 +1,6 @@
|
||||
// PROBLEM: none
|
||||
// WITH_RUNTIME
|
||||
|
||||
class My {
|
||||
@JvmField val <caret>x = 13
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
// PROBLEM: none
|
||||
// WITH_RUNTIME
|
||||
|
||||
annotation class A
|
||||
|
||||
@A val <caret>x = 55
|
||||
@@ -0,0 +1,5 @@
|
||||
// WITH_RUNTIME
|
||||
|
||||
object O {
|
||||
@JvmField val <caret>s = "He" + "llo"
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
// WITH_RUNTIME
|
||||
|
||||
object O {
|
||||
const val s = "He" + "llo"
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
// WITH_RUNTIME
|
||||
|
||||
@JvmField val <caret>x = 1
|
||||
@@ -0,0 +1,3 @@
|
||||
// WITH_RUNTIME
|
||||
|
||||
const val x = 1
|
||||
@@ -0,0 +1 @@
|
||||
val <caret>x = 1
|
||||
@@ -0,0 +1 @@
|
||||
const val x = 1
|
||||
@@ -0,0 +1,3 @@
|
||||
// PROBLEM: none
|
||||
|
||||
var <caret>n = 1
|
||||
@@ -1281,6 +1281,75 @@ public class LocalInspectionTestGenerated extends AbstractLocalInspectionTest {
|
||||
}
|
||||
}
|
||||
|
||||
@TestMetadata("idea/testData/inspectionsLocal/mayBeConstant")
|
||||
@TestDataPath("$PROJECT_ROOT")
|
||||
@RunWith(JUnit3RunnerWithInners.class)
|
||||
public static class MayBeConstant extends AbstractLocalInspectionTest {
|
||||
public void testAllFilesPresentInMayBeConstant() throws Exception {
|
||||
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/inspectionsLocal/mayBeConstant"), Pattern.compile("^([\\w\\-_]+)\\.kt$"), TargetBackend.ANY, true);
|
||||
}
|
||||
|
||||
@TestMetadata("cascadeConst.kt")
|
||||
public void testCascadeConst() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/mayBeConstant/cascadeConst.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("companion.kt")
|
||||
public void testCompanion() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/mayBeConstant/companion.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("const.kt")
|
||||
public void testConst() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/mayBeConstant/const.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("getter.kt")
|
||||
public void testGetter() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/mayBeConstant/getter.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("inClass.kt")
|
||||
public void testInClass() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/mayBeConstant/inClass.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("nonJvmFieldAnnotated.kt")
|
||||
public void testNonJvmFieldAnnotated() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/mayBeConstant/nonJvmFieldAnnotated.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("object.kt")
|
||||
public void testObject() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/mayBeConstant/object.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("simple.kt")
|
||||
public void testSimple() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/mayBeConstant/simple.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("simplest.kt")
|
||||
public void testSimplest() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/mayBeConstant/simplest.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
|
||||
@TestMetadata("var.kt")
|
||||
public void testVar() throws Exception {
|
||||
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/inspectionsLocal/mayBeConstant/var.kt");
|
||||
doTest(fileName);
|
||||
}
|
||||
}
|
||||
|
||||
@TestMetadata("idea/testData/inspectionsLocal/memberVisibilityCanBePrivate")
|
||||
@TestDataPath("$PROJECT_ROOT")
|
||||
@RunWith(JUnit3RunnerWithInners.class)
|
||||
|
||||
Reference in New Issue
Block a user