diff --git a/plugins/kotlin/code-insight/inspections-k2/resources/kotlin.code-insight.inspections.k2.xml b/plugins/kotlin/code-insight/inspections-k2/resources/kotlin.code-insight.inspections.k2.xml
index d6c5b5d0864e..ada821161844 100644
--- a/plugins/kotlin/code-insight/inspections-k2/resources/kotlin.code-insight.inspections.k2.xml
+++ b/plugins/kotlin/code-insight/inspections-k2/resources/kotlin.code-insight.inspections.k2.xml
@@ -754,6 +754,14 @@
key="inspection.variable.initializer.is.redundant.display.name" bundle="messages.KotlinBundle">
+
+
+ if (!property.isLocal || property.annotationEntries.isNotEmpty() || property.hasComment() || property.hasDelegate()) {
+ return@propertyVisitor
+ }
+
+ val status = statusFor(property)
+ if (status != null) {
+ val description = when (status) {
+ Status.RETURN_ONLY -> KotlinBundle.message("variable.used.only.in.following.return.and.should.be.inlined")
+ Status.EXACT_COPY -> KotlinBundle.message(
+ "variable.is.same.as.0.and.should.be.inlined",
+ (property.initializer as? KtNameReferenceExpression)?.getReferencedName().toString()
+ )
+ }
+
+ val hasMultiLineBlock = property.initializer?.hasMultiLineBlock() == true
+ val highlightType = if (hasMultiLineBlock) ProblemHighlightType.INFORMATION else ProblemHighlightType.GENERIC_ERROR_OR_WARNING
+
+ val nameIdentifier = property.nameIdentifier
+ val range = if (nameIdentifier != null) TextRange(0, nameIdentifier.textLength) else null
+
+ holder.registerProblemWithoutOfflineInformation(
+ property.nameIdentifier ?: property,
+ description,
+ isOnTheFly,
+ highlightType,
+ range,
+ InlineVariableFix()
+ )
+ }
+ }
+
+ private fun statusFor(property: KtProperty): Status? {
+ val enclosingElement = KtPsiUtil.getEnclosingElementForLocalDeclaration(property) ?: return null
+ val initializer = property.initializer ?: return null
+
+ fun KaSession.isExactCopy(): Boolean {
+ if (property.isVar || initializer !is KtNameReferenceExpression || property.typeReference != null) return false
+
+ val symbol = initializer.mainReference.resolveToSymbol()
+ val initializerSymbol = symbol as? KaLocalVariableSymbol ?: symbol as? KaParameterSymbol ?: return false
+
+ val isVal = initializerSymbol.isVal
+ val isContainingSymbolFunction = initializerSymbol.containingSymbol is KaFunctionSymbol
+ val hasDelegate = (initializerSymbol.psi as? KtProperty)?.hasDelegate() == true
+
+ if (!isVal || !isContainingSymbolFunction || hasDelegate) return false
+
+ val copyName = initializerSymbol.name.asString()
+ if (ReferencesSearch.search(property, LocalSearchScope(enclosingElement)).findFirst() == null) return false
+
+ val excludedDeclaration = initializerSymbol.psi as? KtDeclaration
+
+ val nameValidator = KotlinDeclarationNameValidator(
+ visibleDeclarationsContext = enclosingElement,
+ checkVisibleDeclarationsContext = true,
+ target = KotlinNameSuggestionProvider.ValidatorTarget.VARIABLE,
+ excludedDeclarations = listOfNotNull(excludedDeclaration)
+ )
+ return nameValidator.validate(copyName)
+ }
+
+ fun KaSession.isReturnOnly(): Boolean {
+ val nextStatement = property.getNextSiblingIgnoringWhitespaceAndComments() as? KtReturnExpression ?: return false
+ val returned = nextStatement.returnedExpression as? KtNameReferenceExpression ?: return false
+
+ val returnedSymbol = returned.mainReference.resolveToSymbol() as? KaNamedSymbol ?: return false
+
+ val elementSymbol = property.symbol as? KaNamedSymbol ?: return false
+
+ return returnedSymbol == elementSymbol
+ }
+
+ return when {
+ analyze(property) { isExactCopy() } -> Status.EXACT_COPY
+ reportImmediatelyReturnedVariables && analyze(property) { isReturnOnly() } -> Status.RETURN_ONLY
+ else -> null
+ }
+ }
+
+ private class InlineVariableFix : LocalQuickFix {
+ override fun getFamilyName(): String = KotlinBundle.message("inline.variable")
+
+ override fun startInWriteAction(): Boolean = false
+
+ override fun applyFix(project: Project, descriptor: ProblemDescriptor) {
+ val element = descriptor.psiElement.parent as? KtProperty ?: return
+ KotlinInlinePropertyHandler().inlineElement(project, null, element)
+ }
+ }
+
+ private fun LeafPsiElement.startsMultilineBlock(): Boolean =
+ node.elementType == KtTokens.LBRACE && parent.safeAs()?.isMultiLine() == true
+
+ private fun KtExpression.hasMultiLineBlock(): Boolean =
+ anyDescendantOfType { it.startsMultilineBlock() }
+
+ private fun KtProperty.hasComment(): Boolean {
+ fun Sequence.firstComment() =
+ takeWhile { it is PsiWhiteSpace || it is PsiComment }.firstIsInstanceOrNull()
+ return prevLeafs.firstComment() != null ||
+ initializer?.nextLeafs?.firstComment()?.takeIf { it.getLineNumber() == this.getLineNumber() } != null
+ }
+}
diff --git a/plugins/kotlin/code-insight/inspections-k2/tests/test/org/jetbrains/kotlin/idea/k2/inspections/tests/K2LocalInspectionTestGenerated.java b/plugins/kotlin/code-insight/inspections-k2/tests/test/org/jetbrains/kotlin/idea/k2/inspections/tests/K2LocalInspectionTestGenerated.java
index dc9163c23e7d..350ff3f13c9e 100644
--- a/plugins/kotlin/code-insight/inspections-k2/tests/test/org/jetbrains/kotlin/idea/k2/inspections/tests/K2LocalInspectionTestGenerated.java
+++ b/plugins/kotlin/code-insight/inspections-k2/tests/test/org/jetbrains/kotlin/idea/k2/inspections/tests/K2LocalInspectionTestGenerated.java
@@ -8306,6 +8306,193 @@ public abstract class K2LocalInspectionTestGenerated extends AbstractK2LocalInsp
}
}
+ @RunWith(JUnit3RunnerWithInners.class)
+ @TestMetadata("../../../idea/tests/testData/inspectionsLocal/unnecessaryVariable")
+ public abstract static class UnnecessaryVariable extends AbstractK2LocalInspectionTest {
+ @RunWith(JUnit3RunnerWithInners.class)
+ @TestMetadata("../../../idea/tests/testData/inspectionsLocal/unnecessaryVariable/reportImmediatelyReturnedVariables")
+ public static class ReportImmediatelyReturnedVariables extends AbstractK2LocalInspectionTest {
+ @java.lang.Override
+ @org.jetbrains.annotations.NotNull
+ public final KotlinPluginMode getPluginMode() {
+ return KotlinPluginMode.K2;
+ }
+
+ private void runTest(String testDataFilePath) throws Exception {
+ KotlinTestUtils.runTest(this::doTest, this, testDataFilePath);
+ }
+
+ @TestMetadata("ifElse.kt")
+ public void testIfElse() throws Exception {
+ runTest("../../../idea/tests/testData/inspectionsLocal/unnecessaryVariable/reportImmediatelyReturnedVariables/ifElse.kt");
+ }
+
+ @TestMetadata("ifElse2.kt")
+ public void testIfElse2() throws Exception {
+ runTest("../../../idea/tests/testData/inspectionsLocal/unnecessaryVariable/reportImmediatelyReturnedVariables/ifElse2.kt");
+ }
+
+ @TestMetadata("ifElse3.kt")
+ public void testIfElse3() throws Exception {
+ runTest("../../../idea/tests/testData/inspectionsLocal/unnecessaryVariable/reportImmediatelyReturnedVariables/ifElse3.kt");
+ }
+
+ @TestMetadata("ifElse4.kt")
+ public void testIfElse4() throws Exception {
+ runTest("../../../idea/tests/testData/inspectionsLocal/unnecessaryVariable/reportImmediatelyReturnedVariables/ifElse4.kt");
+ }
+
+ @TestMetadata("ifElse5.kt")
+ public void testIfElse5() throws Exception {
+ runTest("../../../idea/tests/testData/inspectionsLocal/unnecessaryVariable/reportImmediatelyReturnedVariables/ifElse5.kt");
+ }
+
+ @TestMetadata("lambda.kt")
+ public void testLambda() throws Exception {
+ runTest("../../../idea/tests/testData/inspectionsLocal/unnecessaryVariable/reportImmediatelyReturnedVariables/lambda.kt");
+ }
+
+ @TestMetadata("lambda2.kt")
+ public void testLambda2() throws Exception {
+ runTest("../../../idea/tests/testData/inspectionsLocal/unnecessaryVariable/reportImmediatelyReturnedVariables/lambda2.kt");
+ }
+
+ @TestMetadata("simpleReturn.kt")
+ public void testSimpleReturn() throws Exception {
+ runTest("../../../idea/tests/testData/inspectionsLocal/unnecessaryVariable/reportImmediatelyReturnedVariables/simpleReturn.kt");
+ }
+
+ @TestMetadata("try.kt")
+ public void testTry() throws Exception {
+ runTest("../../../idea/tests/testData/inspectionsLocal/unnecessaryVariable/reportImmediatelyReturnedVariables/try.kt");
+ }
+
+ @TestMetadata("when.kt")
+ public void testWhen() throws Exception {
+ runTest("../../../idea/tests/testData/inspectionsLocal/unnecessaryVariable/reportImmediatelyReturnedVariables/when.kt");
+ }
+ }
+
+ @RunWith(JUnit3RunnerWithInners.class)
+ @TestMetadata("../../../idea/tests/testData/inspectionsLocal/unnecessaryVariable")
+ public static class Uncategorized extends AbstractK2LocalInspectionTest {
+ @java.lang.Override
+ @org.jetbrains.annotations.NotNull
+ public final KotlinPluginMode getPluginMode() {
+ return KotlinPluginMode.K2;
+ }
+
+ private void runTest(String testDataFilePath) throws Exception {
+ KotlinTestUtils.runTest(this::doTest, this, testDataFilePath);
+ }
+
+ @TestMetadata("copyOfVal.kt")
+ public void testCopyOfVal() throws Exception {
+ runTest("../../../idea/tests/testData/inspectionsLocal/unnecessaryVariable/copyOfVal.kt");
+ }
+
+ @TestMetadata("copyOfValInLambda.kt")
+ public void testCopyOfValInLambda() throws Exception {
+ runTest("../../../idea/tests/testData/inspectionsLocal/unnecessaryVariable/copyOfValInLambda.kt");
+ }
+
+ @TestMetadata("copyOfValInLambda2.kt")
+ public void testCopyOfValInLambda2() throws Exception {
+ runTest("../../../idea/tests/testData/inspectionsLocal/unnecessaryVariable/copyOfValInLambda2.kt");
+ }
+
+ @TestMetadata("copyOfValInLambda3.kt")
+ public void testCopyOfValInLambda3() throws Exception {
+ runTest("../../../idea/tests/testData/inspectionsLocal/unnecessaryVariable/copyOfValInLambda3.kt");
+ }
+
+ @TestMetadata("copyOfValUnused.kt")
+ public void testCopyOfValUnused() throws Exception {
+ runTest("../../../idea/tests/testData/inspectionsLocal/unnecessaryVariable/copyOfValUnused.kt");
+ }
+
+ @TestMetadata("copyOfValWithExplicitType.kt")
+ public void testCopyOfValWithExplicitType() throws Exception {
+ runTest("../../../idea/tests/testData/inspectionsLocal/unnecessaryVariable/copyOfValWithExplicitType.kt");
+ }
+
+ @TestMetadata("copyOfVar.kt")
+ public void testCopyOfVar() throws Exception {
+ runTest("../../../idea/tests/testData/inspectionsLocal/unnecessaryVariable/copyOfVar.kt");
+ }
+
+ @TestMetadata("delegated.kt")
+ public void testDelegated() throws Exception {
+ runTest("../../../idea/tests/testData/inspectionsLocal/unnecessaryVariable/delegated.kt");
+ }
+
+ @TestMetadata("hasAnnotation.kt")
+ public void testHasAnnotation() throws Exception {
+ runTest("../../../idea/tests/testData/inspectionsLocal/unnecessaryVariable/hasAnnotation.kt");
+ }
+
+ @TestMetadata("hasComment.kt")
+ public void testHasComment() throws Exception {
+ runTest("../../../idea/tests/testData/inspectionsLocal/unnecessaryVariable/hasComment.kt");
+ }
+
+ @TestMetadata("hasComment2.kt")
+ public void testHasComment2() throws Exception {
+ runTest("../../../idea/tests/testData/inspectionsLocal/unnecessaryVariable/hasComment2.kt");
+ }
+
+ @TestMetadata("hasComment3.kt")
+ public void testHasComment3() throws Exception {
+ runTest("../../../idea/tests/testData/inspectionsLocal/unnecessaryVariable/hasComment3.kt");
+ }
+
+ @TestMetadata("it.kt")
+ public void testIt() throws Exception {
+ runTest("../../../idea/tests/testData/inspectionsLocal/unnecessaryVariable/it.kt");
+ }
+
+ @TestMetadata("override.kt")
+ public void testOverride() throws Exception {
+ runTest("../../../idea/tests/testData/inspectionsLocal/unnecessaryVariable/override.kt");
+ }
+
+ @TestMetadata("paramCopy.kt")
+ public void testParamCopy() throws Exception {
+ runTest("../../../idea/tests/testData/inspectionsLocal/unnecessaryVariable/paramCopy.kt");
+ }
+
+ @TestMetadata("parameterWithSameNameBelow.kt")
+ public void testParameterWithSameNameBelow() throws Exception {
+ runTest("../../../idea/tests/testData/inspectionsLocal/unnecessaryVariable/parameterWithSameNameBelow.kt");
+ }
+
+ @TestMetadata("propertyCopy.kt")
+ public void testPropertyCopy() throws Exception {
+ runTest("../../../idea/tests/testData/inspectionsLocal/unnecessaryVariable/propertyCopy.kt");
+ }
+
+ @TestMetadata("simpleReturn.kt")
+ public void testSimpleReturn() throws Exception {
+ runTest("../../../idea/tests/testData/inspectionsLocal/unnecessaryVariable/simpleReturn.kt");
+ }
+
+ @TestMetadata("topLevelCopy.kt")
+ public void testTopLevelCopy() throws Exception {
+ runTest("../../../idea/tests/testData/inspectionsLocal/unnecessaryVariable/topLevelCopy.kt");
+ }
+
+ @TestMetadata("varCopy.kt")
+ public void testVarCopy() throws Exception {
+ runTest("../../../idea/tests/testData/inspectionsLocal/unnecessaryVariable/varCopy.kt");
+ }
+
+ @TestMetadata("whenSubject.kt")
+ public void testWhenSubject() throws Exception {
+ runTest("../../../idea/tests/testData/inspectionsLocal/unnecessaryVariable/whenSubject.kt");
+ }
+ }
+ }
+
@RunWith(JUnit3RunnerWithInners.class)
@TestMetadata("../../../idea/tests/testData/inspectionsLocal/canBeParameter")
public static class CanBeParameter extends AbstractK2LocalInspectionTest {
diff --git a/plugins/kotlin/idea/tests/testData/inspectionsLocal/unnecessaryVariable/.k2Inspection b/plugins/kotlin/idea/tests/testData/inspectionsLocal/unnecessaryVariable/.k2Inspection
new file mode 100644
index 000000000000..3a91db00514b
--- /dev/null
+++ b/plugins/kotlin/idea/tests/testData/inspectionsLocal/unnecessaryVariable/.k2Inspection
@@ -0,0 +1 @@
+org.jetbrains.kotlin.idea.k2.codeinsight.inspections.UnnecessaryVariableInspection
\ No newline at end of file
diff --git a/plugins/kotlin/idea/tests/testData/inspectionsLocal/unnecessaryVariable/it.kt b/plugins/kotlin/idea/tests/testData/inspectionsLocal/unnecessaryVariable/it.kt
index ce65bce91fa2..b75e3dde1e63 100644
--- a/plugins/kotlin/idea/tests/testData/inspectionsLocal/unnecessaryVariable/it.kt
+++ b/plugins/kotlin/idea/tests/testData/inspectionsLocal/unnecessaryVariable/it.kt
@@ -8,4 +8,5 @@ fun foo(a: List, b: List) {
println(a2.length)
}
}
-}
\ No newline at end of file
+}
+// IGNORE_K2
\ No newline at end of file
diff --git a/plugins/kotlin/util/test-generator-fir/test/org/jetbrains/kotlin/fir/testGenerator/codeinsight/GenerateK2InspectionTests.kt b/plugins/kotlin/util/test-generator-fir/test/org/jetbrains/kotlin/fir/testGenerator/codeinsight/GenerateK2InspectionTests.kt
index 9aa30b321c06..067010c179b2 100644
--- a/plugins/kotlin/util/test-generator-fir/test/org/jetbrains/kotlin/fir/testGenerator/codeinsight/GenerateK2InspectionTests.kt
+++ b/plugins/kotlin/util/test-generator-fir/test/org/jetbrains/kotlin/fir/testGenerator/codeinsight/GenerateK2InspectionTests.kt
@@ -70,6 +70,7 @@ internal fun MutableTWorkspace.generateK2InspectionTests() {
model("${idea}/inspectionsLocal/redundantUnitReturnType")
model("${idea}/inspectionsLocal/suspiciousCollectionReassignment")
model("${idea}/inspectionsLocal/suspiciousVarProperty")
+ model("${idea}/inspectionsLocal/unnecessaryVariable")
model("${idea}/inspectionsLocal/canBeParameter")
model("${idea}/inspectionsLocal/arrayInDataClass")
model("${idea}/inspectionsLocal/collections/simplifiableCallChain")