FIR IDE: make unused variable fix a IDE inspection

GitOrigin-RevId: 7908d577d3dc4e9b85781a3250f5090f39ba7c64
This commit is contained in:
Tianyu Geng
2022-02-08 08:49:37 +00:00
committed by intellij-monorepo-bot
parent 759acce996
commit 0b52f522cb
27 changed files with 221 additions and 70 deletions
@@ -7,11 +7,9 @@ import com.intellij.psi.PsiElement
import com.intellij.psi.tree.TokenSet
import org.jetbrains.kotlin.idea.fir.api.applicator.applicabilityRanges
import org.jetbrains.kotlin.idea.fir.api.applicator.applicabilityTarget
import org.jetbrains.kotlin.idea.util.nameIdentifierTextRangeInThis
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.psi.KtCallableDeclaration
import org.jetbrains.kotlin.psi.KtLambdaExpression
import org.jetbrains.kotlin.psi.KtModifierListOwner
import org.jetbrains.kotlin.psi.KtValueArgument
import org.jetbrains.kotlin.psi.*
object ApplicabilityRanges {
val SELF = applicabilityTarget<PsiElement> { it }
@@ -37,4 +35,8 @@ object ApplicabilityRanges {
listOf(TextRange(0, element.textLength))
}
}
val DECLARATION_NAME = applicabilityTarget<KtNamedDeclaration> { element ->
element.nameIdentifier
}
}
@@ -0,0 +1,50 @@
// Copyright 2000-2021 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package org.jetbrains.kotlin.idea.fir.inspections.diagnosticBased
import org.jetbrains.kotlin.idea.quickfix.RemovePsiElementSimpleFix
import com.intellij.codeInspection.ProblemHighlightType
import org.jetbrains.kotlin.analysis.api.fir.diagnostics.KtFirDiagnostic
import org.jetbrains.kotlin.idea.KotlinBundle
import org.jetbrains.kotlin.idea.api.applicator.HLApplicator
import org.jetbrains.kotlin.idea.api.applicator.HLApplicatorInput
import org.jetbrains.kotlin.idea.api.applicator.applicator
import org.jetbrains.kotlin.idea.fir.api.AbstractHLDiagnosticBasedInspection
import org.jetbrains.kotlin.idea.fir.api.HLInputByDiagnosticProvider
import org.jetbrains.kotlin.idea.fir.api.applicator.HLApplicabilityRange
import org.jetbrains.kotlin.idea.fir.api.applicator.HLPresentation
import org.jetbrains.kotlin.idea.fir.api.applicator.presentation
import org.jetbrains.kotlin.idea.fir.api.inputByDiagnosticProvider
import org.jetbrains.kotlin.idea.fir.applicators.ApplicabilityRanges
import org.jetbrains.kotlin.idea.util.isExplicitTypeReferenceNeededForTypeInference
import org.jetbrains.kotlin.psi.KtNamedDeclaration
import org.jetbrains.kotlin.psi.KtProperty
class HLUnusedVariableInspection :
AbstractHLDiagnosticBasedInspection<KtNamedDeclaration, KtFirDiagnostic.UnusedVariable, HLApplicatorInput.Empty>(
elementType = KtNamedDeclaration::class,
diagnosticType = KtFirDiagnostic.UnusedVariable::class,
) {
override val inputByDiagnosticProvider: HLInputByDiagnosticProvider<KtNamedDeclaration, KtFirDiagnostic.UnusedVariable, HLApplicatorInput.Empty>
get() = inputByDiagnosticProvider { diagnostic ->
val ktProperty = diagnostic.psi as? KtProperty ?: return@inputByDiagnosticProvider null
if (ktProperty.isExplicitTypeReferenceNeededForTypeInference()) return@inputByDiagnosticProvider null
HLApplicatorInput.Empty
}
override val presentation: HLPresentation<KtNamedDeclaration>
get() = presentation {
highlightType(ProblemHighlightType.LIKE_UNUSED_SYMBOL)
}
override val applicabilityRange: HLApplicabilityRange<KtNamedDeclaration>
get() = ApplicabilityRanges.DECLARATION_NAME
override val applicator: HLApplicator<KtNamedDeclaration, HLApplicatorInput.Empty>
get() = applicator {
familyName(KotlinBundle.message("remove.element"))
actionName { psi, _ ->
KotlinBundle.message("remove.variable.0", psi.name.toString())
}
applyTo { psi, _ ->
RemovePsiElementSimpleFix.RemoveVariableFactory.removeProperty(psi as KtProperty)
}
}
}
@@ -176,10 +176,6 @@ class MainKtQuickFixRegistrar : KtQuickFixRegistrar() {
registerPsiQuickFixes(KtFirDiagnostic.NonVarargSpread::class, RemovePsiElementSimpleFix.RemoveSpreadFactory)
}
private val unused = KtQuickFixesListBuilder.registerPsiQuickFix {
registerPsiQuickFixes(KtFirDiagnostic.UnusedVariable::class, RemovePsiElementSimpleFix.RemoveVariableFactory)
}
override val list: KtQuickFixesList = KtQuickFixesList.createCombined(
keywords,
propertyInitialization,
@@ -191,6 +187,5 @@ class MainKtQuickFixRegistrar : KtQuickFixRegistrar() {
typeMismatch,
superKeyword,
vararg,
unused,
)
}
@@ -18,6 +18,64 @@ import org.junit.runner.RunWith;
@TestDataPath("$CONTENT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public abstract class HLLocalInspectionTestGenerated extends AbstractHLLocalInspectionTest {
@RunWith(JUnit3RunnerWithInners.class)
@TestMetadata("../idea/tests/testData/inspectionsLocal/unusedVariable")
public static class UnusedVariable extends AbstractHLLocalInspectionTest {
private void runTest(String testDataFilePath) throws Exception {
KotlinTestUtils.runTest(this::doTest, this, testDataFilePath);
}
@TestMetadata("unusedVariableInDestructuringDeclaration.kt")
public void testUnusedVariableInDestructuringDeclaration() throws Exception {
runTest("../idea/tests/testData/inspectionsLocal/unusedVariable/unusedVariableInDestructuringDeclaration.kt");
}
@TestMetadata("unusedVariableWithAnonymousFunctionInitialize1.kt")
public void testUnusedVariableWithAnonymousFunctionInitialize1() throws Exception {
runTest("../idea/tests/testData/inspectionsLocal/unusedVariable/unusedVariableWithAnonymousFunctionInitialize1.kt");
}
@TestMetadata("unusedVariableWithAnonymousFunctionInitialize2.kt")
public void testUnusedVariableWithAnonymousFunctionInitialize2() throws Exception {
runTest("../idea/tests/testData/inspectionsLocal/unusedVariable/unusedVariableWithAnonymousFunctionInitialize2.kt");
}
@TestMetadata("unusedVariableWithConstantInitializer.kt")
public void testUnusedVariableWithConstantInitializer() throws Exception {
runTest("../idea/tests/testData/inspectionsLocal/unusedVariable/unusedVariableWithConstantInitializer.kt");
}
@TestMetadata("unusedVariableWithInitializer.kt")
public void testUnusedVariableWithInitializer() throws Exception {
runTest("../idea/tests/testData/inspectionsLocal/unusedVariable/unusedVariableWithInitializer.kt");
}
@TestMetadata("unusedVariableWithInitializerAndComment.kt")
public void testUnusedVariableWithInitializerAndComment() throws Exception {
runTest("../idea/tests/testData/inspectionsLocal/unusedVariable/unusedVariableWithInitializerAndComment.kt");
}
@TestMetadata("unusedVariableWithLambdaInitializer1.kt")
public void testUnusedVariableWithLambdaInitializer1() throws Exception {
runTest("../idea/tests/testData/inspectionsLocal/unusedVariable/unusedVariableWithLambdaInitializer1.kt");
}
@TestMetadata("unusedVariableWithLambdaInitializer2.kt")
public void testUnusedVariableWithLambdaInitializer2() throws Exception {
runTest("../idea/tests/testData/inspectionsLocal/unusedVariable/unusedVariableWithLambdaInitializer2.kt");
}
@TestMetadata("unusedVariableWithNullInitializer.kt")
public void testUnusedVariableWithNullInitializer() throws Exception {
runTest("../idea/tests/testData/inspectionsLocal/unusedVariable/unusedVariableWithNullInitializer.kt");
}
@TestMetadata("unusedVariableWithoutInitializer.kt")
public void testUnusedVariableWithoutInitializer() throws Exception {
runTest("../idea/tests/testData/inspectionsLocal/unusedVariable/unusedVariableWithoutInitializer.kt");
}
}
@RunWith(JUnit3RunnerWithInners.class)
@TestMetadata("../idea/tests/testData/inspectionsLocal/redundantVisibilityModifier")
public static class RedundantVisibilityModifier extends AbstractHLLocalInspectionTest {
@@ -1750,64 +1750,6 @@ public abstract class HighLevelQuickFixTestGenerated extends AbstractHighLevelQu
}
}
@RunWith(JUnit3RunnerWithInners.class)
@TestMetadata("../idea/tests/testData/quickfix/variables")
public static class Variables extends AbstractHighLevelQuickFixTest {
private void runTest(String testDataFilePath) throws Exception {
KotlinTestUtils.runTest(this::doTest, this, testDataFilePath);
}
@TestMetadata("unusedVariableInDestructuringDeclaration.kt")
public void testUnusedVariableInDestructuringDeclaration() throws Exception {
runTest("../idea/tests/testData/quickfix/variables/unusedVariableInDestructuringDeclaration.kt");
}
@TestMetadata("unusedVariableWithAnonymousFunctionInitialize1.kt")
public void testUnusedVariableWithAnonymousFunctionInitialize1() throws Exception {
runTest("../idea/tests/testData/quickfix/variables/unusedVariableWithAnonymousFunctionInitialize1.kt");
}
@TestMetadata("unusedVariableWithAnonymousFunctionInitialize2.kt")
public void testUnusedVariableWithAnonymousFunctionInitialize2() throws Exception {
runTest("../idea/tests/testData/quickfix/variables/unusedVariableWithAnonymousFunctionInitialize2.kt");
}
@TestMetadata("unusedVariableWithConstantInitializer.kt")
public void testUnusedVariableWithConstantInitializer() throws Exception {
runTest("../idea/tests/testData/quickfix/variables/unusedVariableWithConstantInitializer.kt");
}
@TestMetadata("unusedVariableWithInitializer.kt")
public void testUnusedVariableWithInitializer() throws Exception {
runTest("../idea/tests/testData/quickfix/variables/unusedVariableWithInitializer.kt");
}
@TestMetadata("unusedVariableWithInitializerAndComment.kt")
public void testUnusedVariableWithInitializerAndComment() throws Exception {
runTest("../idea/tests/testData/quickfix/variables/unusedVariableWithInitializerAndComment.kt");
}
@TestMetadata("unusedVariableWithLambdaInitializer1.kt")
public void testUnusedVariableWithLambdaInitializer1() throws Exception {
runTest("../idea/tests/testData/quickfix/variables/unusedVariableWithLambdaInitializer1.kt");
}
@TestMetadata("unusedVariableWithLambdaInitializer2.kt")
public void testUnusedVariableWithLambdaInitializer2() throws Exception {
runTest("../idea/tests/testData/quickfix/variables/unusedVariableWithLambdaInitializer2.kt");
}
@TestMetadata("unusedVariableWithNullInitializer.kt")
public void testUnusedVariableWithNullInitializer() throws Exception {
runTest("../idea/tests/testData/quickfix/variables/unusedVariableWithNullInitializer.kt");
}
@TestMetadata("unusedVariableWithoutInitializer.kt")
public void testUnusedVariableWithoutInitializer() throws Exception {
runTest("../idea/tests/testData/quickfix/variables/unusedVariableWithoutInitializer.kt");
}
}
@RunWith(JUnit3RunnerWithInners.class)
@TestMetadata("../idea/tests/testData/quickfix/variables/changeMutability")
public static class ChangeMutability extends AbstractHighLevelQuickFixTest {
@@ -2257,6 +2257,7 @@ inspection.overriding.deprecated.member.display.name=Overriding deprecated membe
inspection.unsafe.cast.from.dynamic.display.name=Implicit (unsafe) cast from dynamic type
inspection.redundant.sam.constructor.display.name=Redundant SAM constructor
inspection.kotlin.unused.import.display.name=Unused import directive
inspection.kotlin.unused.variable.display.name=Unused variable
inspection.unused.receiver.parameter.display.name=Unused receiver parameter
inspection.unused.symbol.display.name=Unused symbol
inspection.use.property.access.syntax.display.name=Accessor call that can be replaced with property access syntax
@@ -182,10 +182,10 @@ import org.jetbrains.kotlin.testGenerator.model.Patterns.KTS
import org.jetbrains.kotlin.testGenerator.model.Patterns.KT_OR_KTS
import org.jetbrains.kotlin.testGenerator.model.Patterns.KT_OR_KTS_WITHOUT_DOTS
import org.jetbrains.kotlin.testGenerator.model.Patterns.KT_WITHOUT_DOTS
import org.jetbrains.kotlin.testGenerator.model.Patterns.KT_WITHOUT_DOT_AND_FIR_PREFIX
import org.jetbrains.kotlin.testGenerator.model.Patterns.KT_WITHOUT_FIR_PREFIX
import org.jetbrains.kotlin.testGenerator.model.Patterns.TEST
import org.jetbrains.kotlin.testGenerator.model.Patterns.WS_KTS
import org.jetbrains.kotlin.testGenerator.model.Patterns.KT_WITHOUT_DOT_AND_FIR_PREFIX
import org.jetbrains.kotlin.tools.projectWizard.cli.AbstractProjectTemplateBuildFileGenerationTest
import org.jetbrains.kotlin.tools.projectWizard.cli.AbstractYamlBuildFileGenerationTest
import org.jetbrains.kotlin.tools.projectWizard.wizard.AbstractProjectTemplateNewWizardProjectImportTest
@@ -516,7 +516,11 @@ private fun assembleWorkspace(): TWorkspace = workspace {
}
testClass<AbstractLocalInspectionTest> {
model("inspectionsLocal", pattern = Patterns.forRegex("^([\\w\\-_]+)\\.(kt|kts)$"))
model(
"inspectionsLocal", pattern = Patterns.forRegex("^([\\w\\-_]+)\\.(kt|kts)$"),
// In FE1.0, this is a quickfix rather than a local inspection
excludedDirectories = listOf("unusedVariable")
)
}
testClass<AbstractViewOfflineInspectionTest> {
@@ -1079,7 +1083,6 @@ private fun assembleWorkspace(): TWorkspace = workspace {
model("quickfix/replaceWithDotCall", pattern = pattern)
model("quickfix/replaceWithSafeCall", pattern = pattern)
model("quickfix/supercalls", pattern = pattern)
model("quickfix/variables", pattern = pattern, isRecursive = false)
model("quickfix/variables/changeMutability", pattern = pattern, isRecursive = false)
model("quickfix/variables/removeValVarFromParameter", pattern = pattern)
model("quickfix/when", pattern = pattern)
@@ -1133,6 +1136,7 @@ private fun assembleWorkspace(): TWorkspace = workspace {
/*testGroup("fir", testDataPath = "..") {
testClass<AbstractHLLocalInspectionTest> {
val pattern = Patterns.forRegex("^([\\w\\-_]+)\\.(kt|kts)$")
model("idea/tests/testData/inspectionsLocal/unusedVariable", pattern = pattern)
model("idea/tests/testData/inspectionsLocal/redundantVisibilityModifier", pattern = pattern)
model("fir/testData/inspectionsLocal", pattern = pattern)
}
@@ -0,0 +1,5 @@
<html>
<body>
This inspection reports variables that are not used.
</body>
</html>
@@ -0,0 +1 @@
org.jetbrains.kotlin.idea.fir.inspections.diagnosticBased.HLUnusedVariableInspection
@@ -0,0 +1,11 @@
// PROBLEM: none
fun test(condition: Boolean, foo: Foo) {
val v = if (condition) {
val (<caret>one, two) = foo
two
} else {
null
}
}
data class Foo(val one: String, val two: String)
@@ -0,0 +1,4 @@
// PROBLEM: none
fun test() {
val <caret>a: (String) -> Unit = fun(s) { s + s }
}
@@ -0,0 +1,4 @@
// "Remove variable 'a'" "true"
fun test() {
val <caret>a: (String) -> Unit = fun(s: String) { s + s }
}
@@ -0,0 +1,4 @@
// "Remove variable 'a'" "true"
fun test() {
fun(s: String) { s + s }
}
@@ -0,0 +1,5 @@
// "Remove variable 'flag'" "true"
fun foo() {
val <caret>flag = true
}
@@ -0,0 +1,4 @@
// "Remove variable 'flag'" "true"
fun foo() {
}
@@ -0,0 +1,6 @@
// "Remove variable 'a'" "true"
var cnt = 5
fun getCnt() = cnt++
fun f() {
var <caret>a = getCnt()
}
@@ -0,0 +1,6 @@
// "Remove variable 'a'" "true"
var cnt = 5
fun getCnt() = cnt++
fun f() {
getCnt()
}
@@ -0,0 +1,6 @@
// "Remove variable 'a'" "true"
var cnt = 5
fun getCnt() = cnt++
fun f() {
var <caret>a = getCnt() // comment
}
@@ -0,0 +1,6 @@
// "Remove variable 'a'" "true"
var cnt = 5
fun getCnt() = cnt++
fun f() {
getCnt() // comment
}
@@ -0,0 +1,4 @@
// PROBLEM: none
fun test() {
val <caret>a: (String) -> Unit = { s -> s + s }
}
@@ -0,0 +1,4 @@
// "Remove variable 'a'" "true"
fun test() {
val <caret>a: (String) -> Unit = { s: String -> s + s }
}
@@ -0,0 +1,4 @@
// "Remove variable 'a'" "true"
fun test() {
{ s: String -> s + s }
}
@@ -0,0 +1,5 @@
// "Remove variable 'i'" "true"
fun foo() {
val <caret>i: Int? = null
}
@@ -0,0 +1,4 @@
// "Remove variable 'i'" "true"
fun foo() {
}
@@ -0,0 +1,4 @@
// "Remove variable 'test'" "true"
fun f() {
val <caret>test: Int
}
@@ -0,0 +1,3 @@
// "Remove variable 'test'" "true"
fun f() {
}
@@ -26,5 +26,14 @@
level="WARNING"
language="kotlin"
key="inspection.kotlin.unused.import.display.name" bundle="messages.KotlinBundle"/>
<localInspection implementationClass="org.jetbrains.kotlin.idea.fir.inspections.diagnosticBased.HLUnusedVariableInspection"
groupPath="Kotlin"
groupName="Redundant constructs"
enabledByDefault="true"
cleanupTool="true"
level="WARNING"
language="kotlin"
key="inspection.kotlin.unused.variable.display.name" bundle="messages.KotlinBundle"/>
</extensions>
</idea-plugin>