From f37e3137059f8c0ab1fef50343395244b86a6c63 Mon Sep 17 00:00:00 2001 From: Ilya Kirillov Date: Mon, 18 May 2020 11:37:09 +0300 Subject: [PATCH] FIR IDE: Begin implementing semantic highlighting via FIR * Introduce frontend api module & implement api for FIR * Implement some basic declaration highlighting for FIR --- .../kotlin/generators/tests/GenerateTests.kt | 12 +- idea/idea-fir/build.gradle.kts | 2 +- .../AbstractFirHighlightingTest.kt | 20 ++ .../FirHighlightingTestGenerated.java | 253 ++++++++++++++++++ idea/idea-frontend-api/build.gradle.kts | 34 +++ .../kotlin/idea/frontend/api/CallInfo.kt | 54 ++++ .../frontend/api/FrontendAnalysisSession.kt | 31 +++ .../frontend/api/ImplicitReceiverSmartCast.kt | 14 + idea/idea-frontend-fir/build.gradle.kts | 1 + .../org/jetbrains/kotlin/idea/fir/FirUtils.kt | 17 ++ .../api/fir/AnalysisSessionFirImpl.kt | 154 +++++++++++ .../idea/highlighter/HighlightingUtils.kt | 59 ++++ .../idea/highlighter/KotlinFirPsiChecker.kt | 20 +- .../DeclarationHighlightingVisitor.kt | 62 +++++ ...ExpressionsSmartcastHighlightingVisitor.kt | 65 +++++ .../FirAfterResolveHighlightingVisitor.kt | 33 +++ .../FunctionCallHighlightingVisitor.kt | 72 +++++ .../visitors/TypeHighlightingVisitor.kt | 67 +++++ .../VariableReferenceHighlightingVisitor.kt | 79 ++++++ .../KotlinLightCodeInsightFixtureTestCase.kt | 6 +- .../highlighter/AbstractHighlightingTest.java | 14 +- prepare/idea-plugin/build.gradle.kts | 1 + settings.gradle | 1 + 23 files changed, 1055 insertions(+), 16 deletions(-) create mode 100644 idea/idea-fir/tests/org/jetbrains/kotlin/idea/highlighter/AbstractFirHighlightingTest.kt create mode 100644 idea/idea-fir/tests/org/jetbrains/kotlin/idea/highlighter/FirHighlightingTestGenerated.java create mode 100644 idea/idea-frontend-api/build.gradle.kts create mode 100644 idea/idea-frontend-api/src/org/jetbrains/kotlin/idea/frontend/api/CallInfo.kt create mode 100644 idea/idea-frontend-api/src/org/jetbrains/kotlin/idea/frontend/api/FrontendAnalysisSession.kt create mode 100644 idea/idea-frontend-api/src/org/jetbrains/kotlin/idea/frontend/api/ImplicitReceiverSmartCast.kt create mode 100644 idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/fir/FirUtils.kt create mode 100644 idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/AnalysisSessionFirImpl.kt create mode 100644 idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/highlighter/HighlightingUtils.kt create mode 100644 idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/highlighter/visitors/DeclarationHighlightingVisitor.kt create mode 100644 idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/highlighter/visitors/ExpressionsSmartcastHighlightingVisitor.kt create mode 100644 idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/highlighter/visitors/FirAfterResolveHighlightingVisitor.kt create mode 100644 idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/highlighter/visitors/FunctionCallHighlightingVisitor.kt create mode 100644 idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/highlighter/visitors/TypeHighlightingVisitor.kt create mode 100644 idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/highlighter/visitors/VariableReferenceHighlightingVisitor.kt diff --git a/generators/tests/org/jetbrains/kotlin/generators/tests/GenerateTests.kt b/generators/tests/org/jetbrains/kotlin/generators/tests/GenerateTests.kt index 0ba6a4539fd..4bb475bc58c 100644 --- a/generators/tests/org/jetbrains/kotlin/generators/tests/GenerateTests.kt +++ b/generators/tests/org/jetbrains/kotlin/generators/tests/GenerateTests.kt @@ -1,5 +1,5 @@ /* - * Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors. + * 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. */ @@ -923,9 +923,13 @@ fun main(args: Array) { model("fir/multiModule", recursive = false, extension = null) } - testClass { - model("fir/lazyResolve", extension = "test", singleClass = true, filenameStartsLowerCase = true) - } + testClass { + model("highlighter") + } + + testClass { + model("fir/lazyResolve", extension = "test", singleClass = true, filenameStartsLowerCase = true) + } testClass { model("resolve/references", pattern = KT_WITHOUT_DOTS_IN_NAME) diff --git a/idea/idea-fir/build.gradle.kts b/idea/idea-fir/build.gradle.kts index 5e3d8f5155a..55635ef8f9f 100644 --- a/idea/idea-fir/build.gradle.kts +++ b/idea/idea-fir/build.gradle.kts @@ -25,6 +25,7 @@ dependencies { testRuntime(intellijPluginDep("java")) } + testCompile(projectTests(":idea:idea-test-framework")) testRuntime(intellijRuntimeAnnotations()) testRuntime(project(":plugins:kapt3-idea")) { isTransitive = false } testRuntime(project(":kotlin-reflect")) @@ -48,7 +49,6 @@ dependencies { testRuntime(project(":idea:idea-android")) { isTransitive = false } testRuntime(project(":plugins:lint")) { isTransitive = false } testRuntime(project(":plugins:uast-kotlin")) - testRuntime(project(":nj2k:nj2k-services")) { isTransitive = false } } sourceSets { diff --git a/idea/idea-fir/tests/org/jetbrains/kotlin/idea/highlighter/AbstractFirHighlightingTest.kt b/idea/idea-fir/tests/org/jetbrains/kotlin/idea/highlighter/AbstractFirHighlightingTest.kt new file mode 100644 index 00000000000..2f380e0018c --- /dev/null +++ b/idea/idea-fir/tests/org/jetbrains/kotlin/idea/highlighter/AbstractFirHighlightingTest.kt @@ -0,0 +1,20 @@ +/* + * 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.idea.highlighter + +import org.jetbrains.kotlin.idea.test.ProjectDescriptorWithStdlibSources +import org.jetbrains.kotlin.test.InTextDirectivesUtils + +abstract class AbstractFirHighlightingTest : AbstractHighlightingTest() { + override fun getDefaultProjectDescriptor() = ProjectDescriptorWithStdlibSources.INSTANCE + + override fun checkHighlighting(fileText: String) { + val checkInfos = !InTextDirectivesUtils.isDirectiveDefined(fileText, NO_CHECK_INFOS_PREFIX); + + // warnings are not supported yet + myFixture.checkHighlighting(/* checkWarnings= */ false, checkInfos, /* checkWeakWarnings= */ false) + } +} \ No newline at end of file diff --git a/idea/idea-fir/tests/org/jetbrains/kotlin/idea/highlighter/FirHighlightingTestGenerated.java b/idea/idea-fir/tests/org/jetbrains/kotlin/idea/highlighter/FirHighlightingTestGenerated.java new file mode 100644 index 00000000000..c85b2d57258 --- /dev/null +++ b/idea/idea-fir/tests/org/jetbrains/kotlin/idea/highlighter/FirHighlightingTestGenerated.java @@ -0,0 +1,253 @@ +/* + * 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.idea.highlighter; + +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("idea/testData/highlighter") +@TestDataPath("$PROJECT_ROOT") +@RunWith(JUnit3RunnerWithInners.class) +public class FirHighlightingTestGenerated extends AbstractFirHighlightingTest { + private void runTest(String testDataFilePath) throws Exception { + KotlinTestUtils.runTest(this::doTest, this, testDataFilePath); + } + + public void testAllFilesPresentInHighlighter() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("idea/testData/highlighter"), Pattern.compile("^(.+)\\.kt$"), null, true); + } + + @TestMetadata("Annotations.kt") + public void testAnnotations() throws Exception { + runTest("idea/testData/highlighter/Annotations.kt"); + } + + @TestMetadata("AutoCreatedItParameter.kt") + public void testAutoCreatedItParameter() throws Exception { + runTest("idea/testData/highlighter/AutoCreatedItParameter.kt"); + } + + @TestMetadata("Destructuring.kt") + public void testDestructuring() throws Exception { + runTest("idea/testData/highlighter/Destructuring.kt"); + } + + @TestMetadata("Dynamic.kt") + public void testDynamic() throws Exception { + runTest("idea/testData/highlighter/Dynamic.kt"); + } + + @TestMetadata("Enums.kt") + public void testEnums() throws Exception { + runTest("idea/testData/highlighter/Enums.kt"); + } + + @TestMetadata("Field.kt") + public void testField() throws Exception { + runTest("idea/testData/highlighter/Field.kt"); + } + + @TestMetadata("Functions.kt") + public void testFunctions() throws Exception { + runTest("idea/testData/highlighter/Functions.kt"); + } + + @TestMetadata("InvokeCall.kt") + public void testInvokeCall() throws Exception { + runTest("idea/testData/highlighter/InvokeCall.kt"); + } + + @TestMetadata("JavaTypes.kt") + public void testJavaTypes() throws Exception { + runTest("idea/testData/highlighter/JavaTypes.kt"); + } + + @TestMetadata("KDoc.kt") + public void testKDoc() throws Exception { + runTest("idea/testData/highlighter/KDoc.kt"); + } + + @TestMetadata("KotlinInjection.kt") + public void testKotlinInjection() throws Exception { + runTest("idea/testData/highlighter/KotlinInjection.kt"); + } + + @TestMetadata("Labels.kt") + public void testLabels() throws Exception { + runTest("idea/testData/highlighter/Labels.kt"); + } + + @TestMetadata("NamedArguments.kt") + public void testNamedArguments() throws Exception { + runTest("idea/testData/highlighter/NamedArguments.kt"); + } + + @TestMetadata("NonNullAssertion.kt") + public void testNonNullAssertion() throws Exception { + runTest("idea/testData/highlighter/NonNullAssertion.kt"); + } + + @TestMetadata("Object.kt") + public void testObject() throws Exception { + runTest("idea/testData/highlighter/Object.kt"); + } + + @TestMetadata("PropertiesWithPropertyDeclarations.kt") + public void testPropertiesWithPropertyDeclarations() throws Exception { + runTest("idea/testData/highlighter/PropertiesWithPropertyDeclarations.kt"); + } + + @TestMetadata("SmartCast.kt") + public void testSmartCast() throws Exception { + runTest("idea/testData/highlighter/SmartCast.kt"); + } + + @TestMetadata("Suspend.kt") + public void testSuspend() throws Exception { + runTest("idea/testData/highlighter/Suspend.kt"); + } + + @TestMetadata("SyntheticExtensionProperty.kt") + public void testSyntheticExtensionProperty() throws Exception { + runTest("idea/testData/highlighter/SyntheticExtensionProperty.kt"); + } + + @TestMetadata("Todo.kt") + public void testTodo() throws Exception { + runTest("idea/testData/highlighter/Todo.kt"); + } + + @TestMetadata("TopLevelDestructuring.kt") + public void testTopLevelDestructuring() throws Exception { + runTest("idea/testData/highlighter/TopLevelDestructuring.kt"); + } + + @TestMetadata("TopLevelOpenSuspendFun.kt") + public void testTopLevelOpenSuspendFun() throws Exception { + runTest("idea/testData/highlighter/TopLevelOpenSuspendFun.kt"); + } + + @TestMetadata("TypeAlias.kt") + public void testTypeAlias() throws Exception { + runTest("idea/testData/highlighter/TypeAlias.kt"); + } + + @TestMetadata("TypesAndAnnotations.kt") + public void testTypesAndAnnotations() throws Exception { + runTest("idea/testData/highlighter/TypesAndAnnotations.kt"); + } + + @TestMetadata("Variables.kt") + public void testVariables() throws Exception { + runTest("idea/testData/highlighter/Variables.kt"); + } + + @TestMetadata("VariablesAsFunctions.kt") + public void testVariablesAsFunctions() throws Exception { + runTest("idea/testData/highlighter/VariablesAsFunctions.kt"); + } + + @TestMetadata("idea/testData/highlighter/deprecated") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class Deprecated extends AbstractFirHighlightingTest { + private void runTest(String testDataFilePath) throws Exception { + KotlinTestUtils.runTest(this::doTest, this, testDataFilePath); + } + + public void testAllFilesPresentInDeprecated() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("idea/testData/highlighter/deprecated"), Pattern.compile("^(.+)\\.kt$"), null, true); + } + + @TestMetadata("Class.kt") + public void testClass() throws Exception { + runTest("idea/testData/highlighter/deprecated/Class.kt"); + } + + @TestMetadata("ClassObject.kt") + public void testClassObject() throws Exception { + runTest("idea/testData/highlighter/deprecated/ClassObject.kt"); + } + + @TestMetadata("Constructor.kt") + public void testConstructor() throws Exception { + runTest("idea/testData/highlighter/deprecated/Constructor.kt"); + } + + @TestMetadata("ExtensionFunction.kt") + public void testExtensionFunction() throws Exception { + runTest("idea/testData/highlighter/deprecated/ExtensionFunction.kt"); + } + + @TestMetadata("Function.kt") + public void testFunction() throws Exception { + runTest("idea/testData/highlighter/deprecated/Function.kt"); + } + + @TestMetadata("Get.kt") + public void testGet() throws Exception { + runTest("idea/testData/highlighter/deprecated/Get.kt"); + } + + @TestMetadata("Getter.kt") + public void testGetter() throws Exception { + runTest("idea/testData/highlighter/deprecated/Getter.kt"); + } + + @TestMetadata("Inc.kt") + public void testInc() throws Exception { + runTest("idea/testData/highlighter/deprecated/Inc.kt"); + } + + @TestMetadata("Invalid.kt") + public void testInvalid() throws Exception { + runTest("idea/testData/highlighter/deprecated/Invalid.kt"); + } + + @TestMetadata("Invoke.kt") + public void testInvoke() throws Exception { + runTest("idea/testData/highlighter/deprecated/Invoke.kt"); + } + + @TestMetadata("Operation.kt") + public void testOperation() throws Exception { + runTest("idea/testData/highlighter/deprecated/Operation.kt"); + } + + @TestMetadata("Property.kt") + public void testProperty() throws Exception { + runTest("idea/testData/highlighter/deprecated/Property.kt"); + } + + @TestMetadata("RangeTo.kt") + public void testRangeTo() throws Exception { + runTest("idea/testData/highlighter/deprecated/RangeTo.kt"); + } + + @TestMetadata("Setter.kt") + public void testSetter() throws Exception { + runTest("idea/testData/highlighter/deprecated/Setter.kt"); + } + + @TestMetadata("SuperCall.kt") + public void testSuperCall() throws Exception { + runTest("idea/testData/highlighter/deprecated/SuperCall.kt"); + } + + @TestMetadata("Trait.kt") + public void testTrait() throws Exception { + runTest("idea/testData/highlighter/deprecated/Trait.kt"); + } + } +} diff --git a/idea/idea-frontend-api/build.gradle.kts b/idea/idea-frontend-api/build.gradle.kts new file mode 100644 index 00000000000..0c60843e91a --- /dev/null +++ b/idea/idea-frontend-api/build.gradle.kts @@ -0,0 +1,34 @@ +plugins { + kotlin("jvm") + id("jps-compatible") +} + +dependencies { + compileOnly(project(":compiler:psi")) + compileOnly(project(":compiler:frontend")) + compileOnly(project(":core:type-system")) + compileOnly(project(":idea:idea-frontend-independent")) + compileOnly(project(":compiler:psi")) + compileOnly(intellijCoreDep()) + compileOnly(intellijDep()) + + Platform[191].orLower { + compileOnly(intellijDep()) { includeJars("java-api", "java-impl") } + } + + Platform[192].orHigher { + compileOnly(intellijPluginDep("java")) { includeJars("java-api", "java-impl") } + } +} + +sourceSets { + "main" { projectDefault() } + "test" { projectDefault() } +} + +testsJar() + +projectTest { + dependsOn(":dist") + workingDir = rootDir +} diff --git a/idea/idea-frontend-api/src/org/jetbrains/kotlin/idea/frontend/api/CallInfo.kt b/idea/idea-frontend-api/src/org/jetbrains/kotlin/idea/frontend/api/CallInfo.kt new file mode 100644 index 00000000000..3408faa11fb --- /dev/null +++ b/idea/idea-frontend-api/src/org/jetbrains/kotlin/idea/frontend/api/CallInfo.kt @@ -0,0 +1,54 @@ +/* + * 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.idea.frontend.api + +import com.intellij.psi.PsiElement +import com.intellij.psi.PsiMethod +import org.jetbrains.kotlin.lexer.KtTokens +import org.jetbrains.kotlin.psi.KtNamedFunction + +sealed class CallInfo { + abstract val isSuspendCall: Boolean + abstract val targetFunction: PsiElement? +} + +data class VariableAsFunctionCallInfo(val target: PsiElement, override val isSuspendCall: Boolean) : CallInfo() { + override val targetFunction: PsiElement? = null +} + +data class VariableAsFunctionLikeCallInfo(val target: PsiElement, val invokeFunction: KtNamedFunction) : CallInfo() { + override val isSuspendCall: Boolean get() = invokeFunction.hasModifier(KtTokens.SUSPEND_KEYWORD) + override val targetFunction: PsiElement? get() = invokeFunction +} + +// SimpleFunctionCallInfo + +sealed class SimpleFunctionCallInfo : CallInfo() { + abstract override val targetFunction: PsiElement +} + +data class SimpleKtFunctionCallInfo(override val targetFunction: KtNamedFunction) : SimpleFunctionCallInfo() { + override val isSuspendCall: Boolean get() = targetFunction.hasModifier(KtTokens.SUSPEND_KEYWORD) +} + +data class SimpleJavaFunctionCallInfo(override val targetFunction: PsiMethod) : SimpleFunctionCallInfo() { + override val isSuspendCall: Boolean = false +} + + +// ConstructorCallInfo + +//TODO +object ConstructorCallInfo : CallInfo() { + // abstract val targetConstructor: PsiElement? + final override val isSuspendCall: Boolean = false + override val targetFunction: PsiElement? = null +} + +//data class SimpleKtConstructorCallInfo(override val targetConstructor: KtConstructor<*>) : ConstructorCallInfo() + +//data class SimpleJavaConstructorCallInfo(override val targetConstructor: PsiMethod) : ConstructorCallInfo() + diff --git a/idea/idea-frontend-api/src/org/jetbrains/kotlin/idea/frontend/api/FrontendAnalysisSession.kt b/idea/idea-frontend-api/src/org/jetbrains/kotlin/idea/frontend/api/FrontendAnalysisSession.kt new file mode 100644 index 00000000000..3a1dd3f36a2 --- /dev/null +++ b/idea/idea-frontend-api/src/org/jetbrains/kotlin/idea/frontend/api/FrontendAnalysisSession.kt @@ -0,0 +1,31 @@ +/* + * 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.idea.frontend.api + +import org.jetbrains.kotlin.diagnostics.Diagnostic +import org.jetbrains.kotlin.name.ClassId +import org.jetbrains.kotlin.psi.* +import org.jetbrains.kotlin.types.model.KotlinTypeMarker + +abstract class FrontendAnalysisSession { + abstract fun getSmartCastedToTypes(expression: KtExpression): Collection? + + abstract fun getImplicitReceiverSmartCasts(expression: KtExpression): Collection + + abstract fun getReturnTypeForKtDeclaration(declaration: KtDeclaration): KotlinTypeMarker? + + abstract fun renderType(type: KotlinTypeMarker): String + + abstract fun getKtExpressionType(expression: KtExpression): KotlinTypeMarker? + + abstract fun isSubclassOf(klass: KtClassOrObject, superClassId: ClassId): Boolean + + abstract fun getDiagnosticsForElement(element: KtElement): Collection + + abstract fun resolveCall(call: KtCallExpression): CallInfo? + + abstract fun resolveCall(call: KtBinaryExpression): CallInfo? +} diff --git a/idea/idea-frontend-api/src/org/jetbrains/kotlin/idea/frontend/api/ImplicitReceiverSmartCast.kt b/idea/idea-frontend-api/src/org/jetbrains/kotlin/idea/frontend/api/ImplicitReceiverSmartCast.kt new file mode 100644 index 00000000000..8b31bffd649 --- /dev/null +++ b/idea/idea-frontend-api/src/org/jetbrains/kotlin/idea/frontend/api/ImplicitReceiverSmartCast.kt @@ -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.idea.frontend.api + +import org.jetbrains.kotlin.types.model.KotlinTypeMarker + +data class ImplicitReceiverSmartCast(val types: Collection, val kind: ImplicitReceiverSmartcastKind) + +enum class ImplicitReceiverSmartcastKind { + DISPATCH, EXTENSION +} diff --git a/idea/idea-frontend-fir/build.gradle.kts b/idea/idea-frontend-fir/build.gradle.kts index c6db4798d36..b6a254b0058 100644 --- a/idea/idea-frontend-fir/build.gradle.kts +++ b/idea/idea-frontend-fir/build.gradle.kts @@ -6,6 +6,7 @@ plugins { dependencies { compileOnly(project(":compiler:psi")) compileOnly(project(":idea:idea-frontend-independent")) + implementation(project(":idea:idea-frontend-api")) compileOnly(project(":idea:idea-core")) compileOnly(project(":compiler:fir:fir2ir")) compileOnly(project(":compiler:fir:resolve")) diff --git a/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/fir/FirUtils.kt b/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/fir/FirUtils.kt new file mode 100644 index 00000000000..38b7bb8edf3 --- /dev/null +++ b/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/fir/FirUtils.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.idea.fir + +import org.jetbrains.kotlin.fir.expressions.FirFunctionCall +import org.jetbrains.kotlin.fir.expressions.FirQualifiedAccessExpression +import org.jetbrains.kotlin.fir.references.FirResolvedNamedReference +import org.jetbrains.kotlin.fir.symbols.impl.FirNamedFunctionSymbol + +fun FirFunctionCall.isImplicitFunctionCall(): Boolean { + if (dispatchReceiver !is FirQualifiedAccessExpression) return false + val resolvedCalleeSymbol = (calleeReference as? FirResolvedNamedReference)?.resolvedSymbol + return (resolvedCalleeSymbol as? FirNamedFunctionSymbol)?.fir?.name?.asString() == "invoke" +} \ No newline at end of file diff --git a/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/AnalysisSessionFirImpl.kt b/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/AnalysisSessionFirImpl.kt new file mode 100644 index 00000000000..c9fdab87d21 --- /dev/null +++ b/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/frontend/api/fir/AnalysisSessionFirImpl.kt @@ -0,0 +1,154 @@ +/* + * 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.idea.frontend.api.fir + +import com.intellij.psi.PsiElement +import com.intellij.psi.PsiField +import com.intellij.psi.PsiMethod +import org.jetbrains.kotlin.builtins.KotlinBuiltIns +import org.jetbrains.kotlin.diagnostics.Diagnostic +import org.jetbrains.kotlin.fir.FirElement +import org.jetbrains.kotlin.fir.declarations.* +import org.jetbrains.kotlin.fir.expressions.* +import org.jetbrains.kotlin.fir.references.FirResolvedNamedReference +import org.jetbrains.kotlin.fir.resolve.transformers.firClassLike +import org.jetbrains.kotlin.fir.symbols.CallableId +import org.jetbrains.kotlin.fir.symbols.impl.FirConstructorSymbol +import org.jetbrains.kotlin.fir.symbols.impl.FirNamedFunctionSymbol +import org.jetbrains.kotlin.fir.types.* +import org.jetbrains.kotlin.idea.fir.* +import org.jetbrains.kotlin.idea.frontend.api.* +import org.jetbrains.kotlin.idea.references.FirReferenceResolveHelper +import org.jetbrains.kotlin.idea.references.FirReferenceResolveHelper.toTargetPsi +import org.jetbrains.kotlin.name.ClassId +import org.jetbrains.kotlin.name.FqName +import org.jetbrains.kotlin.name.Name +import org.jetbrains.kotlin.psi.* +import org.jetbrains.kotlin.types.model.KotlinTypeMarker + +class AnalysisSessionFirImpl internal constructor( + private val state: FirModuleResolveState +) : FrontendAnalysisSession() { + + override fun getSmartCastedToTypes(expression: KtExpression): Collection? { + // TODO filter out not used smartcasts + return (expression.toFir() as? FirExpressionWithSmartcast)?.typesFromSmartCast + } + + @OptIn(ExperimentalStdlibApi::class) + override fun getImplicitReceiverSmartCasts(expression: KtExpression): Collection { + // TODO filter out not used smartcasts + val qualifiedExpression = expression.getOrBuildFir(state) as? FirQualifiedAccessExpression ?: return emptyList() + if (qualifiedExpression.dispatchReceiver !is FirExpressionWithSmartcast + && qualifiedExpression.extensionReceiver !is FirExpressionWithSmartcast + ) return emptyList() + return buildList { + (qualifiedExpression.dispatchReceiver as? FirExpressionWithSmartcast)?.let { smartCasted -> + ImplicitReceiverSmartCast(smartCasted.typesFromSmartCast, ImplicitReceiverSmartcastKind.DISPATCH) + }?.let(::add) + (qualifiedExpression.extensionReceiver as? FirExpressionWithSmartcast)?.let { smartCasted -> + ImplicitReceiverSmartCast(smartCasted.typesFromSmartCast, ImplicitReceiverSmartcastKind.EXTENSION) + }?.let(::add) + } + } + + override fun renderType(type: KotlinTypeMarker): String = + type.asConeType().render() + + override fun getReturnTypeForKtDeclaration(declaration: KtDeclaration): KotlinTypeMarker? = + declaration.toFir>()?.returnTypeRef?.coneTypeSafe() + + override fun getKtExpressionType(expression: KtExpression): ConeKotlinType? = + expression.toFir()?.typeRef?.coneTypeSafe() + + override fun isSubclassOf(klass: KtClassOrObject, superClassId: ClassId): Boolean { + var result = false + forEachSubClass(klass.toFir() ?: return false) { type -> + result = result || type.firClassLike(state.getSession(klass))?.symbol?.classId == superClassId + } + return result + } + + override fun getDiagnosticsForElement(element: KtElement): Collection { + element.containingKtFile.getOrBuildFirWithDiagnostics(state) + return state.getDiagnostics(element) + } + + override fun resolveCall(call: KtBinaryExpression): CallInfo? { + val firCall = call.toFir() ?: return null + return resolveCall(firCall, call) + } + + override fun resolveCall(call: KtCallExpression): CallInfo? { + val firCall = call.toFir() ?: return null + return resolveCall(firCall, call) + } + + private fun resolveCall(firCall: FirFunctionCall, callExpression: KtExpression): CallInfo? { + val session = callExpression.getSession() + val resolvedFunctionPsi = firCall.calleeReference.toTargetPsi(session) + val resolvedCalleeSymbol = (firCall.calleeReference as? FirResolvedNamedReference)?.resolvedSymbol + return when { + resolvedCalleeSymbol is FirConstructorSymbol -> { + ConstructorCallInfo//todo use proper constructor info + } + firCall.dispatchReceiver is FirQualifiedAccessExpression && firCall.isImplicitFunctionCall() -> { + val target = with(FirReferenceResolveHelper) { + val calleeReference = (firCall.dispatchReceiver as FirQualifiedAccessExpression).calleeReference + calleeReference.toTargetPsi(session) + } + when (target) { + null -> null + is KtValVarKeywordOwner, is PsiField -> { + val functionSymbol = + (firCall.calleeReference as? FirResolvedNamedReference)?.resolvedSymbol as? FirNamedFunctionSymbol + when (functionSymbol?.callableId) { + null -> null + in kotlinFunctionInvokeCallableIds -> VariableAsFunctionCallInfo(target, functionSymbol.fir.isSuspend) + else -> (resolvedFunctionPsi as? KtNamedFunction)?.let { VariableAsFunctionLikeCallInfo(target, it) } + } + } + else -> resolvedFunctionPsi?.asSimpleFunctionCall() + } + } + else -> resolvedFunctionPsi?.asSimpleFunctionCall() + } + } + + private fun PsiElement.asSimpleFunctionCall() = when (this) { + is KtNamedFunction -> SimpleKtFunctionCallInfo(this) + is PsiMethod -> SimpleJavaFunctionCallInfo(this) + else -> null + } + + private fun KtElement.getSession() = state.getSession(this) + + private inline fun KtElement.toFir(phase: FirResolvePhase = FirResolvePhase.BODY_RESOLVE): F? = + getOrBuildFir(state, phase) as? F + + private fun forEachSubClass(firClass: FirClass<*>, action: (FirResolvedTypeRef) -> Unit) { + firClass.superTypeRefs.forEach { superType -> + (superType as? FirResolvedTypeRef)?.let(action) + (superType.firClassLike(firClass.session) as? FirClass<*>?)?.let { forEachSubClass(it, action) } + } + } + + companion object { + fun forElement(element: KtElement): FrontendAnalysisSession = + AnalysisSessionFirImpl(element.firResolveState()) + + private fun KotlinTypeMarker.asConeType(): ConeKotlinType = + this as? ConeKotlinType ?: error("$this should be ConeKotlinType") + + private val kotlinFunctionInvokeCallableIds = (0..23).flatMapTo(hashSetOf()) { arity -> + listOf( + CallableId(KotlinBuiltIns.getFunctionClassId(arity), Name.identifier("invoke")), + CallableId(KotlinBuiltIns.getSuspendFunctionClassId(arity), Name.identifier("invoke")) + ) + } + } + +} \ No newline at end of file diff --git a/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/highlighter/HighlightingUtils.kt b/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/highlighter/HighlightingUtils.kt new file mode 100644 index 00000000000..d635e46c3dd --- /dev/null +++ b/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/highlighter/HighlightingUtils.kt @@ -0,0 +1,59 @@ +/* + * 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.idea.highlighter + +import com.intellij.lang.jvm.JvmModifier +import com.intellij.openapi.editor.colors.TextAttributesKey +import com.intellij.psi.* +import com.intellij.psi.impl.source.PsiFieldImpl +import com.intellij.psi.util.elementType +import org.jetbrains.kotlin.lexer.KtTokens +import org.jetbrains.kotlin.psi.* +import org.jetbrains.kotlin.psi.psiUtil.isAbstract +import org.jetbrains.kotlin.psi.psiUtil.isExtensionDeclaration +import org.jetbrains.kotlin.idea.highlighter.KotlinHighlightingColors as Colors + +internal fun textAttributesKeyForPropertyDeclaration(declaration: PsiElement): TextAttributesKey? = when { + declaration is KtProperty && declaration.isExtensionDeclaration() -> Colors.EXTENSION_PROPERTY + declaration is KtProperty && declaration.isLocal || declaration is PsiLocalVariable -> + Colors.LOCAL_VARIABLE + declaration is KtParameter -> { + if (declaration.valOrVarKeyword != null) Colors.INSTANCE_PROPERTY + else Colors.PARAMETER + } + declaration is PsiParameter -> Colors.PARAMETER + declaration is KtProperty && declaration.isTopLevel -> { + if (declaration.isCustomPropertyDeclaration()) Colors.PACKAGE_PROPERTY_CUSTOM_PROPERTY_DECLARATION + else Colors.PACKAGE_PROPERTY + } + declaration is KtProperty -> { + if (declaration.isCustomPropertyDeclaration()) Colors.INSTANCE_PROPERTY_CUSTOM_PROPERTY_DECLARATION + else Colors.INSTANCE_PROPERTY + } + declaration is PsiField -> Colors.INSTANCE_PROPERTY + else -> null +} + +private fun KtProperty.isCustomPropertyDeclaration() = + getter?.bodyExpression != null || setter?.bodyExpression != null + +@Suppress("UnstableApiUsage") +internal fun textAttributesKeyForTypeDeclaration(declaration: PsiElement): TextAttributesKey? = when { + declaration is KtTypeParameter || declaration is PsiTypeParameter -> Colors.TYPE_PARAMETER + declaration is KtTypeAlias -> Colors.TYPE_ALIAS + declaration is KtClass && declaration.isInterface() + || declaration is PsiClass && declaration.isInterface && !declaration.isAnnotationType -> Colors.TRAIT + declaration.isAnnotationClass() -> Colors.ANNOTATION + declaration is KtObjectDeclaration -> Colors.OBJECT + declaration is KtEnumEntry || declaration is PsiEnumConstant -> Colors.ENUM_ENTRY + declaration is KtClass && declaration.isAbstract() + || declaration is PsiClass && declaration.hasModifier(JvmModifier.ABSTRACT) -> Colors.ABSTRACT_CLASS + declaration is KtClass || declaration is PsiClass -> Colors.CLASS + else -> null +} + +internal fun PsiElement.isAnnotationClass() = + this is KtClass && isAnnotation() || this is PsiClass && isAnnotationType \ No newline at end of file diff --git a/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/highlighter/KotlinFirPsiChecker.kt b/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/highlighter/KotlinFirPsiChecker.kt index f66d8c67eaa..6e67469d43b 100644 --- a/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/highlighter/KotlinFirPsiChecker.kt +++ b/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/highlighter/KotlinFirPsiChecker.kt @@ -8,8 +8,9 @@ package org.jetbrains.kotlin.idea.highlighter import com.intellij.lang.annotation.AnnotationHolder import com.intellij.psi.PsiElement import org.jetbrains.kotlin.diagnostics.Diagnostic -import org.jetbrains.kotlin.idea.fir.firResolveState -import org.jetbrains.kotlin.idea.fir.getOrBuildFirWithDiagnostics +import org.jetbrains.kotlin.idea.frontend.api.FrontendAnalysisSession +import org.jetbrains.kotlin.idea.frontend.api.fir.AnalysisSessionFirImpl +import org.jetbrains.kotlin.idea.highlighter.visitors.FirAfterResolveHighlightingVisitor import org.jetbrains.kotlin.psi.KtElement import org.jetbrains.kotlin.psi.KtFile @@ -20,12 +21,21 @@ class KotlinFirPsiChecker : AbstractKotlinPsiChecker() { override fun annotateElement(element: PsiElement, containingFile: KtFile, holder: AnnotationHolder) { if (element !is KtElement) return - val state = containingFile.firResolveState() - containingFile.getOrBuildFirWithDiagnostics(state) + val analysisSession = AnalysisSessionFirImpl.forElement(element) - val diagnostics = state.getDiagnostics(element) + highlightDiagnostics(element, analysisSession, holder) + + FirAfterResolveHighlightingVisitor + .createListOfVisitors(analysisSession, holder) + .forEach(element::accept) + } + + private fun highlightDiagnostics(element: KtElement, analysisSession: FrontendAnalysisSession, holder: AnnotationHolder) { + val diagnostics = analysisSession.getDiagnosticsForElement(element) if (diagnostics.isEmpty()) return + if (diagnostics.none(Diagnostic::isValid)) return + if (shouldHighlightErrors(element)) { highlightDiagnostics(diagnostics, holder) } diff --git a/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/highlighter/visitors/DeclarationHighlightingVisitor.kt b/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/highlighter/visitors/DeclarationHighlightingVisitor.kt new file mode 100644 index 00000000000..a433939ab99 --- /dev/null +++ b/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/highlighter/visitors/DeclarationHighlightingVisitor.kt @@ -0,0 +1,62 @@ +/* + * 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.idea.highlighter.visitors + +import com.intellij.lang.annotation.AnnotationHolder +import com.intellij.psi.util.elementType +import org.jetbrains.kotlin.idea.frontend.api.FrontendAnalysisSession +import org.jetbrains.kotlin.idea.highlighter.textAttributesKeyForPropertyDeclaration +import org.jetbrains.kotlin.idea.highlighter.textAttributesKeyForTypeDeclaration +import org.jetbrains.kotlin.lexer.KtTokens +import org.jetbrains.kotlin.psi.* +import org.jetbrains.kotlin.idea.highlighter.KotlinHighlightingColors as Colors + +internal class DeclarationHighlightingVisitor( + analysisSession: FrontendAnalysisSession, + holder: AnnotationHolder +) : FirAfterResolveHighlightingVisitor(analysisSession, holder) { + override fun visitNamedFunction(function: KtNamedFunction) { + highlightNamedDeclaration(function, Colors.FUNCTION_DECLARATION) + super.visitNamedFunction(function) + } + + override fun visitTypeAlias(typeAlias: KtTypeAlias) { + highlightNamedDeclaration(typeAlias, Colors.TYPE_ALIAS) + super.visitTypeAlias(typeAlias) + } + + override fun visitClassOrObject(classOrObject: KtClassOrObject) { + textAttributesKeyForTypeDeclaration(classOrObject)?.let { attributes -> highlightNamedDeclaration(classOrObject, attributes) } + super.visitClassOrObject(classOrObject) + } + + override fun visitTypeParameter(parameter: KtTypeParameter) { + highlightNamedDeclaration(parameter, Colors.TYPE_PARAMETER) + super.visitTypeParameter(parameter) + } + + override fun visitNamedDeclaration(declaration: KtNamedDeclaration) { + if (declaration is KtValVarKeywordOwner) { + textAttributesKeyForPropertyDeclaration(declaration)?.let { attributes -> + highlightNamedDeclaration(declaration, attributes) + } + if (declaration.valOrVarKeyword?.elementType == KtTokens.VAR_KEYWORD) { + highlightNamedDeclaration(declaration, Colors.MUTABLE_VARIABLE) + } + } + super.visitNamedDeclaration(declaration) + } + + + override fun visitSuperTypeCallEntry(call: KtSuperTypeCallEntry) { + val calleeExpression = call.calleeExpression + val typeElement = calleeExpression.typeReference?.typeElement + if (typeElement is KtUserType) { + typeElement.referenceExpression?.let { highlightName(it, Colors.CONSTRUCTOR_CALL) } + } + super.visitSuperTypeCallEntry(call) + } +} \ No newline at end of file diff --git a/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/highlighter/visitors/ExpressionsSmartcastHighlightingVisitor.kt b/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/highlighter/visitors/ExpressionsSmartcastHighlightingVisitor.kt new file mode 100644 index 00000000000..53f1574c9bd --- /dev/null +++ b/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/highlighter/visitors/ExpressionsSmartcastHighlightingVisitor.kt @@ -0,0 +1,65 @@ +/* + * 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.idea.highlighter.visitors + +import com.intellij.lang.annotation.AnnotationHolder +import com.intellij.psi.PsiElement +import org.jetbrains.kotlin.idea.KotlinIdeaAnalysisBundle +import org.jetbrains.kotlin.idea.frontend.api.FrontendAnalysisSession +import org.jetbrains.kotlin.idea.frontend.api.ImplicitReceiverSmartcastKind +import org.jetbrains.kotlin.psi.* + +internal class ExpressionsSmartcastHighlightingVisitor( + analysisSession: FrontendAnalysisSession, + holder: AnnotationHolder +) : FirAfterResolveHighlightingVisitor(analysisSession, holder) { + override fun visitExpression(expression: KtExpression) { + analysisSession.getImplicitReceiverSmartCasts(expression).forEach { (types, kind) -> + val receiverName = when (kind) { + ImplicitReceiverSmartcastKind.EXTENSION -> KotlinIdeaAnalysisBundle.message("extension.implicit.receiver") + ImplicitReceiverSmartcastKind.DISPATCH -> KotlinIdeaAnalysisBundle.message("implicit.receiver") + } + + types.forEach { type -> + createInfoAnnotation( + expression, + KotlinIdeaAnalysisBundle.message( + "0.smart.cast.to.1", + receiverName, + analysisSession.renderType(type) + ) + ).textAttributes = org.jetbrains.kotlin.idea.highlighter.KotlinHighlightingColors.SMART_CAST_RECEIVER + } + } + analysisSession.getSmartCastedToTypes(expression)?.forEach { type -> + createInfoAnnotation( + getSmartCastTarget(expression), + KotlinIdeaAnalysisBundle.message( + "smart.cast.to.0", + analysisSession.renderType(type) + ) + ).textAttributes = org.jetbrains.kotlin.idea.highlighter.KotlinHighlightingColors.SMART_CAST_VALUE + } + + //todo smartcast to null + + super.visitExpression(expression) + } +} + + +private fun getSmartCastTarget(expression: KtExpression): PsiElement { + var target: PsiElement = expression + if (target is KtParenthesizedExpression) { + target = KtPsiUtil.deparenthesize(target) ?: expression + } + return when (target) { + is KtIfExpression -> target.ifKeyword + is KtWhenExpression -> target.whenKeyword + is KtBinaryExpression -> target.operationReference + else -> target + } +} \ No newline at end of file diff --git a/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/highlighter/visitors/FirAfterResolveHighlightingVisitor.kt b/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/highlighter/visitors/FirAfterResolveHighlightingVisitor.kt new file mode 100644 index 00000000000..a99b969ff2a --- /dev/null +++ b/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/highlighter/visitors/FirAfterResolveHighlightingVisitor.kt @@ -0,0 +1,33 @@ +/* + * 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.idea.highlighter.visitors + +import com.intellij.lang.annotation.AnnotationHolder +import org.jetbrains.kotlin.idea.frontend.api.FrontendAnalysisSession +import org.jetbrains.kotlin.idea.highlighter.HighlightingVisitor + +abstract class FirAfterResolveHighlightingVisitor( + protected val analysisSession: FrontendAnalysisSession, + protected val holder: AnnotationHolder +) : HighlightingVisitor(holder) { + + protected fun highlightNamedDeclaration(declaration: KtNamedDeclaration, key: TextAttributesKey) { + declaration.nameIdentifier?.let { highlightName(it, key) } + } + + companion object { + fun createListOfVisitors( + analysisSession: FrontendAnalysisSession, + holder: AnnotationHolder + ): List = listOf( + TypeHighlightingVisitor(analysisSession, holder), + DeclarationHighlightingVisitor(analysisSession, holder), + FunctionCallHighlightingVisitor(analysisSession, holder), + ExpressionsSmartcastHighlightingVisitor(analysisSession, holder), + VariableReferenceHighlightingVisitor(analysisSession, holder), + ) + } +} \ No newline at end of file diff --git a/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/highlighter/visitors/FunctionCallHighlightingVisitor.kt b/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/highlighter/visitors/FunctionCallHighlightingVisitor.kt new file mode 100644 index 00000000000..52af8cbb5a1 --- /dev/null +++ b/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/highlighter/visitors/FunctionCallHighlightingVisitor.kt @@ -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.idea.highlighter.visitors + +import com.intellij.lang.annotation.AnnotationHolder +import com.intellij.openapi.editor.colors.TextAttributesKey +import org.jetbrains.kotlin.idea.frontend.api.* +import org.jetbrains.kotlin.idea.refactoring.fqName.getKotlinFqName +import org.jetbrains.kotlin.lexer.KtTokens +import org.jetbrains.kotlin.psi.* +import org.jetbrains.kotlin.psi.psiUtil.isExtensionDeclaration +import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameOrNull +import org.jetbrains.kotlin.serialization.deserialization.KOTLIN_SUSPEND_BUILT_IN_FUNCTION_FQ_NAME +import org.jetbrains.kotlin.idea.highlighter.KotlinHighlightingColors as Colors + +internal class FunctionCallHighlightingVisitor( + analysisSession: FrontendAnalysisSession, + holder: AnnotationHolder +) : FirAfterResolveHighlightingVisitor(analysisSession, holder) { + override fun visitBinaryExpression(expression: KtBinaryExpression) { + (expression.operationReference as? KtReferenceExpression) + ?.takeIf { + // do not highlight assignment statement + (it as? KtOperationReferenceExpression)?.operationSignTokenType != KtTokens.EQ + }?.let { callee -> + analysisSession.resolveCall(expression) + ?.takeIf { callInfo -> + // ignore arithmetic-like operator calls + (callInfo.targetFunction as? KtNamedFunction)?.hasModifier(KtTokens.OPERATOR_KEYWORD) != true + } + ?.let { callInfo -> + getTextAttributesForCal(callInfo)?.let { attributes -> + highlightName(callee, attributes) + } + } + } + super.visitBinaryExpression(expression) + } + + override fun visitCallExpression(expression: KtCallExpression) { + expression.calleeExpression + ?.takeUnless { it is KtLambdaExpression } + ?.takeUnless { it is KtCallExpression /* KT-16159 */} + ?.let { callee -> + analysisSession.resolveCall(expression)?.let { callInfo -> + getTextAttributesForCal(callInfo)?.let { attributes -> + highlightName(callee, attributes) + } + } + } + super.visitCallExpression(expression) + } + + private fun getTextAttributesForCal(callInfo: CallInfo): TextAttributesKey? = when { + callInfo.isSuspendCall -> Colors.SUSPEND_FUNCTION_CALL + callInfo is ConstructorCallInfo -> + Colors.CONSTRUCTOR_CALL + callInfo is SimpleKtFunctionCallInfo -> when { + callInfo.targetFunction.getKotlinFqName() == KOTLIN_SUSPEND_BUILT_IN_FUNCTION_FQ_NAME ->Colors.KEYWORD + callInfo.targetFunction.isExtensionDeclaration() -> Colors.EXTENSION_FUNCTION_CALL + callInfo.targetFunction.parent is KtFile -> Colors.PACKAGE_FUNCTION_CALL + else -> Colors.FUNCTION_CALL + } + callInfo is SimpleJavaFunctionCallInfo -> Colors.FUNCTION_CALL + callInfo is VariableAsFunctionCallInfo -> Colors.VARIABLE_AS_FUNCTION_CALL + callInfo is VariableAsFunctionLikeCallInfo -> Colors.VARIABLE_AS_FUNCTION_LIKE_CALL + else -> null + } +} \ No newline at end of file diff --git a/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/highlighter/visitors/TypeHighlightingVisitor.kt b/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/highlighter/visitors/TypeHighlightingVisitor.kt new file mode 100644 index 00000000000..a362e092a9c --- /dev/null +++ b/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/highlighter/visitors/TypeHighlightingVisitor.kt @@ -0,0 +1,67 @@ +/* + * 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.idea.highlighter.visitors + +import com.intellij.lang.annotation.AnnotationHolder +import com.intellij.openapi.util.TextRange +import com.intellij.psi.PsiElement +import com.intellij.psi.util.PsiTreeUtil +import org.jetbrains.kotlin.idea.frontend.api.FrontendAnalysisSession +import org.jetbrains.kotlin.idea.highlighter.NameHighlighter +import org.jetbrains.kotlin.idea.highlighter.isAnnotationClass +import org.jetbrains.kotlin.idea.highlighter.textAttributesKeyForTypeDeclaration +import org.jetbrains.kotlin.idea.references.mainReference +import org.jetbrains.kotlin.psi.* +import org.jetbrains.kotlin.idea.highlighter.KotlinHighlightingColors as Colors + +internal class TypeHighlightingVisitor( + analysisSession: FrontendAnalysisSession, + holder: AnnotationHolder +) : FirAfterResolveHighlightingVisitor(analysisSession, holder) { + override fun visitSimpleNameExpression(expression: KtSimpleNameExpression) { + if (!NameHighlighter.namesHighlightingEnabled) return + if (expression.isCalleeExpression()) return + val parent = expression.parent + + if (parent is KtInstanceExpressionWithLabel) { + // Do nothing: 'super' and 'this' are highlighted as a keyword + return + } + val target = expression.mainReference.resolve() ?: return + textAttributesKeyForTypeDeclaration(target)?.let { key -> + if (expression.isConstructorCallReference() && key != Colors.ANNOTATION) { + // Do not highlight constructor call as class reference + return@let + } + highlightName(computeHighlightingRangeForUsage(expression, target), key) + } + } + + + private fun computeHighlightingRangeForUsage(expression: KtSimpleNameExpression, target: PsiElement): TextRange { + val expressionRange = expression.textRange + + if (!target.isAnnotationClass()) return expressionRange + + // include '@' symbol if the reference is the first segment of KtAnnotationEntry + // if "Deprecated" is highlighted then '@' should be highlighted too in "@Deprecated" + val annotationEntry = PsiTreeUtil.getParentOfType( + expression, KtAnnotationEntry::class.java, /* strict = */false, KtValueArgumentList::class.java + ) + val atSymbol = annotationEntry?.atSymbol ?: return expressionRange + return TextRange(atSymbol.textRange.startOffset, expression.textRange.endOffset) + } +} + +private fun KtSimpleNameExpression.isCalleeExpression() = + (parent as? KtCallExpression)?.calleeExpression == this + +private fun KtSimpleNameExpression.isConstructorCallReference(): Boolean { + val type = parent as? KtUserType ?: return false + val typeReference = type.parent as? KtTypeReference ?: return false + val constructorCallee = typeReference.parent as? KtConstructorCalleeExpression ?: return false + return constructorCallee.constructorReferenceExpression == this +} \ No newline at end of file diff --git a/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/highlighter/visitors/VariableReferenceHighlightingVisitor.kt b/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/highlighter/visitors/VariableReferenceHighlightingVisitor.kt new file mode 100644 index 00000000000..9a560f67a07 --- /dev/null +++ b/idea/idea-frontend-fir/src/org/jetbrains/kotlin/idea/highlighter/visitors/VariableReferenceHighlightingVisitor.kt @@ -0,0 +1,79 @@ +/* + * 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.idea.highlighter.visitors + +import com.intellij.lang.annotation.AnnotationHolder +import com.intellij.psi.PsiElement +import com.intellij.psi.PsiMethod +import com.intellij.psi.PsiModifier +import com.intellij.psi.PsiVariable +import com.intellij.psi.util.PsiUtilCore +import com.intellij.psi.util.parentOfType +import org.jetbrains.kotlin.idea.KotlinIdeaAnalysisBundle +import org.jetbrains.kotlin.idea.frontend.api.FrontendAnalysisSession +import org.jetbrains.kotlin.idea.highlighter.NameHighlighter +import org.jetbrains.kotlin.idea.highlighter.textAttributesKeyForPropertyDeclaration +import org.jetbrains.kotlin.idea.references.mainReference +import org.jetbrains.kotlin.lexer.KtTokens +import org.jetbrains.kotlin.psi.* +import org.jetbrains.kotlin.idea.highlighter.KotlinHighlightingColors as Colors + +internal class VariableReferenceHighlightingVisitor( + analysisSession: FrontendAnalysisSession, + holder: AnnotationHolder +) : FirAfterResolveHighlightingVisitor(analysisSession, holder) { + override fun visitSimpleNameExpression(expression: KtSimpleNameExpression) { + if (!NameHighlighter.namesHighlightingEnabled) return + if (expression.isAssignmentReference()) return + if (expression.parent is KtInstanceExpressionWithLabel) return + + if (expression.isAutoCreatedItParameter()) { + createInfoAnnotation( + expression, + Colors.FUNCTION_LITERAL_DEFAULT_PARAMETER, + KotlinIdeaAnalysisBundle.message("automatically.declared.based.on.the.expected.type") + ) + return + } + + val target = expression.mainReference.resolve() + + when { + target != null && expression.isBackingField(target) -> Colors.BACKING_FIELD_VARIABLE + target is PsiMethod -> Colors.SYNTHETIC_EXTENSION_PROPERTY + target != null -> textAttributesKeyForPropertyDeclaration(target) + else -> null + }?.let { attribute -> + highlightName(expression, attribute) + if (target?.isMutableVariable() == true) { + highlightName(expression, Colors.MUTABLE_VARIABLE) + } + } + } + + + private fun KtSimpleNameExpression.isAutoCreatedItParameter(): Boolean { + return getReferencedName() == "it" // todo + } +} + +private fun PsiElement.isMutableVariable() = when { + this is KtValVarKeywordOwner && PsiUtilCore.getElementType(valOrVarKeyword) == KtTokens.VAR_KEYWORD -> true + this is PsiVariable && !hasModifierProperty(PsiModifier.FINAL) -> true + else -> false +} + +private fun KtSimpleNameExpression.isAssignmentReference(): Boolean { + if (this !is KtOperationReferenceExpression) return false + return operationSignTokenType == KtTokens.EQ +} + +private fun KtSimpleNameExpression.isBackingField(target: PsiElement): Boolean { + if (getReferencedName() != "field") return false + if (target !is KtProperty) return false + val accessor = parentOfType() ?: return false + return accessor.parent == target +} diff --git a/idea/idea-test-framework/test/org/jetbrains/kotlin/idea/test/KotlinLightCodeInsightFixtureTestCase.kt b/idea/idea-test-framework/test/org/jetbrains/kotlin/idea/test/KotlinLightCodeInsightFixtureTestCase.kt index 53a8db11c01..bf054eda8bf 100644 --- a/idea/idea-test-framework/test/org/jetbrains/kotlin/idea/test/KotlinLightCodeInsightFixtureTestCase.kt +++ b/idea/idea-test-framework/test/org/jetbrains/kotlin/idea/test/KotlinLightCodeInsightFixtureTestCase.kt @@ -1,5 +1,5 @@ /* - * Copyright 2000-2020 JetBrains s.r.o. and Kotlin Programming Language contributors. + * 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. */ @@ -200,13 +200,15 @@ abstract class KotlinLightCodeInsightFixtureTestCase : KotlinLightCodeInsightFix InTextDirectivesUtils.isDirectiveDefined(fileText, "ENABLE_MULTIPLATFORM") -> KotlinProjectDescriptorWithFacet.KOTLIN_STABLE_WITH_MULTIPLATFORM - else -> KotlinLightProjectDescriptor.INSTANCE + else -> getDefaultProjectDescriptor() } } catch (e: IOException) { throw rethrow(e) } } + protected open fun getDefaultProjectDescriptor(): KotlinLightProjectDescriptor = KotlinLightProjectDescriptor.INSTANCE + protected fun isAllFilesPresentInTest(): Boolean = KotlinTestUtils.isAllFilesPresentTest(getTestName(false)) protected fun performNotWriteEditorAction(actionId: String): Boolean { diff --git a/idea/tests/org/jetbrains/kotlin/idea/highlighter/AbstractHighlightingTest.java b/idea/tests/org/jetbrains/kotlin/idea/highlighter/AbstractHighlightingTest.java index bad30aa36ce..40d6ff01af9 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/highlighter/AbstractHighlightingTest.java +++ b/idea/tests/org/jetbrains/kotlin/idea/highlighter/AbstractHighlightingTest.java @@ -1,5 +1,5 @@ /* - * Copyright 2010-2019 JetBrains s.r.o. and Kotlin Programming Language contributors. + * 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. */ @@ -10,6 +10,7 @@ import com.intellij.codeInsight.daemon.impl.HighlightInfo; import com.intellij.openapi.util.io.FileUtil; import com.intellij.rt.execution.junit.FileComparisonFailure; import com.intellij.testFramework.ExpectedHighlightingData; +import org.jetbrains.annotations.NotNull; import org.jetbrains.kotlin.idea.test.KotlinLightCodeInsightFixtureTestCase; import org.jetbrains.kotlin.test.InTextDirectivesUtils; import org.jetbrains.kotlin.test.TagsTestDataUtil; @@ -24,18 +25,23 @@ public abstract class AbstractHighlightingTest extends KotlinLightCodeInsightFix public static final String NO_CHECK_WARNINGS_PREFIX = "// NO_CHECK_WARNINGS"; public static final String EXPECTED_DUPLICATED_HIGHLIGHTING_PREFIX = "// EXPECTED_DUPLICATED_HIGHLIGHTING"; - protected void doTest(String unused) throws Exception { - String fileText = FileUtil.loadFile(new File(testPath()), true); + protected void checkHighlighting(@NotNull String fileText) { boolean checkInfos = !InTextDirectivesUtils.isDirectiveDefined(fileText, NO_CHECK_INFOS_PREFIX); boolean checkWeakWarnings = !InTextDirectivesUtils.isDirectiveDefined(fileText, NO_CHECK_WEAK_WARNINGS_PREFIX); boolean checkWarnings = !InTextDirectivesUtils.isDirectiveDefined(fileText, NO_CHECK_WARNINGS_PREFIX); + + myFixture.checkHighlighting(checkWarnings, checkInfos, checkWeakWarnings); + } + + protected void doTest(String unused) throws Exception { + String fileText = FileUtil.loadFile(new File(testPath()), true); boolean expectedDuplicatedHighlighting = InTextDirectivesUtils.isDirectiveDefined(fileText, EXPECTED_DUPLICATED_HIGHLIGHTING_PREFIX); myFixture.configureByFile(fileName()); withExpectedDuplicatedHighlighting(expectedDuplicatedHighlighting, () -> { try { - myFixture.checkHighlighting(checkWarnings, checkInfos, checkWeakWarnings); + checkHighlighting(fileText); } catch (FileComparisonFailure e) { List highlights = diff --git a/prepare/idea-plugin/build.gradle.kts b/prepare/idea-plugin/build.gradle.kts index 6e76494037f..9458a057720 100644 --- a/prepare/idea-plugin/build.gradle.kts +++ b/prepare/idea-plugin/build.gradle.kts @@ -95,6 +95,7 @@ val projectsToShadow by extra(listOf( ":idea:idea-jps-common", ":idea:idea-frontend-independent", ":idea:idea-frontend-fir", + ":idea:idea-frontend-api", *if (Ide.IJ()) arrayOf( ":idea:idea-maven", diff --git a/settings.gradle b/settings.gradle index 76e21ab767e..fc567baad5d 100644 --- a/settings.gradle +++ b/settings.gradle @@ -185,6 +185,7 @@ include ":kotlin-build-common", ":idea:scripting-support", ":idea:idea-frontend-independent", ":idea:idea-frontend-fir", + ":idea:idea-frontend-api", ":libraries:tools:new-project-wizard", ":idea:idea-new-project-wizard", ":libraries:tools:new-project-wizard:new-project-wizard-cli",