Introduce inspection to detect vals might be marked as const

So #KT-20644 Fixed
This commit is contained in:
Mikhail Glukhikh
2017-12-07 17:36:13 +03:00
parent 4ec20ad595
commit 70b7e5eb68
20 changed files with 222 additions and 0 deletions
@@ -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>
+9
View File
@@ -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
}
}
+4
View File
@@ -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
+3
View File
@@ -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)