diff --git a/idea/resources/inspectionDescriptions/MayBeConstant.html b/idea/resources/inspectionDescriptions/MayBeConstant.html new file mode 100644 index 00000000000..778dbd47206 --- /dev/null +++ b/idea/resources/inspectionDescriptions/MayBeConstant.html @@ -0,0 +1,6 @@ + + +This inspection reports object and top-level val that might be declared as const +for better performance and Java interoperability. + + diff --git a/idea/src/META-INF/plugin.xml b/idea/src/META-INF/plugin.xml index ecf8fb1c045..d1d23c2cc8e 100644 --- a/idea/src/META-INF/plugin.xml +++ b/idea/src/META-INF/plugin.xml @@ -2712,6 +2712,15 @@ cleanupTool="true" /> + + diff --git a/idea/src/org/jetbrains/kotlin/idea/inspections/MayBeConstantInspection.kt b/idea/src/org/jetbrains/kotlin/idea/inspections/MayBeConstantInspection.kt new file mode 100644 index 00000000000..21d8dc609bb --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/inspections/MayBeConstantInspection.kt @@ -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) + ) + } + } + } +} diff --git a/idea/testData/inspectionsLocal/mayBeConstant/.inspection b/idea/testData/inspectionsLocal/mayBeConstant/.inspection new file mode 100644 index 00000000000..8f8801c81fe --- /dev/null +++ b/idea/testData/inspectionsLocal/mayBeConstant/.inspection @@ -0,0 +1 @@ +org.jetbrains.kotlin.idea.inspections.MayBeConstantInspection diff --git a/idea/testData/inspectionsLocal/mayBeConstant/cascadeConst.kt b/idea/testData/inspectionsLocal/mayBeConstant/cascadeConst.kt new file mode 100644 index 00000000000..8907a84cf6a --- /dev/null +++ b/idea/testData/inspectionsLocal/mayBeConstant/cascadeConst.kt @@ -0,0 +1,9 @@ +// WITH_RUNTIME + +const val y = 0 + +class Your { + companion object { + @JvmField val z = y + 3 + } +} \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/mayBeConstant/cascadeConst.kt.after b/idea/testData/inspectionsLocal/mayBeConstant/cascadeConst.kt.after new file mode 100644 index 00000000000..d2173f10342 --- /dev/null +++ b/idea/testData/inspectionsLocal/mayBeConstant/cascadeConst.kt.after @@ -0,0 +1,9 @@ +// WITH_RUNTIME + +const val y = 0 + +class Your { + companion object { + const val z = y + 3 + } +} \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/mayBeConstant/companion.kt b/idea/testData/inspectionsLocal/mayBeConstant/companion.kt new file mode 100644 index 00000000000..1c8a9fe5651 --- /dev/null +++ b/idea/testData/inspectionsLocal/mayBeConstant/companion.kt @@ -0,0 +1,5 @@ +class Your { + companion object { + val IMPORTANT = 2 * 2 + } +} diff --git a/idea/testData/inspectionsLocal/mayBeConstant/companion.kt.after b/idea/testData/inspectionsLocal/mayBeConstant/companion.kt.after new file mode 100644 index 00000000000..99c046c9420 --- /dev/null +++ b/idea/testData/inspectionsLocal/mayBeConstant/companion.kt.after @@ -0,0 +1,5 @@ +class Your { + companion object { + const val IMPORTANT = 2 * 2 + } +} diff --git a/idea/testData/inspectionsLocal/mayBeConstant/const.kt b/idea/testData/inspectionsLocal/mayBeConstant/const.kt new file mode 100644 index 00000000000..04f1e643684 --- /dev/null +++ b/idea/testData/inspectionsLocal/mayBeConstant/const.kt @@ -0,0 +1,4 @@ +// PROBLEM: none +// WITH_RUNTIME + +const val x = 13 \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/mayBeConstant/getter.kt b/idea/testData/inspectionsLocal/mayBeConstant/getter.kt new file mode 100644 index 00000000000..20d05f373d3 --- /dev/null +++ b/idea/testData/inspectionsLocal/mayBeConstant/getter.kt @@ -0,0 +1,3 @@ +// PROBLEM: none + +val withGetter get() = 42 \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/mayBeConstant/inClass.kt b/idea/testData/inspectionsLocal/mayBeConstant/inClass.kt new file mode 100644 index 00000000000..8dd01068792 --- /dev/null +++ b/idea/testData/inspectionsLocal/mayBeConstant/inClass.kt @@ -0,0 +1,6 @@ +// PROBLEM: none +// WITH_RUNTIME + +class My { + @JvmField val x = 13 +} \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/mayBeConstant/nonJvmFieldAnnotated.kt b/idea/testData/inspectionsLocal/mayBeConstant/nonJvmFieldAnnotated.kt new file mode 100644 index 00000000000..3719b74091f --- /dev/null +++ b/idea/testData/inspectionsLocal/mayBeConstant/nonJvmFieldAnnotated.kt @@ -0,0 +1,6 @@ +// PROBLEM: none +// WITH_RUNTIME + +annotation class A + +@A val x = 55 \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/mayBeConstant/object.kt b/idea/testData/inspectionsLocal/mayBeConstant/object.kt new file mode 100644 index 00000000000..e002478cdd1 --- /dev/null +++ b/idea/testData/inspectionsLocal/mayBeConstant/object.kt @@ -0,0 +1,5 @@ +// WITH_RUNTIME + +object O { + @JvmField val s = "He" + "llo" +} \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/mayBeConstant/object.kt.after b/idea/testData/inspectionsLocal/mayBeConstant/object.kt.after new file mode 100644 index 00000000000..9d28f11dce0 --- /dev/null +++ b/idea/testData/inspectionsLocal/mayBeConstant/object.kt.after @@ -0,0 +1,5 @@ +// WITH_RUNTIME + +object O { + const val s = "He" + "llo" +} \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/mayBeConstant/simple.kt b/idea/testData/inspectionsLocal/mayBeConstant/simple.kt new file mode 100644 index 00000000000..e883f9c3f4f --- /dev/null +++ b/idea/testData/inspectionsLocal/mayBeConstant/simple.kt @@ -0,0 +1,3 @@ +// WITH_RUNTIME + +@JvmField val x = 1 \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/mayBeConstant/simple.kt.after b/idea/testData/inspectionsLocal/mayBeConstant/simple.kt.after new file mode 100644 index 00000000000..81fccdefc30 --- /dev/null +++ b/idea/testData/inspectionsLocal/mayBeConstant/simple.kt.after @@ -0,0 +1,3 @@ +// WITH_RUNTIME + +const val x = 1 \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/mayBeConstant/simplest.kt b/idea/testData/inspectionsLocal/mayBeConstant/simplest.kt new file mode 100644 index 00000000000..4d26dd472d8 --- /dev/null +++ b/idea/testData/inspectionsLocal/mayBeConstant/simplest.kt @@ -0,0 +1 @@ +val x = 1 \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/mayBeConstant/simplest.kt.after b/idea/testData/inspectionsLocal/mayBeConstant/simplest.kt.after new file mode 100644 index 00000000000..26323def675 --- /dev/null +++ b/idea/testData/inspectionsLocal/mayBeConstant/simplest.kt.after @@ -0,0 +1 @@ +const val x = 1 \ No newline at end of file diff --git a/idea/testData/inspectionsLocal/mayBeConstant/var.kt b/idea/testData/inspectionsLocal/mayBeConstant/var.kt new file mode 100644 index 00000000000..237f96b7f42 --- /dev/null +++ b/idea/testData/inspectionsLocal/mayBeConstant/var.kt @@ -0,0 +1,3 @@ +// PROBLEM: none + +var n = 1 \ No newline at end of file diff --git a/idea/tests/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java index 9d9831d1912..6023917c8ea 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/inspections/LocalInspectionTestGenerated.java @@ -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)