From d69140b7feeb12133ffb13bff35e78a8c7e86b2b Mon Sep 17 00:00:00 2001 From: Alexey Sedunov Date: Mon, 23 May 2016 15:42:56 +0300 Subject: [PATCH] Code Insight: Implement package references inside of string literals #KT-12136 In Progress --- ChangeLog.md | 1 + .../kotlin/asJava/KtLightAnnotation.kt | 34 +++++++---- .../kotlin/asJava/lightClassUtils.kt | 2 +- .../KotlinLightConstantExpressionEvaluator.kt | 2 +- ultimate/resources/META-INF/kotlin-spring.xml | 4 ++ .../idea/jam/KotlinJamReferenceContributor.kt | 57 +++++++++++++++++++ .../handler/packageReferenceEnter.kt | 11 ++++ .../handler/packageReferenceEnter.kt.after | 11 ++++ .../completion/handler/packageReferenceTab.kt | 11 ++++ .../handler/packageReferenceTab.kt.after | 11 ++++ .../completion/variants/packageReference.kt | 12 ++++ .../packageReferenceInComponentScan.kt | 11 ++++ ...ferenceCompletionHandlerTestGenerated.java | 12 ++++ ...pringReferenceCompletionTestGenerated.java | 6 ++ ...pringReferenceNavigationTestGenerated.java | 6 ++ 15 files changed, 177 insertions(+), 14 deletions(-) create mode 100644 ultimate/src/org/jetbrains/kotlin/idea/jam/KotlinJamReferenceContributor.kt create mode 100644 ultimate/testData/spring/core/references/completion/handler/packageReferenceEnter.kt create mode 100644 ultimate/testData/spring/core/references/completion/handler/packageReferenceEnter.kt.after create mode 100644 ultimate/testData/spring/core/references/completion/handler/packageReferenceTab.kt create mode 100644 ultimate/testData/spring/core/references/completion/handler/packageReferenceTab.kt.after create mode 100644 ultimate/testData/spring/core/references/completion/variants/packageReference.kt create mode 100644 ultimate/testData/spring/core/references/navigation/packageReferenceInComponentScan.kt diff --git a/ChangeLog.md b/ChangeLog.md index 22fc9d3fc5c..f804d6a7e68 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -80,6 +80,7 @@ - [`KT-11692`](https://youtrack.jetbrains.com/issue/KT-11692) Support Spring model diagrams for Kotlin classes - [`KT-12079`](https://youtrack.jetbrains.com/issue/KT-12079) Support "Autowired members defined in invalid Spring bean" inspection on Kotlin declarations - [`KT-12092`](https://youtrack.jetbrains.com/issue/KT-12092) Implement bean references in @Qualifier annotations +- [`KT-12136`](https://youtrack.jetbrains.com/issue/KT-12136) Implement package references inside of string literals - [`KT-12278`](https://youtrack.jetbrains.com/issue/KT-12278) Implement Spring @Autowired inspection ###### Issues fixed diff --git a/compiler/light-classes/src/org/jetbrains/kotlin/asJava/KtLightAnnotation.kt b/compiler/light-classes/src/org/jetbrains/kotlin/asJava/KtLightAnnotation.kt index 690e3aa4735..933a7466e2b 100644 --- a/compiler/light-classes/src/org/jetbrains/kotlin/asJava/KtLightAnnotation.kt +++ b/compiler/light-classes/src/org/jetbrains/kotlin/asJava/KtLightAnnotation.kt @@ -18,11 +18,12 @@ package org.jetbrains.kotlin.asJava import com.intellij.openapi.util.TextRange import com.intellij.psi.* -import com.intellij.psi.impl.source.resolve.reference.ReferenceProvidersRegistry import com.intellij.util.IncorrectOperationException import org.jetbrains.kotlin.idea.KotlinLanguage import org.jetbrains.kotlin.psi.KtAnnotationEntry +import org.jetbrains.kotlin.psi.KtCallExpression import org.jetbrains.kotlin.psi.KtParameter +import org.jetbrains.kotlin.psi.ValueArgument import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall import org.jetbrains.kotlin.resolve.calls.model.DefaultValueArgument @@ -35,7 +36,7 @@ class KtLightAnnotation( override val kotlinOrigin: KtAnnotationEntry, private val owner: PsiAnnotationOwner ) : PsiAnnotation by clsDelegate, KtLightElement { - inner class LightExpressionValue(private val delegate: PsiExpression) : PsiAnnotationMemberValue, PsiExpression by delegate { + open inner class LightExpressionValue(val delegate: D) : PsiAnnotationMemberValue, PsiExpression by delegate { val originalExpression: PsiElement? by lazy(LazyThreadSafetyMode.PUBLICATION) { val nameAndValue = getStrictParentOfType() ?: return@lazy null val annotationEntry = this@KtLightAnnotation.kotlinOrigin @@ -57,20 +58,24 @@ class KtLightAnnotation( } is ExpressionValueArgument -> { - resolvedArgument.valueArgument?.getArgumentExpression() + val argExpression = resolvedArgument.valueArgument?.getArgumentExpression() + // arrayOf() + if (argExpression is KtCallExpression) unwrapArray(argExpression.valueArguments) else argExpression } - is VarargValueArgument -> { - val arrayInitializer = parent as? PsiArrayInitializerMemberValue ?: return@lazy null - val exprIndex = arrayInitializer.initializers.indexOf(delegate as PsiAnnotationMemberValue) - if (exprIndex < 0) return@lazy null - resolvedArgument.arguments[exprIndex].getArgumentExpression() - } + is VarargValueArgument -> unwrapArray(resolvedArgument.arguments) else -> null } } + private fun unwrapArray(arguments: List): PsiElement? { + val arrayInitializer = parent as? LightArrayInitializerValue ?: return null + val exprIndex = arrayInitializer.initializers.indexOf(this) + if (exprIndex < 0) return null + return arguments[exprIndex].getArgumentExpression() + } + override fun getReference() = references.singleOrNull() override fun getReferences() = originalExpression?.references ?: PsiReference.EMPTY_ARRAY override fun getLanguage() = KotlinLanguage.INSTANCE @@ -78,6 +83,10 @@ class KtLightAnnotation( override fun getTextRange() = originalExpression?.textRange ?: TextRange.EMPTY_RANGE } + inner class LightStringLiteral(delegate: PsiLiteralExpression): LightExpressionValue(delegate), PsiLiteralExpression { + override fun getValue() = delegate.value + } + inner class LightArrayInitializerValue(private val delegate: PsiArrayInitializerMemberValue) : PsiArrayInitializerMemberValue by delegate { private val _initializers by lazy(LazyThreadSafetyMode.PUBLICATION) { delegate.initializers.map { wrapAnnotationValue(it) }.toTypedArray() } @@ -86,9 +95,10 @@ class KtLightAnnotation( } private fun wrapAnnotationValue(value: PsiAnnotationMemberValue): PsiAnnotationMemberValue { - return when (value) { - is PsiExpression -> LightExpressionValue(value) - is PsiArrayInitializerMemberValue -> LightArrayInitializerValue(value) + return when { + value is PsiLiteralExpression && value.value is String -> LightStringLiteral(value) + value is PsiExpression -> LightExpressionValue(value) + value is PsiArrayInitializerMemberValue -> LightArrayInitializerValue(value) else -> value } } diff --git a/compiler/light-classes/src/org/jetbrains/kotlin/asJava/lightClassUtils.kt b/compiler/light-classes/src/org/jetbrains/kotlin/asJava/lightClassUtils.kt index 29825404594..4b816755631 100644 --- a/compiler/light-classes/src/org/jetbrains/kotlin/asJava/lightClassUtils.kt +++ b/compiler/light-classes/src/org/jetbrains/kotlin/asJava/lightClassUtils.kt @@ -112,7 +112,7 @@ val PsiElement.unwrapped: PsiElement? get() = when { this is KtLightElement<*, *> -> kotlinOrigin this is KtLightIdentifier -> origin - this is KtLightAnnotation.LightExpressionValue -> originalExpression + this is KtLightAnnotation.LightExpressionValue<*> -> originalExpression else -> this } diff --git a/idea/src/org/jetbrains/kotlin/idea/KotlinLightConstantExpressionEvaluator.kt b/idea/src/org/jetbrains/kotlin/idea/KotlinLightConstantExpressionEvaluator.kt index 25cca4c8e72..a45f265d631 100644 --- a/idea/src/org/jetbrains/kotlin/idea/KotlinLightConstantExpressionEvaluator.kt +++ b/idea/src/org/jetbrains/kotlin/idea/KotlinLightConstantExpressionEvaluator.kt @@ -48,7 +48,7 @@ class KotlinLightConstantExpressionEvaluator : ConstantExpressionEvaluator { throwExceptionOnOverflow: Boolean, auxEvaluator: PsiConstantEvaluationHelper.AuxEvaluator? ): Any? { - if (expression !is KtLightAnnotation.LightExpressionValue) return null + if (expression !is KtLightAnnotation.LightExpressionValue<*>) return null val expressionToCompute = expression.originalExpression ?: return null return when (expressionToCompute) { is KtExpression -> { diff --git a/ultimate/resources/META-INF/kotlin-spring.xml b/ultimate/resources/META-INF/kotlin-spring.xml index 74ed659f837..59afba5808d 100644 --- a/ultimate/resources/META-INF/kotlin-spring.xml +++ b/ultimate/resources/META-INF/kotlin-spring.xml @@ -4,6 +4,10 @@ language="kotlin" implementation="org.jetbrains.kotlin.idea.spring.references.KotlinSpringReferenceContributor" order="before kotlinFilePathReferenceContributor" /> + diff --git a/ultimate/src/org/jetbrains/kotlin/idea/jam/KotlinJamReferenceContributor.kt b/ultimate/src/org/jetbrains/kotlin/idea/jam/KotlinJamReferenceContributor.kt new file mode 100644 index 00000000000..2860e10b445 --- /dev/null +++ b/ultimate/src/org/jetbrains/kotlin/idea/jam/KotlinJamReferenceContributor.kt @@ -0,0 +1,57 @@ +package org.jetbrains.kotlin.idea.jam + +import com.intellij.codeInsight.completion.CompletionUtil +import com.intellij.jam.JamService +import com.intellij.jam.JamStringAttributeElement +import com.intellij.jam.reflect.JamStringAttributeMeta +import com.intellij.psi.PsiElementRef +import com.intellij.psi.PsiReference +import com.intellij.psi.PsiReferenceRegistrar +import com.intellij.util.SmartList +import org.jetbrains.kotlin.asJava.toLightAnnotation +import org.jetbrains.kotlin.asJava.unwrapped +import org.jetbrains.kotlin.idea.references.AbstractKotlinReferenceContributor +import org.jetbrains.kotlin.psi.KtAnnotationEntry +import org.jetbrains.kotlin.psi.KtStringTemplateExpression +import org.jetbrains.kotlin.psi.KtValueArgument +import org.jetbrains.kotlin.psi.psiUtil.isPlain +import org.jetbrains.kotlin.psi.psiUtil.parents + +// Based on the JamReferenceContributor +class KotlinJamReferenceContributor : AbstractKotlinReferenceContributor() { + @Suppress("UNCHECKED_CAST") + override fun registerReferenceProviders(registrar: PsiReferenceRegistrar) { + registrar.registerMultiProvider { expression -> + if (!expression.isPlain()) return@registerMultiProvider PsiReference.EMPTY_ARRAY + val argument = expression + .parents + .filterIsInstance() + .firstOrNull() { it.parent?.parent is KtAnnotationEntry } ?: return@registerMultiProvider PsiReference.EMPTY_ARRAY + val annotationEntry = argument.parent.parent as KtAnnotationEntry + val lightAnnotation = (CompletionUtil.getOriginalOrSelf(annotationEntry)).toLightAnnotation() + ?: return@registerMultiProvider PsiReference.EMPTY_ARRAY + + val service = JamService.getJamService(expression.project) + val annotationMeta = service.getMeta(lightAnnotation) + ?: return@registerMultiProvider PsiReference.EMPTY_ARRAY + val attributeName = argument.getArgumentName()?.asName?.asString() + val attribute = annotationMeta.findAttribute(attributeName) as? JamStringAttributeMeta + ?: return@registerMultiProvider PsiReference.EMPTY_ARRAY + val jam = attribute.getJam(PsiElementRef.real(lightAnnotation)) + val converter = attribute.converter + when (jam) { + is JamStringAttributeElement<*> -> converter.createReferences(jam as JamStringAttributeElement) + is List<*> -> { + val list = SmartList() + for (item in jam) { + val jamElement = item as? JamStringAttributeElement ?: continue + if (jamElement.psiElement?.unwrapped != expression) continue + list += converter.createReferences(jamElement) + } + list.toTypedArray() + } + else -> PsiReference.EMPTY_ARRAY + } + } + } +} diff --git a/ultimate/testData/spring/core/references/completion/handler/packageReferenceEnter.kt b/ultimate/testData/spring/core/references/completion/handler/packageReferenceEnter.kt new file mode 100644 index 00000000000..41ee6c3cbd4 --- /dev/null +++ b/ultimate/testData/spring/core/references/completion/handler/packageReferenceEnter.kt @@ -0,0 +1,11 @@ +// ELEMENT: java +// CHAR: \n + +import org.springframework.context.annotation.ComponentScan +import org.springframework.context.annotation.Configuration + +@Configuration +@ComponentScan(basePackages = arrayOf("java")) +open class App { + +} \ No newline at end of file diff --git a/ultimate/testData/spring/core/references/completion/handler/packageReferenceEnter.kt.after b/ultimate/testData/spring/core/references/completion/handler/packageReferenceEnter.kt.after new file mode 100644 index 00000000000..2a2eadee882 --- /dev/null +++ b/ultimate/testData/spring/core/references/completion/handler/packageReferenceEnter.kt.after @@ -0,0 +1,11 @@ +// ELEMENT: java +// CHAR: \n + +import org.springframework.context.annotation.ComponentScan +import org.springframework.context.annotation.Configuration + +@Configuration +@ComponentScan(basePackages = arrayOf("javava")) +open class App { + +} \ No newline at end of file diff --git a/ultimate/testData/spring/core/references/completion/handler/packageReferenceTab.kt b/ultimate/testData/spring/core/references/completion/handler/packageReferenceTab.kt new file mode 100644 index 00000000000..db5ba7d9225 --- /dev/null +++ b/ultimate/testData/spring/core/references/completion/handler/packageReferenceTab.kt @@ -0,0 +1,11 @@ +// ELEMENT: java +// CHAR: \t + +import org.springframework.context.annotation.ComponentScan +import org.springframework.context.annotation.Configuration + +@Configuration +@ComponentScan(basePackages = arrayOf("java")) +open class App { + +} \ No newline at end of file diff --git a/ultimate/testData/spring/core/references/completion/handler/packageReferenceTab.kt.after b/ultimate/testData/spring/core/references/completion/handler/packageReferenceTab.kt.after new file mode 100644 index 00000000000..17e1f5d7589 --- /dev/null +++ b/ultimate/testData/spring/core/references/completion/handler/packageReferenceTab.kt.after @@ -0,0 +1,11 @@ +// ELEMENT: java +// CHAR: \t + +import org.springframework.context.annotation.ComponentScan +import org.springframework.context.annotation.Configuration + +@Configuration +@ComponentScan(basePackages = arrayOf("java")) +open class App { + +} \ No newline at end of file diff --git a/ultimate/testData/spring/core/references/completion/variants/packageReference.kt b/ultimate/testData/spring/core/references/completion/variants/packageReference.kt new file mode 100644 index 00000000000..326f884cbb3 --- /dev/null +++ b/ultimate/testData/spring/core/references/completion/variants/packageReference.kt @@ -0,0 +1,12 @@ +// NUMBER: 2 +// EXIST: { lookupString:"java" } +// EXIST: { lookupString:"javax" } + +import org.springframework.context.annotation.ComponentScan +import org.springframework.context.annotation.Configuration + +@Configuration +@ComponentScan(basePackages = arrayOf("java")) +open class App { + +} \ No newline at end of file diff --git a/ultimate/testData/spring/core/references/navigation/packageReferenceInComponentScan.kt b/ultimate/testData/spring/core/references/navigation/packageReferenceInComponentScan.kt new file mode 100644 index 00000000000..e96efce0e6c --- /dev/null +++ b/ultimate/testData/spring/core/references/navigation/packageReferenceInComponentScan.kt @@ -0,0 +1,11 @@ +// NO_XML_CONFIG +// REF: java + +import org.springframework.context.annotation.ComponentScan +import org.springframework.context.annotation.Configuration + +@Configuration +@ComponentScan(basePackages = arrayOf("java")) +open class App { + +} \ No newline at end of file diff --git a/ultimate/tests/org/jetbrains/kotlin/idea/spring/tests/references/SpringReferenceCompletionHandlerTestGenerated.java b/ultimate/tests/org/jetbrains/kotlin/idea/spring/tests/references/SpringReferenceCompletionHandlerTestGenerated.java index a5b95143a9a..868ececd93e 100644 --- a/ultimate/tests/org/jetbrains/kotlin/idea/spring/tests/references/SpringReferenceCompletionHandlerTestGenerated.java +++ b/ultimate/tests/org/jetbrains/kotlin/idea/spring/tests/references/SpringReferenceCompletionHandlerTestGenerated.java @@ -35,6 +35,18 @@ public class SpringReferenceCompletionHandlerTestGenerated extends AbstractSprin KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("ultimate/testData/spring/core/references/completion/handler"), Pattern.compile("^(.+)\\.kt$"), true); } + @TestMetadata("packageReferenceEnter.kt") + public void testPackageReferenceEnter() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("ultimate/testData/spring/core/references/completion/handler/packageReferenceEnter.kt"); + doTest(fileName); + } + + @TestMetadata("packageReferenceTab.kt") + public void testPackageReferenceTab() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("ultimate/testData/spring/core/references/completion/handler/packageReferenceTab.kt"); + doTest(fileName); + } + @TestMetadata("qualifierReferenceEnter.kt") public void testQualifierReferenceEnter() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("ultimate/testData/spring/core/references/completion/handler/qualifierReferenceEnter.kt"); diff --git a/ultimate/tests/org/jetbrains/kotlin/idea/spring/tests/references/SpringReferenceCompletionTestGenerated.java b/ultimate/tests/org/jetbrains/kotlin/idea/spring/tests/references/SpringReferenceCompletionTestGenerated.java index 92b4ead1311..897083d3858 100644 --- a/ultimate/tests/org/jetbrains/kotlin/idea/spring/tests/references/SpringReferenceCompletionTestGenerated.java +++ b/ultimate/tests/org/jetbrains/kotlin/idea/spring/tests/references/SpringReferenceCompletionTestGenerated.java @@ -47,6 +47,12 @@ public class SpringReferenceCompletionTestGenerated extends AbstractSpringRefere doTest(fileName); } + @TestMetadata("packageReference.kt") + public void testPackageReference() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("ultimate/testData/spring/core/references/completion/variants/packageReference.kt"); + doTest(fileName); + } + @TestMetadata("qualifierReference1Xml.kt") public void testQualifierReference1Xml() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("ultimate/testData/spring/core/references/completion/variants/qualifierReference1Xml.kt"); diff --git a/ultimate/tests/org/jetbrains/kotlin/idea/spring/tests/references/SpringReferenceNavigationTestGenerated.java b/ultimate/tests/org/jetbrains/kotlin/idea/spring/tests/references/SpringReferenceNavigationTestGenerated.java index cd65733fdca..e3630bbbf2c 100644 --- a/ultimate/tests/org/jetbrains/kotlin/idea/spring/tests/references/SpringReferenceNavigationTestGenerated.java +++ b/ultimate/tests/org/jetbrains/kotlin/idea/spring/tests/references/SpringReferenceNavigationTestGenerated.java @@ -47,6 +47,12 @@ public class SpringReferenceNavigationTestGenerated extends AbstractSpringRefere doTest(fileName); } + @TestMetadata("packageReferenceInComponentScan.kt") + public void testPackageReferenceInComponentScan() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("ultimate/testData/spring/core/references/navigation/packageReferenceInComponentScan.kt"); + doTest(fileName); + } + @TestMetadata("qualifierReference.kt") public void testQualifierReference() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("ultimate/testData/spring/core/references/navigation/qualifierReference.kt");