diff --git a/idea/idea-test-framework/src/org/jetbrains/kotlin/idea/test/JdkAndMockLibraryProjectDescriptor.java b/idea/idea-test-framework/src/org/jetbrains/kotlin/idea/test/JdkAndMockLibraryProjectDescriptor.java index 79747783728..6b6ea73e51a 100644 --- a/idea/idea-test-framework/src/org/jetbrains/kotlin/idea/test/JdkAndMockLibraryProjectDescriptor.java +++ b/idea/idea-test-framework/src/org/jetbrains/kotlin/idea/test/JdkAndMockLibraryProjectDescriptor.java @@ -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(); diff --git a/idea/src/org/jetbrains/kotlin/idea/injection/KotlinLanguageInjector.kt b/idea/src/org/jetbrains/kotlin/idea/injection/KotlinLanguageInjector.kt index 92ab00fee0f..e7834a1ecaf 100644 --- a/idea/src/org/jetbrains/kotlin/idea/injection/KotlinLanguageInjector.kt +++ b/idea/src/org/jetbrains/kotlin/idea/injection/KotlinLanguageInjector.kt @@ -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) } diff --git a/idea/testData/injection/lib/injection.kt b/idea/testData/injection/lib/injection.kt new file mode 100644 index 00000000000..6b8e9391f7c --- /dev/null +++ b/idea/testData/injection/lib/injection.kt @@ -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) {} + diff --git a/idea/testData/stubs/AnnotationWithValue.expected b/idea/testData/stubs/AnnotationWithValue.expected new file mode 100644 index 00000000000..1f0e13c3656 --- /dev/null +++ b/idea/testData/stubs/AnnotationWithValue.expected @@ -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] diff --git a/idea/testData/stubs/AnnotationWithValue.kt b/idea/testData/stubs/AnnotationWithValue.kt new file mode 100644 index 00000000000..a2228c605f6 --- /dev/null +++ b/idea/testData/stubs/AnnotationWithValue.kt @@ -0,0 +1,3 @@ +annotation class Test(val value: String, val other: String) + +@Test("", other = "") class Annotated \ No newline at end of file diff --git a/idea/tests/org/jetbrains/kotlin/idea/stubs/StubBuilderTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/stubs/StubBuilderTestGenerated.java index aa529dd50d7..4763afffacf 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/stubs/StubBuilderTestGenerated.java +++ b/idea/tests/org/jetbrains/kotlin/idea/stubs/StubBuilderTestGenerated.java @@ -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"); diff --git a/idea/tests/org/jetbrains/kotlin/psi/KotlinLibInjectionTest.kt b/idea/tests/org/jetbrains/kotlin/psi/KotlinLibInjectionTest.kt new file mode 100644 index 00000000000..3fd4b5adefa --- /dev/null +++ b/idea/tests/org/jetbrains/kotlin/psi/KotlinLibInjectionTest.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("") + } + """, + HTMLLanguage.INSTANCE.id + ) + + fun testFunInjectionWithImportedAnnotation() = assertInjectionPresent( + """ + import injection.regexp + fun test() { + 12.regexp("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")) + } +} +