[FIR] Add CanBeReplacedWithOperatorAssignmentChecker
This commit is contained in:
+7
@@ -0,0 +1,7 @@
|
||||
fun goo() {
|
||||
var a = 2
|
||||
val b = 4
|
||||
a <!CAN_BE_REPLACED_WITH_OPERATOR_ASSIGNMENT!>=<!> a + 1 + b
|
||||
a <!CAN_BE_REPLACED_WITH_OPERATOR_ASSIGNMENT!>=<!> (a + 1)
|
||||
a = a * b + 1
|
||||
}
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
FILE: BasicTest.kt
|
||||
public final fun goo(): R|kotlin/Unit| {
|
||||
lvar a: R|kotlin/Int| = Int(2)
|
||||
lval b: R|kotlin/Int| = Int(4)
|
||||
R|<local>/a| = R|<local>/a|.R|kotlin/Int.plus|(Int(1)).R|kotlin/Int.plus|(R|<local>/b|)
|
||||
R|<local>/a| = R|<local>/a|.R|kotlin/Int.plus|(Int(1))
|
||||
R|<local>/a| = R|<local>/a|.R|kotlin/Int.times|(R|<local>/b|).R|kotlin/Int.plus|(Int(1))
|
||||
}
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
fun foo() {
|
||||
var a = 0
|
||||
a = (a + 1) / 2
|
||||
}
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
FILE: ComplexExpression.kt
|
||||
public final fun foo(): R|kotlin/Unit| {
|
||||
lvar a: R|kotlin/Int| = Int(0)
|
||||
R|<local>/a| = R|<local>/a|.R|kotlin/Int.plus|(Int(1)).R|kotlin/Int.div|(Int(2))
|
||||
}
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
fun foo() {
|
||||
var a = 0
|
||||
a += 10 + a
|
||||
}
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
FILE: OperatorAssignment.kt
|
||||
public final fun foo(): R|kotlin/Unit| {
|
||||
lvar a: R|kotlin/Int| = Int(0)
|
||||
R|<local>/a| = R|<local>/a|.R|kotlin/Int.plus|(Int(10).R|kotlin/Int.plus|(R|<local>/a|))
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
// WITH_RUNTIME
|
||||
fun foo() {
|
||||
var list1 = java.util.Collections.emptyList<String>()
|
||||
val list2 = listOf("b")
|
||||
list1 = list1 + list2
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
FILE: flexibleTypeBug.kt
|
||||
public final fun foo(): R|kotlin/Unit| {
|
||||
lvar list1: R|ft<kotlin/collections/MutableList<ft<kotlin/String, kotlin/String?>!>, kotlin/collections/List<ft<kotlin/String, kotlin/String?>!>?>!| = Q|java/util/Collections|.R|java/util/Collections.emptyList|<R|ft<kotlin/String, kotlin/String?>!|>()
|
||||
lval list2: R|kotlin/collections/List<kotlin/String>| = R|kotlin/collections/listOf|<R|kotlin/String|>(String(b))
|
||||
R|<local>/list1| = R|<local>/list1|.R|kotlin/collections/plus|<R|ft<kotlin/String, kotlin/String?>!|>(R|<local>/list2|)
|
||||
}
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
fun foo() {
|
||||
var x = 0
|
||||
x = x / 1 + 1
|
||||
}
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
FILE: illegalMultipleOperators.kt
|
||||
public final fun foo(): R|kotlin/Unit| {
|
||||
lvar x: R|kotlin/Int| = Int(0)
|
||||
R|<local>/x| = R|<local>/x|.R|kotlin/Int.div|(Int(1)).R|kotlin/Int.plus|(Int(1))
|
||||
}
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
fun foo() {
|
||||
var x = 0
|
||||
val y = 0
|
||||
x = y / x + 0
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
FILE: illegalMultipleOperatorsMiddle.kt
|
||||
public final fun foo(): R|kotlin/Unit| {
|
||||
lvar x: R|kotlin/Int| = Int(0)
|
||||
lval y: R|kotlin/Int| = Int(0)
|
||||
R|<local>/x| = R|<local>/y|.R|kotlin/Int.div|(R|<local>/x|).R|kotlin/Int.plus|(Int(0))
|
||||
}
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
fun foo() {
|
||||
var x = 0
|
||||
x = 1 - x
|
||||
}
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
FILE: invalidSubtraction.kt
|
||||
public final fun foo(): R|kotlin/Unit| {
|
||||
lvar x: R|kotlin/Int| = Int(0)
|
||||
R|<local>/x| = Int(1).R|kotlin/Int.minus|(R|<local>/x|)
|
||||
}
|
||||
Vendored
+7
@@ -0,0 +1,7 @@
|
||||
// WITH_RUNTIME
|
||||
fun foo() {
|
||||
var list = listOf(1, 2, 3)
|
||||
// Should not be highlighted because it's the way we use to say explicitly
|
||||
// "yes, we want to re-assign this immutable list"
|
||||
list = list + 4
|
||||
}
|
||||
Vendored
+5
@@ -0,0 +1,5 @@
|
||||
FILE: list.kt
|
||||
public final fun foo(): R|kotlin/Unit| {
|
||||
lvar list: R|kotlin/collections/List<kotlin/Int>| = R|kotlin/collections/listOf|<R|kotlin/Int|>(vararg(Int(1), Int(2), Int(3)))
|
||||
R|<local>/list| = R|<local>/list|.R|kotlin/collections/plus|<R|kotlin/Int|>(Int(4))
|
||||
}
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
fun foo() {
|
||||
var x = 0
|
||||
var y = 0
|
||||
x <!CAN_BE_REPLACED_WITH_OPERATOR_ASSIGNMENT!>=<!> x + y + 5
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
FILE: multipleOperators.kt
|
||||
public final fun foo(): R|kotlin/Unit| {
|
||||
lvar x: R|kotlin/Int| = Int(0)
|
||||
lvar y: R|kotlin/Int| = Int(0)
|
||||
R|<local>/x| = R|<local>/x|.R|kotlin/Int.plus|(R|<local>/y|).R|kotlin/Int.plus|(Int(5))
|
||||
}
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
fun foo() {
|
||||
var x = 0
|
||||
var y = 0
|
||||
x <!CAN_BE_REPLACED_WITH_OPERATOR_ASSIGNMENT!>=<!> y + x + 5
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
FILE: multipleOperatorsRightSideRepeat.kt
|
||||
public final fun foo(): R|kotlin/Unit| {
|
||||
lvar x: R|kotlin/Int| = Int(0)
|
||||
lvar y: R|kotlin/Int| = Int(0)
|
||||
R|<local>/x| = R|<local>/y|.R|kotlin/Int.plus|(R|<local>/x|).R|kotlin/Int.plus|(Int(5))
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
// WITH_RUNTIME
|
||||
|
||||
fun foo() {
|
||||
var listVar = mutableListOf(1, 2, 3)
|
||||
// now, Idea hightlights this code like error (cuz listVar
|
||||
// is mutable and listVar + 4 is immutable) and like warning
|
||||
// (cuz can be replaced with +=)
|
||||
listVar = listVar + 4
|
||||
}
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
FILE: mutableList.kt
|
||||
public final fun foo(): R|kotlin/Unit| {
|
||||
lvar listVar: R|kotlin/collections/MutableList<kotlin/Int>| = R|kotlin/collections/mutableListOf|<R|kotlin/Int|>(vararg(Int(1), Int(2), Int(3)))
|
||||
R|<local>/listVar| = R|<local>/listVar|.R|kotlin/collections/plus|<R|kotlin/Int|>(Int(4))
|
||||
}
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
fun foo() {
|
||||
var x = 0
|
||||
x <!CAN_BE_REPLACED_WITH_OPERATOR_ASSIGNMENT!>=<!> x - 1 - 1
|
||||
|
||||
x <!CAN_BE_REPLACED_WITH_OPERATOR_ASSIGNMENT!>=<!> x / 1
|
||||
x = 1 / x
|
||||
x <!CAN_BE_REPLACED_WITH_OPERATOR_ASSIGNMENT!>=<!> -1 + x
|
||||
}
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
FILE: nonCommutativeRepeat.kt
|
||||
public final fun foo(): R|kotlin/Unit| {
|
||||
lvar x: R|kotlin/Int| = Int(0)
|
||||
R|<local>/x| = R|<local>/x|.R|kotlin/Int.minus|(Int(1)).R|kotlin/Int.minus|(Int(1))
|
||||
R|<local>/x| = R|<local>/x|.R|kotlin/Int.div|(Int(1))
|
||||
R|<local>/x| = Int(1).R|kotlin/Int.div|(R|<local>/x|)
|
||||
R|<local>/x| = Int(1).R|kotlin/Int.unaryMinus|().R|kotlin/Int.plus|(R|<local>/x|)
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
fun foo() {
|
||||
var x = 0
|
||||
val y = 0
|
||||
val z = 0
|
||||
x = y + z
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
FILE: nonRepeatingAssignment.kt
|
||||
public final fun foo(): R|kotlin/Unit| {
|
||||
lvar x: R|kotlin/Int| = Int(0)
|
||||
lval y: R|kotlin/Int| = Int(0)
|
||||
lval z: R|kotlin/Int| = Int(0)
|
||||
R|<local>/x| = R|<local>/y|.R|kotlin/Int.plus|(R|<local>/z|)
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
class A
|
||||
|
||||
operator fun A.plus(a: A): A = A()
|
||||
operator fun A.plusAssign(a: A){}
|
||||
|
||||
fun foo() {
|
||||
var a1 = A()
|
||||
val a2 = A()
|
||||
a1 = a1 + a2
|
||||
}
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
FILE: plusAssignConflict.kt
|
||||
public final class A : R|kotlin/Any| {
|
||||
public constructor(): R|A| {
|
||||
super<R|kotlin/Any|>()
|
||||
}
|
||||
|
||||
}
|
||||
public final operator fun R|A|.plus(a: R|A|): R|A| {
|
||||
^plus R|/A.A|()
|
||||
}
|
||||
public final operator fun R|A|.plusAssign(a: R|A|): R|kotlin/Unit| {
|
||||
}
|
||||
public final fun foo(): R|kotlin/Unit| {
|
||||
lvar a1: R|A| = R|/A.A|()
|
||||
lval a2: R|A| = R|/A.A|()
|
||||
R|<local>/a1| = R|<local>/a1|.R|/plus|(R|<local>/a2|)
|
||||
}
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
fun foo() {
|
||||
var x = 0
|
||||
x <!CAN_BE_REPLACED_WITH_OPERATOR_ASSIGNMENT!>=<!> 1 + x
|
||||
}
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
FILE: rightSideRepeat.kt
|
||||
public final fun foo(): R|kotlin/Unit| {
|
||||
lvar x: R|kotlin/Int| = Int(0)
|
||||
R|<local>/x| = Int(1).R|kotlin/Int.plus|(R|<local>/x|)
|
||||
}
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
fun foo() {
|
||||
var y = 0
|
||||
val x = 0
|
||||
y <!CAN_BE_REPLACED_WITH_OPERATOR_ASSIGNMENT!>=<!> y + x
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
FILE: simpleAssign.kt
|
||||
public final fun foo(): R|kotlin/Unit| {
|
||||
lvar y: R|kotlin/Int| = Int(0)
|
||||
lval x: R|kotlin/Int| = Int(0)
|
||||
R|<local>/y| = R|<local>/y|.R|kotlin/Int.plus|(R|<local>/x|)
|
||||
}
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
fun foo() {
|
||||
var x = 0
|
||||
x <!CAN_BE_REPLACED_WITH_OPERATOR_ASSIGNMENT!>=<!> x + 1
|
||||
}
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
FILE: validAddition.kt
|
||||
public final fun foo(): R|kotlin/Unit| {
|
||||
lvar x: R|kotlin/Int| = Int(0)
|
||||
R|<local>/x| = R|<local>/x|.R|kotlin/Int.plus|(Int(1))
|
||||
}
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
fun foo() {
|
||||
var x = 0
|
||||
x <!CAN_BE_REPLACED_WITH_OPERATOR_ASSIGNMENT!>=<!> x - 1
|
||||
}
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
FILE: validSubtraction.kt
|
||||
public final fun foo(): R|kotlin/Unit| {
|
||||
lvar x: R|kotlin/Int| = Int(0)
|
||||
R|<local>/x| = R|<local>/x|.R|kotlin/Int.minus|(Int(1))
|
||||
}
|
||||
Generated
+103
@@ -47,4 +47,107 @@ public class ExtendedFirDiagnosticsTestGenerated extends AbstractExtendedFirDiag
|
||||
public void testRedundantVisibilityModifierChecker() throws Exception {
|
||||
runTest("compiler/fir/analysis-tests/testData/extendedCheckers/RedundantVisibilityModifierChecker.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("compiler/fir/analysis-tests/testData/extendedCheckers/canBeReplacedWithOperatorAssignment")
|
||||
@TestDataPath("$PROJECT_ROOT")
|
||||
@RunWith(JUnit3RunnerWithInners.class)
|
||||
public static class CanBeReplacedWithOperatorAssignment extends AbstractExtendedFirDiagnosticsTest {
|
||||
private void runTest(String testDataFilePath) throws Exception {
|
||||
KotlinTestUtils.runTest(this::doTest, this, testDataFilePath);
|
||||
}
|
||||
|
||||
public void testAllFilesPresentInCanBeReplacedWithOperatorAssignment() throws Exception {
|
||||
KotlinTestUtils.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/fir/analysis-tests/testData/extendedCheckers/canBeReplacedWithOperatorAssignment"), Pattern.compile("^([^.]+)\\.kt$"), null, true);
|
||||
}
|
||||
|
||||
@TestMetadata("BasicTest.kt")
|
||||
public void testBasicTest() throws Exception {
|
||||
runTest("compiler/fir/analysis-tests/testData/extendedCheckers/canBeReplacedWithOperatorAssignment/BasicTest.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("ComplexExpression.kt")
|
||||
public void testComplexExpression() throws Exception {
|
||||
runTest("compiler/fir/analysis-tests/testData/extendedCheckers/canBeReplacedWithOperatorAssignment/ComplexExpression.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("flexibleTypeBug.kt")
|
||||
public void testFlexibleTypeBug() throws Exception {
|
||||
runTest("compiler/fir/analysis-tests/testData/extendedCheckers/canBeReplacedWithOperatorAssignment/flexibleTypeBug.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("illegalMultipleOperators.kt")
|
||||
public void testIllegalMultipleOperators() throws Exception {
|
||||
runTest("compiler/fir/analysis-tests/testData/extendedCheckers/canBeReplacedWithOperatorAssignment/illegalMultipleOperators.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("illegalMultipleOperatorsMiddle.kt")
|
||||
public void testIllegalMultipleOperatorsMiddle() throws Exception {
|
||||
runTest("compiler/fir/analysis-tests/testData/extendedCheckers/canBeReplacedWithOperatorAssignment/illegalMultipleOperatorsMiddle.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("invalidSubtraction.kt")
|
||||
public void testInvalidSubtraction() throws Exception {
|
||||
runTest("compiler/fir/analysis-tests/testData/extendedCheckers/canBeReplacedWithOperatorAssignment/invalidSubtraction.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("list.kt")
|
||||
public void testList() throws Exception {
|
||||
runTest("compiler/fir/analysis-tests/testData/extendedCheckers/canBeReplacedWithOperatorAssignment/list.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("multipleOperators.kt")
|
||||
public void testMultipleOperators() throws Exception {
|
||||
runTest("compiler/fir/analysis-tests/testData/extendedCheckers/canBeReplacedWithOperatorAssignment/multipleOperators.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("multipleOperatorsRightSideRepeat.kt")
|
||||
public void testMultipleOperatorsRightSideRepeat() throws Exception {
|
||||
runTest("compiler/fir/analysis-tests/testData/extendedCheckers/canBeReplacedWithOperatorAssignment/multipleOperatorsRightSideRepeat.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("mutableList.kt")
|
||||
public void testMutableList() throws Exception {
|
||||
runTest("compiler/fir/analysis-tests/testData/extendedCheckers/canBeReplacedWithOperatorAssignment/mutableList.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("nonCommutativeRepeat.kt")
|
||||
public void testNonCommutativeRepeat() throws Exception {
|
||||
runTest("compiler/fir/analysis-tests/testData/extendedCheckers/canBeReplacedWithOperatorAssignment/nonCommutativeRepeat.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("nonRepeatingAssignment.kt")
|
||||
public void testNonRepeatingAssignment() throws Exception {
|
||||
runTest("compiler/fir/analysis-tests/testData/extendedCheckers/canBeReplacedWithOperatorAssignment/nonRepeatingAssignment.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("OperatorAssignment.kt")
|
||||
public void testOperatorAssignment() throws Exception {
|
||||
runTest("compiler/fir/analysis-tests/testData/extendedCheckers/canBeReplacedWithOperatorAssignment/OperatorAssignment.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("plusAssignConflict.kt")
|
||||
public void testPlusAssignConflict() throws Exception {
|
||||
runTest("compiler/fir/analysis-tests/testData/extendedCheckers/canBeReplacedWithOperatorAssignment/plusAssignConflict.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("rightSideRepeat.kt")
|
||||
public void testRightSideRepeat() throws Exception {
|
||||
runTest("compiler/fir/analysis-tests/testData/extendedCheckers/canBeReplacedWithOperatorAssignment/rightSideRepeat.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("simpleAssign.kt")
|
||||
public void testSimpleAssign() throws Exception {
|
||||
runTest("compiler/fir/analysis-tests/testData/extendedCheckers/canBeReplacedWithOperatorAssignment/simpleAssign.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("validAddition.kt")
|
||||
public void testValidAddition() throws Exception {
|
||||
runTest("compiler/fir/analysis-tests/testData/extendedCheckers/canBeReplacedWithOperatorAssignment/validAddition.kt");
|
||||
}
|
||||
|
||||
@TestMetadata("validSubtraction.kt")
|
||||
public void testValidSubtraction() throws Exception {
|
||||
runTest("compiler/fir/analysis-tests/testData/extendedCheckers/canBeReplacedWithOperatorAssignment/validSubtraction.kt");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,14 +79,18 @@ private class ComposedExpressionCheckers : ExpressionCheckers() {
|
||||
get() = _qualifiedAccessCheckers
|
||||
override val functionCallCheckers: List<FirFunctionCallChecker>
|
||||
get() = _functionCallCheckers
|
||||
override val variableAssignmentCheckers: List<FirVariableAssignmentChecker>
|
||||
get() = _variableAssignmentCheckers
|
||||
|
||||
private val _expressionCheckers: MutableList<FirBasicExpresionChecker> = mutableListOf()
|
||||
private val _qualifiedAccessCheckers: MutableList<FirQualifiedAccessChecker> = mutableListOf()
|
||||
private val _functionCallCheckers: MutableList<FirFunctionCallChecker> = mutableListOf()
|
||||
private val _variableAssignmentCheckers: MutableList<FirVariableAssignmentChecker> = mutableListOf()
|
||||
|
||||
fun register(checkers: ExpressionCheckers) {
|
||||
_expressionCheckers += checkers.allExpressionCheckers
|
||||
_qualifiedAccessCheckers += checkers.allQualifiedAccessCheckers
|
||||
_functionCallCheckers += checkers.allFunctionCallCheckers
|
||||
_variableAssignmentCheckers += checkers.variableAssignmentCheckers
|
||||
}
|
||||
}
|
||||
|
||||
+1
-4
@@ -5,10 +5,7 @@
|
||||
|
||||
package org.jetbrains.kotlin.fir.analysis.checkers.declaration
|
||||
|
||||
import org.jetbrains.kotlin.fir.analysis.checkers.extended.RedundantExplicitTypeChecker
|
||||
import org.jetbrains.kotlin.fir.analysis.checkers.extended.RedundantModalityModifierChecker
|
||||
import org.jetbrains.kotlin.fir.analysis.checkers.extended.RedundantReturnUnitType
|
||||
import org.jetbrains.kotlin.fir.analysis.checkers.extended.RedundantVisibilityModifierChecker
|
||||
import org.jetbrains.kotlin.fir.analysis.checkers.extended.*
|
||||
|
||||
object ExtendedDeclarationCheckers : DeclarationCheckers() {
|
||||
override val declarationCheckers = listOf(
|
||||
|
||||
+1
@@ -13,6 +13,7 @@ abstract class ExpressionCheckers {
|
||||
open val expressionCheckers: List<FirBasicExpresionChecker> = emptyList()
|
||||
open val qualifiedAccessCheckers: List<FirQualifiedAccessChecker> = emptyList()
|
||||
open val functionCallCheckers: List<FirFunctionCallChecker> = emptyList()
|
||||
open val variableAssignmentCheckers: List<FirVariableAssignmentChecker> = emptyList()
|
||||
|
||||
internal val allExpressionCheckers get() = expressionCheckers
|
||||
internal val allQualifiedAccessCheckers get() = qualifiedAccessCheckers + allExpressionCheckers
|
||||
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
/*
|
||||
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.fir.analysis.checkers.expression
|
||||
|
||||
import org.jetbrains.kotlin.fir.analysis.checkers.extended.CanBeReplacedWithOperatorAssignmentChecker
|
||||
|
||||
object ExtendedExpressionCheckers : ExpressionCheckers() {
|
||||
override val variableAssignmentCheckers: List<FirVariableAssignmentChecker> = listOf(
|
||||
CanBeReplacedWithOperatorAssignmentChecker
|
||||
)
|
||||
}
|
||||
+5
-3
@@ -7,14 +7,16 @@ package org.jetbrains.kotlin.fir.analysis.checkers.expression
|
||||
|
||||
import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.DiagnosticReporter
|
||||
import org.jetbrains.kotlin.fir.expressions.FirExpression
|
||||
import org.jetbrains.kotlin.fir.expressions.FirFunctionCall
|
||||
import org.jetbrains.kotlin.fir.expressions.FirQualifiedAccessExpression
|
||||
import org.jetbrains.kotlin.fir.expressions.FirStatement
|
||||
import org.jetbrains.kotlin.fir.expressions.FirVariableAssignment
|
||||
|
||||
abstract class FirExpressionChecker<in E : FirExpression> {
|
||||
abstract class FirExpressionChecker<in E : FirStatement> {
|
||||
abstract fun check(functionCall: E, context: CheckerContext, reporter: DiagnosticReporter)
|
||||
}
|
||||
|
||||
typealias FirBasicExpresionChecker = FirExpressionChecker<FirExpression>
|
||||
typealias FirBasicExpresionChecker = FirExpressionChecker<FirStatement>
|
||||
typealias FirQualifiedAccessChecker = FirExpressionChecker<FirQualifiedAccessExpression>
|
||||
typealias FirFunctionCallChecker = FirExpressionChecker<FirFunctionCall>
|
||||
typealias FirVariableAssignmentChecker = FirExpressionChecker<FirVariableAssignment>
|
||||
|
||||
+72
@@ -0,0 +1,72 @@
|
||||
/*
|
||||
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.fir.analysis.checkers.extended
|
||||
|
||||
import com.intellij.psi.tree.IElementType
|
||||
import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext
|
||||
import org.jetbrains.kotlin.fir.analysis.checkers.expression.FirExpressionChecker
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.DiagnosticReporter
|
||||
import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors
|
||||
import org.jetbrains.kotlin.fir.expressions.FirFunctionCall
|
||||
import org.jetbrains.kotlin.fir.expressions.FirVariableAssignment
|
||||
import org.jetbrains.kotlin.fir.expressions.toResolvedCallableSymbol
|
||||
import org.jetbrains.kotlin.fir.psi
|
||||
import org.jetbrains.kotlin.fir.references.FirResolvedNamedReference
|
||||
import org.jetbrains.kotlin.fir.symbols.StandardClassIds
|
||||
import org.jetbrains.kotlin.fir.toFirPsiSourceElement
|
||||
import org.jetbrains.kotlin.fir.types.classId
|
||||
import org.jetbrains.kotlin.fir.types.coneType
|
||||
import org.jetbrains.kotlin.lexer.KtTokens
|
||||
import org.jetbrains.kotlin.psi.KtBinaryExpression
|
||||
import org.jetbrains.kotlin.psi.KtNameReferenceExpression
|
||||
|
||||
object CanBeReplacedWithOperatorAssignmentChecker : FirExpressionChecker<FirVariableAssignment>() {
|
||||
override fun check(functionCall: FirVariableAssignment, context: CheckerContext, reporter: DiagnosticReporter) {
|
||||
val lValue = functionCall.lValue
|
||||
if (lValue !is FirResolvedNamedReference) return
|
||||
val operator = functionCall.psi?.children?.getOrNull(1) ?: return
|
||||
if (operator.text != "=") return
|
||||
|
||||
val lValuePsi = lValue.psi as? KtNameReferenceExpression ?: return
|
||||
val rValue = functionCall.rValue as? FirFunctionCall ?: return
|
||||
val rValuePsi = rValue.psi as? KtBinaryExpression ?: return
|
||||
val rValueClassId = rValue.explicitReceiver?.typeRef?.coneType?.classId
|
||||
|
||||
if (rValueClassId !in StandardClassIds.primitiveTypes) return
|
||||
val rValueResolvedSymbol = rValue.toResolvedCallableSymbol() ?: return
|
||||
if (rValueResolvedSymbol.callableId.classId !in StandardClassIds.primitiveTypes) return
|
||||
|
||||
if (rValuePsi.matcher(lValuePsi)) {
|
||||
val operatorStatement = operator.toFirPsiSourceElement()
|
||||
reporter.report(operatorStatement, FirErrors.CAN_BE_REPLACED_WITH_OPERATOR_ASSIGNMENT)
|
||||
}
|
||||
}
|
||||
|
||||
fun KtBinaryExpression.matcher(variable: KtNameReferenceExpression): Boolean {
|
||||
if ((left as? KtNameReferenceExpression)?.getReferencedName() == variable.getReferencedName()) return true
|
||||
if ((right as? KtNameReferenceExpression)?.getReferencedName() == variable.getReferencedName() && isCommutative()) return true
|
||||
|
||||
return if (isCommutative()) {
|
||||
val leftExpression = left as? KtBinaryExpression
|
||||
val rightExpression = right as? KtBinaryExpression
|
||||
|
||||
val isLeftMatch = isHierarchicallyTrue(operationToken, leftExpression?.operationToken)
|
||||
&& leftExpression?.matcher(variable) == true
|
||||
val isRightMatch = isHierarchicallyTrue(operationToken, rightExpression?.operationToken)
|
||||
&& rightExpression?.matcher(variable) == true
|
||||
isLeftMatch or isRightMatch
|
||||
} else {
|
||||
val leftExpression = left as? KtBinaryExpression
|
||||
|
||||
isHierarchicallyTrue(operationToken, leftExpression?.operationToken)
|
||||
&& leftExpression?.matcher(variable) == true
|
||||
}
|
||||
}
|
||||
|
||||
private fun KtBinaryExpression.isCommutative() = this.operationToken == KtTokens.PLUS || this.operationToken == KtTokens.MUL
|
||||
|
||||
private fun isHierarchicallyTrue(currentOperation: IElementType, nextOperation: IElementType?) = currentOperation == nextOperation
|
||||
}
|
||||
+5
-1
@@ -79,7 +79,11 @@ class ExpressionCheckersDiagnosticComponent(collector: AbstractDiagnosticCollect
|
||||
runCheck { checkers.expressionCheckers.check(getClassCall, data, it) }
|
||||
}
|
||||
|
||||
private fun <E : FirExpression> List<FirExpressionChecker<E>>.check(expression: E, context: CheckerContext, reporter: DiagnosticReporter) {
|
||||
override fun visitVariableAssignment(variableAssignment: FirVariableAssignment, data: CheckerContext) {
|
||||
runCheck { checkers.variableAssignmentCheckers.check(variableAssignment, data, it) }
|
||||
}
|
||||
|
||||
private fun <E : FirStatement> List<FirExpressionChecker<E>>.check(expression: E, context: CheckerContext, reporter: DiagnosticReporter) {
|
||||
for (checker in this) {
|
||||
checker.check(expression, context, reporter)
|
||||
}
|
||||
|
||||
@@ -87,6 +87,7 @@ object FirErrors {
|
||||
val REDUNDANT_MODALITY_MODIFIER by warning0<FirSourceElement, PsiElement>()
|
||||
val REDUNDANT_RETURN_UNIT_TYPE by warning0<FirSourceElement, PsiTypeElement>()
|
||||
val REDUNDANT_EXPLICIT_TYPE by warning0<FirSourceElement, PsiElement>()
|
||||
val CAN_BE_REPLACED_WITH_OPERATOR_ASSIGNMENT by warning0<FirSourceElement, PsiElement>()
|
||||
}
|
||||
|
||||
|
||||
|
||||
+2
@@ -6,10 +6,12 @@
|
||||
package org.jetbrains.kotlin.fir
|
||||
|
||||
import org.jetbrains.kotlin.fir.analysis.checkers.declaration.ExtendedDeclarationCheckers
|
||||
import org.jetbrains.kotlin.fir.analysis.checkers.expression.ExtendedExpressionCheckers
|
||||
import org.jetbrains.kotlin.fir.analysis.checkersComponent
|
||||
|
||||
abstract class AbstractExtendedFirDiagnosticsTest : AbstractFirDiagnosticsTest() {
|
||||
override fun configureSession(session: FirSession) {
|
||||
session.checkersComponent.register(ExtendedDeclarationCheckers)
|
||||
session.checkersComponent.register(ExtendedExpressionCheckers)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user