Get annotation for injection from reference descriptor (KT-15548)

#KT-15548 Fixed
This commit is contained in:
Nikolay Krasko
2017-01-11 19:32:29 +03:00
parent 0cbe19b930
commit c1538aea43
7 changed files with 131 additions and 8 deletions
@@ -22,6 +22,7 @@ import com.intellij.openapi.roots.ModifiableRootModel;
import com.intellij.openapi.roots.OrderRootType;
import com.intellij.openapi.roots.libraries.Library;
import com.intellij.openapi.util.io.FileUtilRt;
import com.intellij.util.ArrayUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.kotlin.test.MockLibraryUtil;
import org.jetbrains.kotlin.utils.PathUtil;
@@ -36,22 +37,30 @@ public class JdkAndMockLibraryProjectDescriptor extends KotlinLightProjectDescri
private final boolean withRuntime;
private final boolean isJsLibrary;
private final boolean allowKotlinPackage;
private final String[] classpath;
public JdkAndMockLibraryProjectDescriptor(String sourcesPath, boolean withSources) {
this(sourcesPath, withSources, false, false, false);
}
public JdkAndMockLibraryProjectDescriptor(String sourcesPath, boolean withSources, boolean withRuntime, boolean isJsLibrary, boolean allowKotlinPackage) {
public JdkAndMockLibraryProjectDescriptor(
String sourcesPath, boolean withSources, boolean withRuntime, boolean isJsLibrary, boolean allowKotlinPackage) {
this(sourcesPath, withSources, withRuntime, isJsLibrary, allowKotlinPackage, ArrayUtil.EMPTY_STRING_ARRAY);
}
public JdkAndMockLibraryProjectDescriptor(
String sourcesPath, boolean withSources, boolean withRuntime, boolean isJsLibrary, boolean allowKotlinPackage, String[] classpath) {
this.sourcesPath = sourcesPath;
this.withSources = withSources;
this.withRuntime = withRuntime;
this.isJsLibrary = isJsLibrary;
this.allowKotlinPackage = allowKotlinPackage;
this.classpath = classpath;
}
@Override
public void configureModule(@NotNull Module module, @NotNull ModifiableRootModel model) {
File libraryJar = MockLibraryUtil.compileLibraryToJar(sourcesPath, LIBRARY_NAME, withSources, isJsLibrary, allowKotlinPackage);
File libraryJar = MockLibraryUtil.compileLibraryToJar(sourcesPath, LIBRARY_NAME, withSources, isJsLibrary, allowKotlinPackage, classpath);
String jarUrl = getJarUrl(libraryJar);
Library.ModifiableModel libraryModel = model.getModuleLibraryTable().getModifiableModel().createLibrary(LIBRARY_NAME).getModifiableModel();
@@ -29,10 +29,14 @@ import org.intellij.plugins.intelliLang.inject.InjectorUtils
import org.intellij.plugins.intelliLang.inject.config.BaseInjection
import org.intellij.plugins.intelliLang.inject.java.JavaLanguageInjectionSupport
import org.intellij.plugins.intelliLang.util.AnnotationUtilEx
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
import org.jetbrains.kotlin.idea.caches.resolve.analyze
import org.jetbrains.kotlin.idea.references.KtReference
import org.jetbrains.kotlin.idea.util.ProjectRootsUtil
import org.jetbrains.kotlin.idea.util.findAnnotation
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.resolve.annotations.argumentValue
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
import java.util.*
class KotlinLanguageInjector : LanguageInjector {
@@ -126,7 +130,7 @@ class KotlinLanguageInjector : LanguageInjector {
}
}
else if (resolvedTo is KtFunction) {
val injectionForJavaMethod = injectionForKotlinCall(argument, resolvedTo)
val injectionForJavaMethod = injectionForKotlinCall(argument, resolvedTo, reference)
if (injectionForJavaMethod != null) {
return injectionForJavaMethod
}
@@ -150,14 +154,14 @@ class KotlinLanguageInjector : LanguageInjector {
Configuration.getProjectInstance(psiParameter.project).advancedConfiguration.languageAnnotationPair,
true)
if (annotations.size > 0) {
if (annotations.isNotEmpty()) {
return processAnnotationInjectionInner(annotations)
}
return null
}
private fun injectionForKotlinCall(argument: KtValueArgument, ktFunction: KtFunction): InjectionInfo? {
private fun injectionForKotlinCall(argument: KtValueArgument, ktFunction: KtFunction, reference: PsiReference): InjectionInfo? {
val argumentIndex = (argument.parent as KtValueArgumentList).arguments.indexOf(argument)
val ktParameter = ktFunction.valueParameters.getOrNull(argumentIndex) ?: return null
@@ -166,8 +170,16 @@ class KotlinLanguageInjector : LanguageInjector {
return patternInjection
}
val injectAnnotation = ktParameter.findAnnotation(FqName(AnnotationUtil.LANGUAGE)) ?: return null
val languageId = extractLanguageFromInjectAnnotation(injectAnnotation) ?: return null
// Found psi element after resolve can be obtained from compiled declaration but annotations parameters are lost there.
// Search for original descriptor from reference.
val ktReference = reference as? KtReference ?: return null
val bindingContext = ktReference.element.analyze(BodyResolveMode.PARTIAL_WITH_DIAGNOSTICS)
val functionDescriptor = ktReference.resolveToDescriptors(bindingContext).singleOrNull() as? FunctionDescriptor ?: return null
val parameterDescriptor = functionDescriptor.valueParameters.getOrNull(argumentIndex) ?: return null
val injectAnnotation = parameterDescriptor.annotations.findAnnotation(FqName(AnnotationUtil.LANGUAGE)) ?: return null
val languageId = injectAnnotation.argumentValue("value") as? String ?: return null
return InjectionInfo(languageId, null, null)
}
+8
View File
@@ -0,0 +1,8 @@
package injection
import org.intellij.lang.annotations.Language
fun Int.html(@org.intellij.lang.annotations.Language("HTML") html: String) {}
fun Int.regexp(@Language("RegExp") html: String) {}
+22
View File
@@ -0,0 +1,22 @@
PsiJetFileStubImpl[package=]
PACKAGE_DIRECTIVE
IMPORT_LIST
CLASS[fqName=Test, isEnumEntry=false, isInterface=false, isLocal=false, isTopLevel=true, name=Test, superNames=[]]
MODIFIER_LIST[annotation]
PRIMARY_CONSTRUCTOR
VALUE_PARAMETER_LIST
VALUE_PARAMETER[fqName=Test.value, hasDefaultValue=false, hasValOrVar=true, isMutable=false, name=value]
TYPE_REFERENCE
USER_TYPE
REFERENCE_EXPRESSION[referencedName=String]
VALUE_PARAMETER[fqName=Test.other, hasDefaultValue=false, hasValOrVar=true, isMutable=false, name=other]
TYPE_REFERENCE
USER_TYPE
REFERENCE_EXPRESSION[referencedName=String]
CLASS[fqName=Annotated, isEnumEntry=false, isInterface=false, isLocal=false, isTopLevel=true, name=Annotated, superNames=[]]
MODIFIER_LIST[]
ANNOTATION_ENTRY[hasValueArguments=true, shortName=Test]
CONSTRUCTOR_CALLEE
TYPE_REFERENCE
USER_TYPE
REFERENCE_EXPRESSION[referencedName=Test]
+3
View File
@@ -0,0 +1,3 @@
annotation class Test(val value: String, val other: String)
@Test("", other = "") class Annotated
@@ -60,6 +60,12 @@ public class StubBuilderTestGenerated extends AbstractStubBuilderTest {
doTest(fileName);
}
@TestMetadata("AnnotationWithValue.kt")
public void testAnnotationWithValue() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/stubs/AnnotationWithValue.kt");
doTest(fileName);
}
@TestMetadata("AnnotationsOnPrimaryCtr.kt")
public void testAnnotationsOnPrimaryCtr() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/stubs/AnnotationsOnPrimaryCtr.kt");
@@ -0,0 +1,63 @@
/*
* Copyright 2010-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.kotlin.psi
import com.intellij.lang.html.HTMLLanguage
import com.intellij.testFramework.LightProjectDescriptor
import org.intellij.lang.annotations.Language
import org.intellij.lang.regexp.RegExpLanguage
import org.jetbrains.kotlin.idea.test.JdkAndMockLibraryProjectDescriptor
import org.jetbrains.kotlin.idea.test.PluginTestCaseBase
import org.jetbrains.kotlin.test.KotlinTestUtils
class KotlinLibInjectionTest : AbstractInjectionTest() {
override fun setUp() {
super.setUp()
}
fun testFunInjection() = assertInjectionPresent(
"""
import injection.html
fun test() {
12.html("<caret><html></html>")
}
""",
HTMLLanguage.INSTANCE.id
)
fun testFunInjectionWithImportedAnnotation() = assertInjectionPresent(
"""
import injection.regexp
fun test() {
12.regexp("<caret>test")
}
""",
RegExpLanguage.INSTANCE.id
)
private fun assertInjectionPresent(@Language("kotlin") text: String, languageId: String) {
doInjectionPresentTest(text, languageId = languageId, unInjectShouldBePresent = false)
}
override fun getProjectDescriptor(): LightProjectDescriptor {
return JdkAndMockLibraryProjectDescriptor(
PluginTestCaseBase.getTestDataPathBase() + "/injection/lib/", false, false, false, true,
arrayOf(KotlinTestUtils.getHomeDirectory() + "/ideaSDK/lib/annotations.jar"))
}
}