diff --git a/compiler/fir/analysis-tests/testData/extendedCheckers/RedundantVisibilityModifierChecker.kt b/compiler/fir/analysis-tests/testData/extendedCheckers/RedundantVisibilityModifierChecker.kt index 60bb9ed977f..378a5a75428 100644 --- a/compiler/fir/analysis-tests/testData/extendedCheckers/RedundantVisibilityModifierChecker.kt +++ b/compiler/fir/analysis-tests/testData/extendedCheckers/RedundantVisibilityModifierChecker.kt @@ -1,10 +1,9 @@ fun f() { - + public var baz = 0 class LocalClass { internal var foo = 0 } LocalClass().foo = 1 - public var baz = 0 } internal inline fun internal() { diff --git a/compiler/fir/analysis-tests/testData/extendedCheckers/RedundantVisibilityModifierChecker.txt b/compiler/fir/analysis-tests/testData/extendedCheckers/RedundantVisibilityModifierChecker.txt index 131e4e5fcf0..3d9510284c9 100644 --- a/compiler/fir/analysis-tests/testData/extendedCheckers/RedundantVisibilityModifierChecker.txt +++ b/compiler/fir/analysis-tests/testData/extendedCheckers/RedundantVisibilityModifierChecker.txt @@ -1,5 +1,6 @@ FILE: RedundantVisibilityModifierChecker.kt public final fun f(): R|kotlin/Unit| { + lvar baz: R|kotlin/Int| = Int(0) local final class LocalClass : R|kotlin/Any| { public constructor(): R|LocalClass| { super() @@ -12,7 +13,6 @@ FILE: RedundantVisibilityModifierChecker.kt } R|/LocalClass.LocalClass|().R|/LocalClass.foo| = Int(1) - lvar baz: R|kotlin/Int| = Int(0) } internal final inline fun internal(): R|kotlin/Unit| { R|/f|() diff --git a/compiler/fir/analysis-tests/tests/org/jetbrains/kotlin/fir/ExtendedFirWithLightTreeDiagnosticsTestGenerated.java b/compiler/fir/analysis-tests/tests/org/jetbrains/kotlin/fir/ExtendedFirWithLightTreeDiagnosticsTestGenerated.java new file mode 100644 index 00000000000..d2044ddc641 --- /dev/null +++ b/compiler/fir/analysis-tests/tests/org/jetbrains/kotlin/fir/ExtendedFirWithLightTreeDiagnosticsTestGenerated.java @@ -0,0 +1,314 @@ +/* + * 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; + +import com.intellij.testFramework.TestDataPath; +import org.jetbrains.kotlin.test.JUnit3RunnerWithInners; +import org.jetbrains.kotlin.test.KotlinTestUtils; +import org.jetbrains.kotlin.test.TestMetadata; +import org.junit.runner.RunWith; + +import java.io.File; +import java.util.regex.Pattern; + +/** This class is generated by {@link org.jetbrains.kotlin.generators.tests.TestsPackage}. DO NOT MODIFY MANUALLY */ +@SuppressWarnings("all") +@TestMetadata("compiler/fir/analysis-tests/testData/extendedCheckers") +@TestDataPath("$PROJECT_ROOT") +@RunWith(JUnit3RunnerWithInners.class) +public class ExtendedFirWithLightTreeDiagnosticsTestGenerated extends AbstractExtendedFirWithLightTreeDiagnosticsTest { + private void runTest(String testDataFilePath) throws Exception { + KotlinTestUtils.runTest(this::doTest, this, testDataFilePath); + } + + public void testAllFilesPresentInExtendedCheckers() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/fir/analysis-tests/testData/extendedCheckers"), Pattern.compile("^([^.]+)\\.kt$"), null, true); + } + + @TestMetadata("ArrayEqualityCanBeReplacedWithEquals.kt") + public void testArrayEqualityCanBeReplacedWithEquals() throws Exception { + runTest("compiler/fir/analysis-tests/testData/extendedCheckers/ArrayEqualityCanBeReplacedWithEquals.kt"); + } + + @TestMetadata("RedundantExplicitTypeChecker.kt") + public void testRedundantExplicitTypeChecker() throws Exception { + runTest("compiler/fir/analysis-tests/testData/extendedCheckers/RedundantExplicitTypeChecker.kt"); + } + + @TestMetadata("RedundantModalityModifierChecker.kt") + public void testRedundantModalityModifierChecker() throws Exception { + runTest("compiler/fir/analysis-tests/testData/extendedCheckers/RedundantModalityModifierChecker.kt"); + } + + @TestMetadata("RedundantReturnUnitTypeChecker.kt") + public void testRedundantReturnUnitTypeChecker() throws Exception { + runTest("compiler/fir/analysis-tests/testData/extendedCheckers/RedundantReturnUnitTypeChecker.kt"); + } + + @TestMetadata("RedundantSetterParameterTypeChecker.kt") + public void testRedundantSetterParameterTypeChecker() throws Exception { + runTest("compiler/fir/analysis-tests/testData/extendedCheckers/RedundantSetterParameterTypeChecker.kt"); + } + + @TestMetadata("RedundantSingleExpressionStringTemplateChecker.kt") + public void testRedundantSingleExpressionStringTemplateChecker() throws Exception { + runTest("compiler/fir/analysis-tests/testData/extendedCheckers/RedundantSingleExpressionStringTemplateChecker.kt"); + } + + @TestMetadata("RedundantVisibilityModifierChecker.kt") + public void testRedundantVisibilityModifierChecker() throws Exception { + runTest("compiler/fir/analysis-tests/testData/extendedCheckers/RedundantVisibilityModifierChecker.kt"); + } + + @TestMetadata("VariableAssignmentChecker.kt") + public void testVariableAssignmentChecker() throws Exception { + runTest("compiler/fir/analysis-tests/testData/extendedCheckers/VariableAssignmentChecker.kt"); + } + + @TestMetadata("compiler/fir/analysis-tests/testData/extendedCheckers/canBeReplacedWithOperatorAssignment") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class CanBeReplacedWithOperatorAssignment extends AbstractExtendedFirWithLightTreeDiagnosticsTest { + 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("logicOperators.kt") + public void testLogicOperators() throws Exception { + runTest("compiler/fir/analysis-tests/testData/extendedCheckers/canBeReplacedWithOperatorAssignment/logicOperators.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"); + } + } + + @TestMetadata("compiler/fir/analysis-tests/testData/extendedCheckers/emptyRangeChecker") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class EmptyRangeChecker extends AbstractExtendedFirWithLightTreeDiagnosticsTest { + private void runTest(String testDataFilePath) throws Exception { + KotlinTestUtils.runTest(this::doTest, this, testDataFilePath); + } + + public void testAllFilesPresentInEmptyRangeChecker() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/fir/analysis-tests/testData/extendedCheckers/emptyRangeChecker"), Pattern.compile("^([^.]+)\\.kt$"), null, true); + } + + @TestMetadata("NoWarning.kt") + public void testNoWarning() throws Exception { + runTest("compiler/fir/analysis-tests/testData/extendedCheckers/emptyRangeChecker/NoWarning.kt"); + } + + @TestMetadata("Warning.kt") + public void testWarning() throws Exception { + runTest("compiler/fir/analysis-tests/testData/extendedCheckers/emptyRangeChecker/Warning.kt"); + } + } + + @TestMetadata("compiler/fir/analysis-tests/testData/extendedCheckers/RedundantCallOfConversionMethod") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class RedundantCallOfConversionMethod extends AbstractExtendedFirWithLightTreeDiagnosticsTest { + private void runTest(String testDataFilePath) throws Exception { + KotlinTestUtils.runTest(this::doTest, this, testDataFilePath); + } + + public void testAllFilesPresentInRedundantCallOfConversionMethod() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/fir/analysis-tests/testData/extendedCheckers/RedundantCallOfConversionMethod"), Pattern.compile("^([^.]+)\\.kt$"), null, true); + } + + @TestMetadata("booleanToInt.kt") + public void testBooleanToInt() throws Exception { + runTest("compiler/fir/analysis-tests/testData/extendedCheckers/RedundantCallOfConversionMethod/booleanToInt.kt"); + } + + @TestMetadata("byte.kt") + public void testByte() throws Exception { + runTest("compiler/fir/analysis-tests/testData/extendedCheckers/RedundantCallOfConversionMethod/byte.kt"); + } + + @TestMetadata("char.kt") + public void testChar() throws Exception { + runTest("compiler/fir/analysis-tests/testData/extendedCheckers/RedundantCallOfConversionMethod/char.kt"); + } + + @TestMetadata("double.kt") + public void testDouble() throws Exception { + runTest("compiler/fir/analysis-tests/testData/extendedCheckers/RedundantCallOfConversionMethod/double.kt"); + } + + @TestMetadata("float.kt") + public void testFloat() throws Exception { + runTest("compiler/fir/analysis-tests/testData/extendedCheckers/RedundantCallOfConversionMethod/float.kt"); + } + + @TestMetadata("int.kt") + public void testInt() throws Exception { + runTest("compiler/fir/analysis-tests/testData/extendedCheckers/RedundantCallOfConversionMethod/int.kt"); + } + + @TestMetadata("long.kt") + public void testLong() throws Exception { + runTest("compiler/fir/analysis-tests/testData/extendedCheckers/RedundantCallOfConversionMethod/long.kt"); + } + + @TestMetadata("nullable.kt") + public void testNullable() throws Exception { + runTest("compiler/fir/analysis-tests/testData/extendedCheckers/RedundantCallOfConversionMethod/nullable.kt"); + } + + @TestMetadata("nullable2.kt") + public void testNullable2() throws Exception { + runTest("compiler/fir/analysis-tests/testData/extendedCheckers/RedundantCallOfConversionMethod/nullable2.kt"); + } + + @TestMetadata("safeString.kt") + public void testSafeString() throws Exception { + runTest("compiler/fir/analysis-tests/testData/extendedCheckers/RedundantCallOfConversionMethod/safeString.kt"); + } + + @TestMetadata("safeString2.kt") + public void testSafeString2() throws Exception { + runTest("compiler/fir/analysis-tests/testData/extendedCheckers/RedundantCallOfConversionMethod/safeString2.kt"); + } + + @TestMetadata("short.kt") + public void testShort() throws Exception { + runTest("compiler/fir/analysis-tests/testData/extendedCheckers/RedundantCallOfConversionMethod/short.kt"); + } + + @TestMetadata("string.kt") + public void testString() throws Exception { + runTest("compiler/fir/analysis-tests/testData/extendedCheckers/RedundantCallOfConversionMethod/string.kt"); + } + + @TestMetadata("StringTemplate.kt") + public void testStringTemplate() throws Exception { + runTest("compiler/fir/analysis-tests/testData/extendedCheckers/RedundantCallOfConversionMethod/StringTemplate.kt"); + } + + @TestMetadata("toOtherType.kt") + public void testToOtherType() throws Exception { + runTest("compiler/fir/analysis-tests/testData/extendedCheckers/RedundantCallOfConversionMethod/toOtherType.kt"); + } + + @TestMetadata("uByte.kt") + public void testUByte() throws Exception { + runTest("compiler/fir/analysis-tests/testData/extendedCheckers/RedundantCallOfConversionMethod/uByte.kt"); + } + + @TestMetadata("uInt.kt") + public void testUInt() throws Exception { + runTest("compiler/fir/analysis-tests/testData/extendedCheckers/RedundantCallOfConversionMethod/uInt.kt"); + } + + @TestMetadata("uLong.kt") + public void testULong() throws Exception { + runTest("compiler/fir/analysis-tests/testData/extendedCheckers/RedundantCallOfConversionMethod/uLong.kt"); + } + + @TestMetadata("uShort.kt") + public void testUShort() throws Exception { + runTest("compiler/fir/analysis-tests/testData/extendedCheckers/RedundantCallOfConversionMethod/uShort.kt"); + } + + @TestMetadata("variable.kt") + public void testVariable() throws Exception { + runTest("compiler/fir/analysis-tests/testData/extendedCheckers/RedundantCallOfConversionMethod/variable.kt"); + } + } +} diff --git a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/FirHelpers.kt b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/FirHelpers.kt index 867797f504e..2f2d1e83ddb 100644 --- a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/FirHelpers.kt +++ b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/FirHelpers.kt @@ -5,12 +5,11 @@ package org.jetbrains.kotlin.fir.analysis.checkers +import com.intellij.lang.LighterASTNode +import com.intellij.openapi.util.Ref import org.jetbrains.kotlin.descriptors.ClassKind import org.jetbrains.kotlin.descriptors.Modality -import org.jetbrains.kotlin.fir.FirSession -import org.jetbrains.kotlin.fir.FirSymbolOwner -import org.jetbrains.kotlin.fir.Visibilities -import org.jetbrains.kotlin.fir.Visibility +import org.jetbrains.kotlin.fir.* import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext import org.jetbrains.kotlin.fir.declarations.* import org.jetbrains.kotlin.fir.expressions.FirQualifiedAccessExpression @@ -223,15 +222,18 @@ fun FirSimpleFunction.overriddenFunctions( /** * Returns the visibility by given KtModifierList */ -fun KtModifierList?.getVisibility() = this?.visibilityModifierType()?.toFirVisibility() +fun KtModifierList?.getVisibility() = this?.visibilityModifierType()?.toVisibilityOrNull() -fun KtModifierKeywordToken.toFirVisibility(): Visibility { +/** + * Returns Visibility by token or null + */ +fun KtModifierKeywordToken.toVisibilityOrNull(): Visibility? { return when (this) { KtTokens.PUBLIC_KEYWORD -> Visibilities.Public KtTokens.PRIVATE_KEYWORD -> Visibilities.Private KtTokens.PROTECTED_KEYWORD -> Visibilities.Protected KtTokens.INTERNAL_KEYWORD -> Visibilities.Internal - else -> throw IllegalArgumentException("Unknown visibility modifier:$this") + else -> null } } @@ -256,10 +258,10 @@ fun FirMemberDeclaration.implicitModality(context: CheckerContext): Modality { val klass = context.findClosestClassOrObject() ?: return Modality.FINAL val modifiers = this.modifierListOrNull() ?: return Modality.FINAL - if (modifiers.hasModifier(KtTokens.OVERRIDE_KEYWORD)) { + if (modifiers.contains(KtTokens.OVERRIDE_KEYWORD)) { val klassModifiers = klass.modifierListOrNull() if (klassModifiers != null && klassModifiers.run { - hasModifier(KtTokens.ABSTRACT_KEYWORD) || hasModifier(KtTokens.OPEN_KEYWORD) || hasModifier(KtTokens.SEALED_KEYWORD) + contains(KtTokens.ABSTRACT_KEYWORD) || contains(KtTokens.OPEN_KEYWORD) || contains(KtTokens.SEALED_KEYWORD) }) { return Modality.OPEN } @@ -268,7 +270,7 @@ fun FirMemberDeclaration.implicitModality(context: CheckerContext): Modality { if ( klass is FirRegularClass && klass.classKind == ClassKind.INTERFACE - && !modifiers.hasModifier(KtTokens.PRIVATE_KEYWORD) + && !modifiers.contains(KtTokens.PRIVATE_KEYWORD) ) { return if (this.hasBody()) Modality.OPEN else Modality.ABSTRACT } @@ -276,7 +278,7 @@ fun FirMemberDeclaration.implicitModality(context: CheckerContext): Modality { return Modality.FINAL } -private fun FirDeclaration.modifierListOrNull() = (this.source.getModifierList() as? FirPsiModifierList)?.modifierList +private fun FirDeclaration.modifierListOrNull() = this.source.getModifierList()?.modifiers?.map { it.token } private fun FirDeclaration.hasBody(): Boolean = when (this) { is FirSimpleFunction -> this.body != null && this.body !is FirEmptyExpressionBlock @@ -306,3 +308,23 @@ fun FirClass<*>.findNonInterfaceSupertype(context: CheckerContext): FirTypeRef? return null } + +/** + * Returns the source element of the eq operator in assignment statement + */ +fun FirSourceElement.eqOperatorSource(): FirSourceElement? { + return when (this) { + is FirLightSourceElement -> { + val children = Ref>() + val tree = tree + tree.getChildren(element, children) + val element = children.get().getOrNull(2) ?: return null + element.toFirLightSourceElement(element.startOffset, element.endOffset, tree) + } + is FirPsiSourceElement<*> -> { + val operator = psi.children.getOrNull(1) + operator?.toFirPsiSourceElement() + } + else -> null + } +} \ No newline at end of file diff --git a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/extended/ArrayEqualityCanBeReplacedWithEquals.kt b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/extended/ArrayEqualityCanBeReplacedWithEquals.kt index 5e4dea1dd66..dd343a126f0 100644 --- a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/extended/ArrayEqualityCanBeReplacedWithEquals.kt +++ b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/extended/ArrayEqualityCanBeReplacedWithEquals.kt @@ -6,6 +6,7 @@ package org.jetbrains.kotlin.fir.analysis.checkers.extended import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext +import org.jetbrains.kotlin.fir.analysis.checkers.eqOperatorSource import org.jetbrains.kotlin.fir.analysis.checkers.expression.FirBasicExpresionChecker import org.jetbrains.kotlin.fir.analysis.diagnostics.DiagnosticReporter import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.ARRAY_EQUALITY_OPERATOR_CAN_BE_REPLACED_WITH_EQUALS @@ -13,9 +14,7 @@ import org.jetbrains.kotlin.fir.expressions.FirEqualityOperatorCall import org.jetbrains.kotlin.fir.expressions.FirOperation import org.jetbrains.kotlin.fir.expressions.FirStatement import org.jetbrains.kotlin.fir.expressions.arguments -import org.jetbrains.kotlin.fir.psi 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 @@ -29,6 +28,6 @@ object ArrayEqualityCanBeReplacedWithEquals : FirBasicExpresionChecker() { if (left.typeRef.coneType.classId != StandardClassIds.Array) return if (right.typeRef.coneType.classId != StandardClassIds.Array) return - reporter.report(expression.psi?.children?.get(1)?.toFirPsiSourceElement(), ARRAY_EQUALITY_OPERATOR_CAN_BE_REPLACED_WITH_EQUALS) + reporter.report(expression.source?.eqOperatorSource(), ARRAY_EQUALITY_OPERATOR_CAN_BE_REPLACED_WITH_EQUALS) } } \ No newline at end of file diff --git a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/extended/CanBeReplacedWithOperatorAssignmentChecker.kt b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/extended/CanBeReplacedWithOperatorAssignmentChecker.kt index 054643bb57e..6367b7545be 100644 --- a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/extended/CanBeReplacedWithOperatorAssignmentChecker.kt +++ b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/extended/CanBeReplacedWithOperatorAssignmentChecker.kt @@ -5,9 +5,16 @@ package org.jetbrains.kotlin.fir.analysis.checkers.extended +import com.intellij.lang.LighterASTNode +import com.intellij.openapi.util.Ref import com.intellij.psi.tree.IElementType +import org.jetbrains.kotlin.KtNodeTypes +import org.jetbrains.kotlin.fir.FirFakeSourceElementKind +import org.jetbrains.kotlin.fir.FirLightSourceElement +import org.jetbrains.kotlin.fir.FirPsiSourceElement import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext -import org.jetbrains.kotlin.fir.analysis.checkers.expression.FirVariableAssignmentChecker +import org.jetbrains.kotlin.fir.analysis.checkers.eqOperatorSource +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 @@ -16,39 +23,85 @@ 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 -import org.jetbrains.kotlin.psi.KtOperationReferenceExpression -object CanBeReplacedWithOperatorAssignmentChecker : FirVariableAssignmentChecker() { +object CanBeReplacedWithOperatorAssignmentChecker : FirExpressionChecker() { override fun check(expression: FirVariableAssignment, context: CheckerContext, reporter: DiagnosticReporter) { val lValue = expression.lValue if (lValue !is FirResolvedNamedReference) return + if (expression.source?.kind is FirFakeSourceElementKind) return - val operator = expression.psi?.children?.getOrNull(1) ?: return - val operationSign = (operator as? KtOperationReferenceExpression)?.operationSignTokenType - if (operationSign != KtTokens.EQ) return - - val lValuePsi = lValue.psi as? KtNameReferenceExpression ?: return val rValue = expression.rValue as? FirFunctionCall ?: return - val rValuePsi = rValue.psi as? KtBinaryExpression ?: return - val rValueClassId = rValue.explicitReceiver?.typeRef?.coneType?.classId + if (rValue.source?.kind is FirFakeSourceElementKind) 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) + var needToReport = false + val assignmentSource = expression.source + + if (assignmentSource is FirPsiSourceElement<*>) { + val lValuePsi = lValue.psi as? KtNameReferenceExpression ?: return + val rValuePsi = rValue.psi as? KtBinaryExpression ?: return + + if (rValuePsi.matcher(lValuePsi)) { + needToReport = true + } + } else if (assignmentSource is FirLightSourceElement) { + val lValueLightTree = (lValue.source as FirLightSourceElement).element + val rValueLightTree = (rValue.source as FirLightSourceElement).element + if (lightTreeMatcher(lValueLightTree, rValueLightTree, assignmentSource)) { + needToReport = true + } } + + if (needToReport) { + val reportSource = expression.source?.eqOperatorSource() + reporter.report(reportSource, FirErrors.CAN_BE_REPLACED_WITH_OPERATOR_ASSIGNMENT) + } + } - fun KtBinaryExpression.matcher(variable: KtNameReferenceExpression): Boolean { + fun lightTreeMatcher( + variable: LighterASTNode, + expression: LighterASTNode, + source: FirLightSourceElement, + prevOperator: LighterASTNode? = null + ): Boolean { + val tree = source.tree + val childrenNullable = Ref>() + tree.getChildren(expression, childrenNullable) + val children = childrenNullable.get().filterNotNull() + + val operator = children.firstOrNull { it.tokenType == KtNodeTypes.OPERATION_REFERENCE } + if (prevOperator != null && !isLightNodesHierarchicallyTrue(prevOperator, operator)) return false + if (operator?.canBeAugmented() == false) return false + + val commutative = operator != null && isCommutativeOperator(operator) + var afterOperatorNode = false + children.forEach { + when (it.tokenType) { + KtNodeTypes.REFERENCE_EXPRESSION -> { + if ((commutative || !afterOperatorNode) && it.toString() == variable.toString()) return true + } + KtNodeTypes.BINARY_EXPRESSION -> { + return lightTreeMatcher(variable, it, source, operator) + } + KtNodeTypes.OPERATION_REFERENCE -> { + afterOperatorNode = true + } + } + } + return false + } + + private fun KtBinaryExpression.matcher(variable: KtNameReferenceExpression): Boolean { if (!canBeAugmented()) return false if ((left as? KtNameReferenceExpression)?.getReferencedName() == variable.getReferencedName()) return true if ((right as? KtNameReferenceExpression)?.getReferencedName() == variable.getReferencedName() && isCommutative()) return true @@ -60,9 +113,11 @@ object CanBeReplacedWithOperatorAssignmentChecker : FirVariableAssignmentChecker val isLeftMatch = isHierarchicallyTrue(operationToken, leftExpression?.operationToken) && leftExpression?.matcher(variable) == true if (isLeftMatch) return true + val isRightMatch = isHierarchicallyTrue(operationToken, rightExpression?.operationToken) + && rightExpression?.matcher(variable) == true + if (isRightMatch) return true - return (isHierarchicallyTrue(operationToken, rightExpression?.operationToken) - && rightExpression?.matcher(variable) == true) + false } else { val leftExpression = left as? KtBinaryExpression @@ -80,4 +135,13 @@ object CanBeReplacedWithOperatorAssignmentChecker : FirVariableAssignmentChecker || this.operationToken == KtTokens.PERC private fun isHierarchicallyTrue(currentOperation: IElementType, nextOperation: IElementType?) = currentOperation == nextOperation + + private fun isCommutativeOperator(operator: LighterASTNode) = operator.toString().let { it == "+" || it == "*" } + + private fun isLightNodesHierarchicallyTrue(first: LighterASTNode?, second: LighterASTNode?) = + first.toString() == second.toString() + + private fun LighterASTNode.canBeAugmented() = this.toString().let { + it == "+" || it == "*" || it == "-" || it == "/" || it == "%" + } } \ No newline at end of file diff --git a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/extended/RedundantCallOfConversionMethod.kt b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/extended/RedundantCallOfConversionMethod.kt index 9ec47057dc1..e67cc357168 100644 --- a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/extended/RedundantCallOfConversionMethod.kt +++ b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/extended/RedundantCallOfConversionMethod.kt @@ -21,9 +21,7 @@ import org.jetbrains.kotlin.fir.types.classId import org.jetbrains.kotlin.fir.types.coneType import org.jetbrains.kotlin.fir.types.isMarkedNullable import org.jetbrains.kotlin.name.ClassId -import org.jetbrains.kotlin.psi.KtBinaryExpression import org.jetbrains.kotlin.psi.KtSafeQualifiedExpression -import org.jetbrains.kotlin.types.expressions.OperatorConventions object RedundantCallOfConversionMethod : FirQualifiedAccessChecker() { override fun check(expression: FirQualifiedAccessExpression, context: CheckerContext, reporter: DiagnosticReporter) { @@ -37,6 +35,21 @@ object RedundantCallOfConversionMethod : FirQualifiedAccessChecker() { } } + private fun FirExpression.isRedundant(qualifiedClassId: ClassId): Boolean { + val thisType = if (this is FirConstExpression<*>) { + this.typeRef.coneType.classId + } else { + when { + typeRef.coneType is ConeFlexibleType -> null + psi?.parent !is KtSafeQualifiedExpression + && (psi is KtSafeQualifiedExpression || typeRef.coneType.isMarkedNullable) -> null + this.typeRef.coneType.isMarkedNullable -> null + else -> this.typeRef.coneType.classId + } + } + return thisType == qualifiedClassId + } + private val targetClassMap = hashMapOf( "toString" to StandardClassIds.String, "toDouble" to StandardClassIds.Double, @@ -51,26 +64,4 @@ object RedundantCallOfConversionMethod : FirQualifiedAccessChecker() { "toUShort" to StandardClassIds.UShort, "toUByte" to StandardClassIds.UByte ) - - private fun FirExpression.isRedundant(qualifiedClassId: ClassId): Boolean { - val thisType = if (this is FirConstExpression<*>) { - this.typeRef.coneType.classId - } else { - val binaryExpression = psi as? KtBinaryExpression - val operator = binaryExpression?.operationToken - if (operator in OperatorConventions.COMPARISON_OPERATIONS) { - // Special case here because compareTo returns Int - StandardClassIds.Boolean - } else { - when { - typeRef.coneType is ConeFlexibleType -> null - psi?.parent !is KtSafeQualifiedExpression - && (psi is KtSafeQualifiedExpression || typeRef.coneType.isMarkedNullable) -> null - this.typeRef.coneType.isMarkedNullable -> null - else -> this.typeRef.coneType.classId - } - } - } - return thisType == qualifiedClassId - } } \ No newline at end of file diff --git a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/extended/RedundantExplicitTypeChecker.kt b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/extended/RedundantExplicitTypeChecker.kt index 9f4555a6e17..582d9a1cc4c 100644 --- a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/extended/RedundantExplicitTypeChecker.kt +++ b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/extended/RedundantExplicitTypeChecker.kt @@ -6,7 +6,9 @@ package org.jetbrains.kotlin.fir.analysis.checkers.extended import org.jetbrains.kotlin.KtNodeTypes -import org.jetbrains.kotlin.fir.FirFakeSourceElement +import org.jetbrains.kotlin.fir.FirFakeSourceElementKind +import org.jetbrains.kotlin.fir.FirLightSourceElement +import org.jetbrains.kotlin.fir.FirPsiSourceElement import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext import org.jetbrains.kotlin.fir.analysis.checkers.declaration.FirMemberDeclarationChecker import org.jetbrains.kotlin.fir.analysis.diagnostics.DiagnosticReporter @@ -32,7 +34,7 @@ object RedundantExplicitTypeChecker : FirMemberDeclarationChecker() { val initializer = declaration.initializer ?: return val typeReference = declaration.returnTypeRef - if (typeReference.source is FirFakeSourceElement<*>) return + if (typeReference.source?.kind is FirFakeSourceElementKind) return val type = declaration.returnTypeRef.coneType @@ -46,14 +48,14 @@ object RedundantExplicitTypeChecker : FirMemberDeclarationChecker() { if (!type.isSame(StandardClassIds.Boolean)) return } KtNodeTypes.INTEGER_CONSTANT -> { - if (initializer.text?.endsWith("L") == true) { + if (initializer.kind == FirConstKind.Long) { if (!type.isSame(StandardClassIds.Long)) return } else { if (!type.isSame(StandardClassIds.Int)) return } } KtNodeTypes.FLOAT_CONSTANT -> { - if (initializer.text?.endsWith("f", ignoreCase = true) == true) { + if (initializer.kind == FirConstKind.Float) { if (!type.isSame(StandardClassIds.Float)) return } else { if (!type.isSame(StandardClassIds.Double)) return @@ -89,11 +91,18 @@ object RedundantExplicitTypeChecker : FirMemberDeclarationChecker() { reporter.report(declaration.returnTypeRef.source, FirErrors.REDUNDANT_EXPLICIT_TYPE) } - private val FirExpression.text - get() = this.source.psi?.text - - private val FirTypeRef.text - get() = this.psi?.text + private val FirTypeRef.text: String? + get() { + return when (source) { + is FirPsiSourceElement<*> -> { + source.psi?.text + } + is FirLightSourceElement -> { + (source as FirLightSourceElement).element.toString() + } + else -> null + } + } private fun ConeKotlinType.isSame(other: ClassId?): Boolean { if (this.nullability.isNullable) return false diff --git a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/extended/RedundantModalityModifierChecker.kt b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/extended/RedundantModalityModifierChecker.kt index 51ceeb73cb3..bd8f667d951 100644 --- a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/extended/RedundantModalityModifierChecker.kt +++ b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/extended/RedundantModalityModifierChecker.kt @@ -7,23 +7,24 @@ package org.jetbrains.kotlin.fir.analysis.checkers.extended import org.jetbrains.kotlin.descriptors.ClassKind import org.jetbrains.kotlin.descriptors.Modality -import org.jetbrains.kotlin.fir.FirFakeSourceElement +import org.jetbrains.kotlin.fir.FirFakeSourceElementKind +import org.jetbrains.kotlin.fir.FirSourceElement +import org.jetbrains.kotlin.fir.analysis.checkers.FirModifier import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext import org.jetbrains.kotlin.fir.analysis.checkers.declaration.FirMemberDeclarationChecker +import org.jetbrains.kotlin.fir.analysis.checkers.getModifierList import org.jetbrains.kotlin.fir.analysis.checkers.implicitModality +import org.jetbrains.kotlin.fir.analysis.checkers.source import org.jetbrains.kotlin.fir.analysis.diagnostics.DiagnosticReporter import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors.REDUNDANT_MODALITY_MODIFIER import org.jetbrains.kotlin.fir.declarations.FirClass import org.jetbrains.kotlin.fir.declarations.FirMemberDeclaration import org.jetbrains.kotlin.fir.declarations.modality -import org.jetbrains.kotlin.fir.psi -import org.jetbrains.kotlin.fir.toFirPsiSourceElement -import org.jetbrains.kotlin.psi.KtDeclaration -import org.jetbrains.kotlin.psi.psiUtil.modalityModifier +import org.jetbrains.kotlin.lexer.KtTokens object RedundantModalityModifierChecker : FirMemberDeclarationChecker() { override fun check(declaration: FirMemberDeclaration, context: CheckerContext, reporter: DiagnosticReporter) { - if (declaration.source is FirFakeSourceElement<*>) return + if (declaration.source?.kind is FirFakeSourceElementKind) return val modality = declaration.modality ?: return if ( @@ -35,7 +36,16 @@ object RedundantModalityModifierChecker : FirMemberDeclarationChecker() { if (modality != implicitModality) return - val modalityModifier = (declaration.psi as? KtDeclaration)?.modalityModifier() ?: return - reporter.report(modalityModifier.toFirPsiSourceElement(), REDUNDANT_MODALITY_MODIFIER) + val modalityModifierSource = declaration.source.getModifierList()?.modifiers?.modalitySource() + reporter.report(modalityModifierSource, REDUNDANT_MODALITY_MODIFIER) + } + + private fun Collection>.modalitySource(): FirSourceElement? { + return this.firstOrNull { + it.token == KtTokens.FINAL_KEYWORD + || it.token == KtTokens.OPEN_KEYWORD + || it.token == KtTokens.SEALED_KEYWORD + || it.token == KtTokens.ABSTRACT_KEYWORD + }?.source } } diff --git a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/extended/RedundantReturnUnitTypeChecker.kt b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/extended/RedundantReturnUnitTypeChecker.kt index 84f91968d89..c1207f16dd4 100644 --- a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/extended/RedundantReturnUnitTypeChecker.kt +++ b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/extended/RedundantReturnUnitTypeChecker.kt @@ -5,7 +5,7 @@ package org.jetbrains.kotlin.fir.analysis.checkers.extended -import org.jetbrains.kotlin.fir.FirFakeSourceElement +import org.jetbrains.kotlin.fir.FirFakeSourceElementKind import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext import org.jetbrains.kotlin.fir.analysis.checkers.declaration.FirBasicDeclarationChecker import org.jetbrains.kotlin.fir.analysis.diagnostics.DiagnosticReporter @@ -21,7 +21,7 @@ object RedundantReturnUnitType : FirBasicDeclarationChecker() { if (declaration.body is FirSingleExpressionBlock) return val returnType = declaration.returnTypeRef if (returnType.source == null) return - if (declaration.source is FirFakeSourceElement<*>) return + if (declaration.source?.kind is FirFakeSourceElementKind) return if (returnType.annotations.isNotEmpty()) return if (returnType.isUnit) { diff --git a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/extended/RedundantSingleExpressionStringTemplateChecker.kt b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/extended/RedundantSingleExpressionStringTemplateChecker.kt index bffefdf308c..4f94fba1acd 100644 --- a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/extended/RedundantSingleExpressionStringTemplateChecker.kt +++ b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/extended/RedundantSingleExpressionStringTemplateChecker.kt @@ -5,8 +5,14 @@ package org.jetbrains.kotlin.fir.analysis.checkers.extended +import com.intellij.lang.LighterASTNode +import com.intellij.lang.PsiBuilder +import com.intellij.openapi.util.Ref import com.intellij.psi.PsiElement +import org.jetbrains.kotlin.KtNodeTypes import org.jetbrains.kotlin.fir.FirFakeSourceElementKind +import org.jetbrains.kotlin.fir.FirLightSourceElement +import org.jetbrains.kotlin.fir.FirPsiSourceElement import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext import org.jetbrains.kotlin.fir.analysis.checkers.expression.FirBasicExpresionChecker import org.jetbrains.kotlin.fir.analysis.diagnostics.DiagnosticReporter @@ -20,19 +26,42 @@ import org.jetbrains.kotlin.fir.types.coneType import org.jetbrains.kotlin.psi.KtStringTemplateExpression object RedundantSingleExpressionStringTemplateChecker : FirBasicExpresionChecker() { - override fun check(functionCall: FirStatement, context: CheckerContext, reporter: DiagnosticReporter) { - if (functionCall.source?.kind != FirFakeSourceElementKind.GeneratedToStringCallOnTemplateEntry) return - if (functionCall !is FirFunctionCall) return + override fun check(expression: FirStatement, context: CheckerContext, reporter: DiagnosticReporter) { + if (expression.source?.kind != FirFakeSourceElementKind.GeneratedToStringCallOnTemplateEntry) return + if (expression !is FirFunctionCall) return if ( - functionCall.explicitReceiver?.typeRef?.coneType?.classId == StandardClassIds.String - && functionCall.psi?.findStringParent()?.children?.size == 1 // there is no more children in original string template + expression.explicitReceiver?.typeRef?.coneType?.classId == StandardClassIds.String + && expression.stringParentChildrenCount() == 1 // there is no more children in original string template ) { - reporter.report(functionCall.source, REDUNDANT_SINGLE_EXPRESSION_STRING_TEMPLATE) + reporter.report(expression.source, REDUNDANT_SINGLE_EXPRESSION_STRING_TEMPLATE) } } - private fun PsiElement.findStringParent(): KtStringTemplateExpression? { - if (this is KtStringTemplateExpression) return this - return this.parent?.findStringParent() + private fun FirStatement.stringParentChildrenCount(): Int? { + return when (source) { + is FirPsiSourceElement<*> -> { + source.psi?.stringParentChildrenCount() + } + is FirLightSourceElement -> { + (source as FirLightSourceElement).element.stringParentChildrenCount(source as FirLightSourceElement) + } + else -> null + } } -} + + private fun PsiElement.stringParentChildrenCount(): Int? { + if (parent is KtStringTemplateExpression) return parent?.children?.size + return parent.stringParentChildrenCount() + } + + private fun LighterASTNode.stringParentChildrenCount(source: FirLightSourceElement): Int? { + val parent = source.tree.getParent(this) + return if (parent?.tokenType == KtNodeTypes.STRING_TEMPLATE) { + val childrenOfParent = Ref>() + source.tree.getChildren(parent!!, childrenOfParent) + childrenOfParent.get().filter { it is PsiBuilder.Marker }.size + } else { + parent?.stringParentChildrenCount(source) + } + } +} \ No newline at end of file diff --git a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/extended/RedundantVisibilityModifierChecker.kt b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/extended/RedundantVisibilityModifierChecker.kt index bbfe156a798..d9dd16e5c7c 100644 --- a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/extended/RedundantVisibilityModifierChecker.kt +++ b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/extended/RedundantVisibilityModifierChecker.kt @@ -7,54 +7,54 @@ package org.jetbrains.kotlin.fir.analysis.checkers.extended import org.jetbrains.kotlin.descriptors.ClassKind import org.jetbrains.kotlin.descriptors.Modality -import org.jetbrains.kotlin.fir.FirFakeSourceElement -import org.jetbrains.kotlin.fir.Visibilities -import org.jetbrains.kotlin.fir.Visibility +import org.jetbrains.kotlin.fir.* import org.jetbrains.kotlin.fir.analysis.checkers.* import org.jetbrains.kotlin.fir.analysis.checkers.context.CheckerContext import org.jetbrains.kotlin.fir.analysis.checkers.declaration.FirBasicDeclarationChecker import org.jetbrains.kotlin.fir.analysis.diagnostics.DiagnosticReporter import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors import org.jetbrains.kotlin.fir.declarations.* -import org.jetbrains.kotlin.fir.toFirPsiSourceElement +import org.jetbrains.kotlin.lexer.KtModifierKeywordToken import org.jetbrains.kotlin.lexer.KtTokens +import org.jetbrains.kotlin.psi.KtModifierListOwner import org.jetbrains.kotlin.psi.psiUtil.visibilityModifier object RedundantVisibilityModifierChecker : FirBasicDeclarationChecker() { override fun check(declaration: FirDeclaration, context: CheckerContext, reporter: DiagnosticReporter) { - if (declaration is FirConstructor && declaration.source is FirFakeSourceElement<*>) return + if (declaration is FirConstructor && declaration.source?.kind is FirFakeSourceElementKind) return if (declaration.source is FirFakeSourceElement<*>) return if ( declaration !is FirMemberDeclaration && !(declaration is FirPropertyAccessor && declaration.visibility == context.containingPropertyVisibility) ) return - val modifierList = (declaration.source.getModifierList() as? FirPsiModifierList)?.modifierList ?: return - val visibilityModifier = modifierList.getVisibility() ?: return + val modifiers = declaration.source.getModifierList() + val visibilityModifier = when (modifiers) { + is FirPsiModifierList -> modifiers.modifierList.getVisibility() + is FirLightModifierList -> modifiers.modifiers.visibilityOrNull() + else -> null + } ?: return val implicitVisibility = declaration.implicitVisibility(context) val containingMemberDeclaration = context.findClosest() val redundantVisibility = when { visibilityModifier == implicitVisibility -> implicitVisibility - - modifierList.hasModifier(KtTokens.INTERNAL_KEYWORD) && - containingMemberDeclaration.let { - it != null && (it.isLocalMember || modifierList.hasModifier(KtTokens.PRIVATE_KEYWORD)) + modifiers?.modifiers.hasModifier(KtTokens.INTERNAL_KEYWORD) && + containingMemberDeclaration.let { decl -> + decl != null && (decl.isLocalMember || modifiers?.modifiers.hasModifier(KtTokens.PRIVATE_KEYWORD)) } -> Visibilities.Internal - - else -> null - } ?: return + else -> return + } if ( redundantVisibility == Visibilities.Public && declaration is FirProperty - && modifierList.hasModifier(KtTokens.OVERRIDE_KEYWORD) + && modifiers?.modifiers.hasModifier(KtTokens.OVERRIDE_KEYWORD) && declaration.isVar && declaration.setter?.visibility == Visibilities.Public ) return - val source = modifierList.visibilityModifier()?.toFirPsiSourceElement() - reporter.report(source, FirErrors.REDUNDANT_VISIBILITY_MODIFIER) + reporter.report(declaration.source.modifierSource, FirErrors.REDUNDANT_VISIBILITY_MODIFIER) } private fun FirDeclaration.implicitVisibility(context: CheckerContext): Visibility { @@ -124,4 +124,23 @@ object RedundantVisibilityModifierChecker : FirBasicDeclarationChecker() { private val CheckerContext.containingPropertyVisibility get() = (this.containingDeclarations.last() as? FirProperty)?.visibility + + private val FirSourceElement?.modifierSource: FirSourceElement? + get() = when (this) { + null -> null + is FirPsiSourceElement<*> -> (psi as? KtModifierListOwner)?.modifierList?.visibilityModifier()?.toFirPsiSourceElement() + is FirLightSourceElement -> { + val modifier = this.getModifierList()?.modifiers?.firstOrNull { it.isVisibilityModifier } + modifier?.source + } + } + + private val FirModifier<*>.isVisibilityModifier + get() = this.token.toVisibilityOrNull() != null + + private fun List.visibilityOrNull() = + firstOrNull { it.token.toVisibilityOrNull() != null }?.token?.toVisibilityOrNull() + + private fun List>?.hasModifier(token: KtModifierKeywordToken) = this != null && any { it.token == token } + } diff --git a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/extended/VariableAssignmentChecker.kt b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/extended/VariableAssignmentChecker.kt index 61d86631ef1..743a6b3d249 100644 --- a/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/extended/VariableAssignmentChecker.kt +++ b/compiler/fir/checkers/src/org/jetbrains/kotlin/fir/analysis/checkers/extended/VariableAssignmentChecker.kt @@ -6,20 +6,20 @@ package org.jetbrains.kotlin.fir.analysis.checkers.extended +import com.intellij.lang.LighterASTNode +import com.intellij.openapi.util.Ref import org.jetbrains.kotlin.contracts.description.EventOccurrencesRange -import org.jetbrains.kotlin.fir.FirFakeSourceElement -import org.jetbrains.kotlin.fir.FirSourceElement +import org.jetbrains.kotlin.fir.* import org.jetbrains.kotlin.fir.analysis.cfa.AbstractFirPropertyInitializationChecker import org.jetbrains.kotlin.fir.analysis.cfa.PropertyInitializationInfo import org.jetbrains.kotlin.fir.analysis.cfa.TraverseDirection import org.jetbrains.kotlin.fir.analysis.cfa.traverse import org.jetbrains.kotlin.fir.analysis.diagnostics.DiagnosticReporter import org.jetbrains.kotlin.fir.analysis.diagnostics.FirErrors -import org.jetbrains.kotlin.fir.psi import org.jetbrains.kotlin.fir.references.FirResolvedNamedReference import org.jetbrains.kotlin.fir.resolve.dfa.cfg.* import org.jetbrains.kotlin.fir.symbols.impl.FirPropertySymbol -import org.jetbrains.kotlin.fir.toFirPsiSourceElement +import org.jetbrains.kotlin.lexer.KtTokens import org.jetbrains.kotlin.psi.KtProperty @@ -50,8 +50,7 @@ object VariableAssignmentChecker : AbstractFirPropertyInitializationChecker() { val source = symbol.getValOrVarSource if (symbol.callableId.callableName.asString() == "") { lastDestructuringSource = symbol.getValOrVarSource - val childrenCount = symbol.fir.psi?.children?.size ?: continue - lastDestructuredVariables = childrenCount - 1 // -1 cuz we don't need expression node after equals operator + lastDestructuredVariables = symbol.getDestructuringChildrenCount() ?: continue destructuringCanBeVal = true continue } @@ -65,7 +64,7 @@ object VariableAssignmentChecker : AbstractFirPropertyInitializationChecker() { destructuringCanBeVal = false } lastDestructuredVariables-- - } else if (canBeVal(symbol, value) && symbol.fir.delegate == null ) { + } else if (canBeVal(symbol, value) && symbol.fir.delegate == null) { reporter.report(source, FirErrors.CAN_BE_VAL) } } @@ -106,7 +105,32 @@ object VariableAssignmentChecker : AbstractFirPropertyInitializationChecker() { } private val FirPropertySymbol.getValOrVarSource - get() = (fir.psi as? KtProperty)?.valOrVarKeyword?.toFirPsiSourceElement() - ?: fir.psi?.firstChild?.toFirPsiSourceElement() - ?: fir.source + get() = when (fir.source) { + is FirSourceElement -> { + (fir.psi as? KtProperty)?.valOrVarKeyword?.toFirPsiSourceElement() + ?: fir.psi?.firstChild?.toFirPsiSourceElement() + ?: fir.source + } + is FirLightSourceElement -> { + val children = Ref>() + val tree = (fir.source as FirLightSourceElement).tree + tree.getChildren(tree.root, children) + children.get().first { it.tokenType == KtTokens.VAL_KEYWORD || it.tokenType == KtTokens.VAR_KEYWORD }.let { + it.toFirLightSourceElement(it.startOffset, it.endOffset, tree) + } + } + else -> null + } + + private fun FirPropertySymbol.getDestructuringChildrenCount(): Int? = when (fir.source) { + is FirPsiSourceElement<*> -> fir.psi?.children?.size?.minus(1) // -1 cuz we don't need expression node after equals operator + is FirLightSourceElement -> { + val source = fir.source as FirLightSourceElement + val tree = (fir.source as FirLightSourceElement).tree + val children = Ref>() + tree.getChildren(source.element, children) + children.get().count { it != null } + } + else -> null + } } \ No newline at end of file diff --git a/compiler/tests-common/tests/org/jetbrains/kotlin/fir/AbstractExtendedFirWithLightTreeDiagnosticsTest.kt b/compiler/tests-common/tests/org/jetbrains/kotlin/fir/AbstractExtendedFirWithLightTreeDiagnosticsTest.kt new file mode 100644 index 00000000000..d82361018b0 --- /dev/null +++ b/compiler/tests-common/tests/org/jetbrains/kotlin/fir/AbstractExtendedFirWithLightTreeDiagnosticsTest.kt @@ -0,0 +1,17 @@ +/* + * 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 + +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 AbstractExtendedFirWithLightTreeDiagnosticsTest : AbstractFirDiagnosticsWithLightTreeTest() { + override fun configureSession(session: FirSession) { + session.checkersComponent.register(ExtendedDeclarationCheckers) + session.checkersComponent.register(ExtendedExpressionCheckers) + } +} \ No newline at end of file diff --git a/compiler/tests/org/jetbrains/kotlin/generators/tests/GenerateCompilerTests.kt b/compiler/tests/org/jetbrains/kotlin/generators/tests/GenerateCompilerTests.kt index a04cfb12418..0d067a2f09a 100644 --- a/compiler/tests/org/jetbrains/kotlin/generators/tests/GenerateCompilerTests.kt +++ b/compiler/tests/org/jetbrains/kotlin/generators/tests/GenerateCompilerTests.kt @@ -646,5 +646,11 @@ fun main(args: Array) { model("extendedCheckers", pattern = KT_WITHOUT_DOTS_IN_NAME) } } + + testGroup("compiler/fir/analysis-tests/tests", "compiler/fir/analysis-tests/testData") { + testClass { + model("extendedCheckers", pattern = KT_WITHOUT_DOTS_IN_NAME) + } + } } }