Code Insight: Implement package references inside of string literals
#KT-12136 In Progress
This commit is contained in:
@@ -80,6 +80,7 @@
|
|||||||
- [`KT-11692`](https://youtrack.jetbrains.com/issue/KT-11692) Support Spring model diagrams for Kotlin classes
|
- [`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-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-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
|
- [`KT-12278`](https://youtrack.jetbrains.com/issue/KT-12278) Implement Spring @Autowired inspection
|
||||||
|
|
||||||
###### Issues fixed
|
###### Issues fixed
|
||||||
|
|||||||
@@ -18,11 +18,12 @@ package org.jetbrains.kotlin.asJava
|
|||||||
|
|
||||||
import com.intellij.openapi.util.TextRange
|
import com.intellij.openapi.util.TextRange
|
||||||
import com.intellij.psi.*
|
import com.intellij.psi.*
|
||||||
import com.intellij.psi.impl.source.resolve.reference.ReferenceProvidersRegistry
|
|
||||||
import com.intellij.util.IncorrectOperationException
|
import com.intellij.util.IncorrectOperationException
|
||||||
import org.jetbrains.kotlin.idea.KotlinLanguage
|
import org.jetbrains.kotlin.idea.KotlinLanguage
|
||||||
import org.jetbrains.kotlin.psi.KtAnnotationEntry
|
import org.jetbrains.kotlin.psi.KtAnnotationEntry
|
||||||
|
import org.jetbrains.kotlin.psi.KtCallExpression
|
||||||
import org.jetbrains.kotlin.psi.KtParameter
|
import org.jetbrains.kotlin.psi.KtParameter
|
||||||
|
import org.jetbrains.kotlin.psi.ValueArgument
|
||||||
import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType
|
import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType
|
||||||
import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall
|
import org.jetbrains.kotlin.resolve.calls.callUtil.getResolvedCall
|
||||||
import org.jetbrains.kotlin.resolve.calls.model.DefaultValueArgument
|
import org.jetbrains.kotlin.resolve.calls.model.DefaultValueArgument
|
||||||
@@ -35,7 +36,7 @@ class KtLightAnnotation(
|
|||||||
override val kotlinOrigin: KtAnnotationEntry,
|
override val kotlinOrigin: KtAnnotationEntry,
|
||||||
private val owner: PsiAnnotationOwner
|
private val owner: PsiAnnotationOwner
|
||||||
) : PsiAnnotation by clsDelegate, KtLightElement<KtAnnotationEntry, PsiAnnotation> {
|
) : PsiAnnotation by clsDelegate, KtLightElement<KtAnnotationEntry, PsiAnnotation> {
|
||||||
inner class LightExpressionValue(private val delegate: PsiExpression) : PsiAnnotationMemberValue, PsiExpression by delegate {
|
open inner class LightExpressionValue<out D : PsiExpression>(val delegate: D) : PsiAnnotationMemberValue, PsiExpression by delegate {
|
||||||
val originalExpression: PsiElement? by lazy(LazyThreadSafetyMode.PUBLICATION) {
|
val originalExpression: PsiElement? by lazy(LazyThreadSafetyMode.PUBLICATION) {
|
||||||
val nameAndValue = getStrictParentOfType<PsiNameValuePair>() ?: return@lazy null
|
val nameAndValue = getStrictParentOfType<PsiNameValuePair>() ?: return@lazy null
|
||||||
val annotationEntry = this@KtLightAnnotation.kotlinOrigin
|
val annotationEntry = this@KtLightAnnotation.kotlinOrigin
|
||||||
@@ -57,20 +58,24 @@ class KtLightAnnotation(
|
|||||||
}
|
}
|
||||||
|
|
||||||
is ExpressionValueArgument -> {
|
is ExpressionValueArgument -> {
|
||||||
resolvedArgument.valueArgument?.getArgumentExpression()
|
val argExpression = resolvedArgument.valueArgument?.getArgumentExpression()
|
||||||
|
// arrayOf()
|
||||||
|
if (argExpression is KtCallExpression) unwrapArray(argExpression.valueArguments) else argExpression
|
||||||
}
|
}
|
||||||
|
|
||||||
is VarargValueArgument -> {
|
is VarargValueArgument -> unwrapArray(resolvedArgument.arguments)
|
||||||
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()
|
|
||||||
}
|
|
||||||
|
|
||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun unwrapArray(arguments: List<ValueArgument>): 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 getReference() = references.singleOrNull()
|
||||||
override fun getReferences() = originalExpression?.references ?: PsiReference.EMPTY_ARRAY
|
override fun getReferences() = originalExpression?.references ?: PsiReference.EMPTY_ARRAY
|
||||||
override fun getLanguage() = KotlinLanguage.INSTANCE
|
override fun getLanguage() = KotlinLanguage.INSTANCE
|
||||||
@@ -78,6 +83,10 @@ class KtLightAnnotation(
|
|||||||
override fun getTextRange() = originalExpression?.textRange ?: TextRange.EMPTY_RANGE
|
override fun getTextRange() = originalExpression?.textRange ?: TextRange.EMPTY_RANGE
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inner class LightStringLiteral(delegate: PsiLiteralExpression): LightExpressionValue<PsiLiteralExpression>(delegate), PsiLiteralExpression {
|
||||||
|
override fun getValue() = delegate.value
|
||||||
|
}
|
||||||
|
|
||||||
inner class LightArrayInitializerValue(private val delegate: PsiArrayInitializerMemberValue) : PsiArrayInitializerMemberValue by delegate {
|
inner class LightArrayInitializerValue(private val delegate: PsiArrayInitializerMemberValue) : PsiArrayInitializerMemberValue by delegate {
|
||||||
private val _initializers by lazy(LazyThreadSafetyMode.PUBLICATION) { delegate.initializers.map { wrapAnnotationValue(it) }.toTypedArray() }
|
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 {
|
private fun wrapAnnotationValue(value: PsiAnnotationMemberValue): PsiAnnotationMemberValue {
|
||||||
return when (value) {
|
return when {
|
||||||
is PsiExpression -> LightExpressionValue(value)
|
value is PsiLiteralExpression && value.value is String -> LightStringLiteral(value)
|
||||||
is PsiArrayInitializerMemberValue -> LightArrayInitializerValue(value)
|
value is PsiExpression -> LightExpressionValue(value)
|
||||||
|
value is PsiArrayInitializerMemberValue -> LightArrayInitializerValue(value)
|
||||||
else -> value
|
else -> value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -112,7 +112,7 @@ val PsiElement.unwrapped: PsiElement?
|
|||||||
get() = when {
|
get() = when {
|
||||||
this is KtLightElement<*, *> -> kotlinOrigin
|
this is KtLightElement<*, *> -> kotlinOrigin
|
||||||
this is KtLightIdentifier -> origin
|
this is KtLightIdentifier -> origin
|
||||||
this is KtLightAnnotation.LightExpressionValue -> originalExpression
|
this is KtLightAnnotation.LightExpressionValue<*> -> originalExpression
|
||||||
else -> this
|
else -> this
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ class KotlinLightConstantExpressionEvaluator : ConstantExpressionEvaluator {
|
|||||||
throwExceptionOnOverflow: Boolean,
|
throwExceptionOnOverflow: Boolean,
|
||||||
auxEvaluator: PsiConstantEvaluationHelper.AuxEvaluator?
|
auxEvaluator: PsiConstantEvaluationHelper.AuxEvaluator?
|
||||||
): Any? {
|
): Any? {
|
||||||
if (expression !is KtLightAnnotation.LightExpressionValue) return null
|
if (expression !is KtLightAnnotation.LightExpressionValue<*>) return null
|
||||||
val expressionToCompute = expression.originalExpression ?: return null
|
val expressionToCompute = expression.originalExpression ?: return null
|
||||||
return when (expressionToCompute) {
|
return when (expressionToCompute) {
|
||||||
is KtExpression -> {
|
is KtExpression -> {
|
||||||
|
|||||||
@@ -4,6 +4,10 @@
|
|||||||
language="kotlin"
|
language="kotlin"
|
||||||
implementation="org.jetbrains.kotlin.idea.spring.references.KotlinSpringReferenceContributor"
|
implementation="org.jetbrains.kotlin.idea.spring.references.KotlinSpringReferenceContributor"
|
||||||
order="before kotlinFilePathReferenceContributor" />
|
order="before kotlinFilePathReferenceContributor" />
|
||||||
|
<psi.referenceContributor
|
||||||
|
language="kotlin"
|
||||||
|
implementation="org.jetbrains.kotlin.idea.jam.KotlinJamReferenceContributor"
|
||||||
|
order="before kotlinFilePathReferenceContributor" />
|
||||||
|
|
||||||
<multiHostInjector implementation="org.jetbrains.kotlin.idea.spring.el.KotlinSpringELInjector"/>
|
<multiHostInjector implementation="org.jetbrains.kotlin.idea.spring.el.KotlinSpringELInjector"/>
|
||||||
|
|
||||||
|
|||||||
@@ -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<KtStringTemplateExpression> { expression ->
|
||||||
|
if (!expression.isPlain()) return@registerMultiProvider PsiReference.EMPTY_ARRAY
|
||||||
|
val argument = expression
|
||||||
|
.parents
|
||||||
|
.filterIsInstance<KtValueArgument>()
|
||||||
|
.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<Any, Any>
|
||||||
|
?: 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<Any>)
|
||||||
|
is List<*> -> {
|
||||||
|
val list = SmartList<PsiReference>()
|
||||||
|
for (item in jam) {
|
||||||
|
val jamElement = item as? JamStringAttributeElement<Any> ?: continue
|
||||||
|
if (jamElement.psiElement?.unwrapped != expression) continue
|
||||||
|
list += converter.createReferences(jamElement)
|
||||||
|
}
|
||||||
|
list.toTypedArray()
|
||||||
|
}
|
||||||
|
else -> PsiReference.EMPTY_ARRAY
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+11
@@ -0,0 +1,11 @@
|
|||||||
|
// ELEMENT: java
|
||||||
|
// CHAR: \n
|
||||||
|
|
||||||
|
import org.springframework.context.annotation.ComponentScan
|
||||||
|
import org.springframework.context.annotation.Configuration
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@ComponentScan(basePackages = arrayOf("ja<caret>va"))
|
||||||
|
open class App {
|
||||||
|
|
||||||
|
}
|
||||||
+11
@@ -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 {
|
||||||
|
|
||||||
|
}
|
||||||
+11
@@ -0,0 +1,11 @@
|
|||||||
|
// ELEMENT: java
|
||||||
|
// CHAR: \t
|
||||||
|
|
||||||
|
import org.springframework.context.annotation.ComponentScan
|
||||||
|
import org.springframework.context.annotation.Configuration
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@ComponentScan(basePackages = arrayOf("ja<caret>va"))
|
||||||
|
open class App {
|
||||||
|
|
||||||
|
}
|
||||||
+11
@@ -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 {
|
||||||
|
|
||||||
|
}
|
||||||
+12
@@ -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("ja<caret>va"))
|
||||||
|
open class App {
|
||||||
|
|
||||||
|
}
|
||||||
+11
@@ -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("<caret>java"))
|
||||||
|
open class App {
|
||||||
|
|
||||||
|
}
|
||||||
+12
@@ -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);
|
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")
|
@TestMetadata("qualifierReferenceEnter.kt")
|
||||||
public void testQualifierReferenceEnter() throws Exception {
|
public void testQualifierReferenceEnter() throws Exception {
|
||||||
String fileName = KotlinTestUtils.navigationMetadata("ultimate/testData/spring/core/references/completion/handler/qualifierReferenceEnter.kt");
|
String fileName = KotlinTestUtils.navigationMetadata("ultimate/testData/spring/core/references/completion/handler/qualifierReferenceEnter.kt");
|
||||||
|
|||||||
+6
@@ -47,6 +47,12 @@ public class SpringReferenceCompletionTestGenerated extends AbstractSpringRefere
|
|||||||
doTest(fileName);
|
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")
|
@TestMetadata("qualifierReference1Xml.kt")
|
||||||
public void testQualifierReference1Xml() throws Exception {
|
public void testQualifierReference1Xml() throws Exception {
|
||||||
String fileName = KotlinTestUtils.navigationMetadata("ultimate/testData/spring/core/references/completion/variants/qualifierReference1Xml.kt");
|
String fileName = KotlinTestUtils.navigationMetadata("ultimate/testData/spring/core/references/completion/variants/qualifierReference1Xml.kt");
|
||||||
|
|||||||
+6
@@ -47,6 +47,12 @@ public class SpringReferenceNavigationTestGenerated extends AbstractSpringRefere
|
|||||||
doTest(fileName);
|
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")
|
@TestMetadata("qualifierReference.kt")
|
||||||
public void testQualifierReference() throws Exception {
|
public void testQualifierReference() throws Exception {
|
||||||
String fileName = KotlinTestUtils.navigationMetadata("ultimate/testData/spring/core/references/navigation/qualifierReference.kt");
|
String fileName = KotlinTestUtils.navigationMetadata("ultimate/testData/spring/core/references/navigation/qualifierReference.kt");
|
||||||
|
|||||||
Reference in New Issue
Block a user