From 64c46a33c48e59de17daf4d45a3e62cdd847ab59 Mon Sep 17 00:00:00 2001 From: Nikolay Krasko Date: Fri, 22 Apr 2016 21:15:14 +0300 Subject: [PATCH] Make parameter injection work for Java methods --- ChangeLog.md | 2 +- .../idea/injection/KotlinLanguageInjector.kt | 25 +++++++- ...ameterInJavaConstructorWithAnnotation.java | 6 ++ ...arameterInJavaConstructorWithAnnotation.kt | 3 + ...onOfCustomParameterJavaWithAnnotation.java | 5 ++ ...tionOfCustomParameterJavaWithAnnotation.kt | 3 + .../kotlin/psi/AbstractInjectionTest.kt | 8 ++- .../kotlin/psi/KotlinInjectionTest.kt | 60 +++++++++++++++---- .../psi/KotlinMultifileInjectionTest.kt | 45 ++++++++++++++ .../kotlin/psi/KotlinStdlibInjectionTest.kt | 2 +- 10 files changed, 143 insertions(+), 16 deletions(-) create mode 100644 idea/testData/injection/InjectionOfCustomParameterInJavaConstructorWithAnnotation.java create mode 100644 idea/testData/injection/InjectionOfCustomParameterInJavaConstructorWithAnnotation.kt create mode 100644 idea/testData/injection/InjectionOfCustomParameterJavaWithAnnotation.java create mode 100644 idea/testData/injection/InjectionOfCustomParameterJavaWithAnnotation.kt create mode 100644 idea/tests/org/jetbrains/kotlin/psi/KotlinMultifileInjectionTest.kt diff --git a/ChangeLog.md b/ChangeLog.md index 55eedd95785..b72b9c1d39e 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -19,7 +19,7 @@ New features: - [KT-2428](https://youtrack.jetbrains.com/issue/KT-11574) Support basic use-cases of language injection for expressions marked with @Language annotation - [KT-11472](https://youtrack.jetbrains.com/issue/KT-11472) Add comment or @Language annotation after "Inject language or reference" intention automatically - Apply injection for the literals in property initializer through property usages -- Enable injection in Kotlin by annotation function or method parameter with @Language annotation +- Enable injection from Java or Kotlin function declaration by annotating parameter with @Language annotation - [KT-11807](https://youtrack.jetbrains.com/issue/KT-11807) Use function body template when generating overriding functions with default body Issues fixed: diff --git a/idea/src/org/jetbrains/kotlin/idea/injection/KotlinLanguageInjector.kt b/idea/src/org/jetbrains/kotlin/idea/injection/KotlinLanguageInjector.kt index 57918c30339..b26b21bd3f2 100644 --- a/idea/src/org/jetbrains/kotlin/idea/injection/KotlinLanguageInjector.kt +++ b/idea/src/org/jetbrains/kotlin/idea/injection/KotlinLanguageInjector.kt @@ -28,6 +28,7 @@ import org.intellij.plugins.intelliLang.Configuration 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.idea.util.findAnnotation import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.psi.* @@ -136,7 +137,21 @@ class KotlinLanguageInjector : LanguageInjector { val argumentIndex = (argument.parent as KtValueArgumentList).arguments.indexOf(argument) val psiParameter = javaMethod.parameterList.parameters.getOrNull(argumentIndex) ?: return null - return findInjection(psiParameter, Configuration.getInstance().getInjections(JavaLanguageInjectionSupport.JAVA_SUPPORT_ID)) + val injectionInfo = findInjection(psiParameter, Configuration.getInstance().getInjections(JavaLanguageInjectionSupport.JAVA_SUPPORT_ID)) + if (injectionInfo != null) { + return injectionInfo + } + + val annotations = AnnotationUtilEx.getAnnotationFrom( + psiParameter, + Configuration.getProjectInstance(psiParameter.project).advancedConfiguration.languageAnnotationPair, + true) + + if (annotations.size > 0) { + return processAnnotationInjectionInner(annotations) + } + + return null } private fun injectionForKotlinCall(argument: KtValueArgument, ktFunction: KtFunction): InjectionInfo? { @@ -168,4 +183,12 @@ class KotlinLanguageInjector : LanguageInjector { } private class InjectionInfo(val languageId: String?, val prefix: String?, val suffix: String?) + + private fun processAnnotationInjectionInner(annotations: Array): InjectionInfo? { + val id = AnnotationUtilEx.calcAnnotationValue(annotations, "value") + val prefix = AnnotationUtilEx.calcAnnotationValue(annotations, "prefix") + val suffix = AnnotationUtilEx.calcAnnotationValue(annotations, "suffix") + + return InjectionInfo(id, prefix, suffix) + } } \ No newline at end of file diff --git a/idea/testData/injection/InjectionOfCustomParameterInJavaConstructorWithAnnotation.java b/idea/testData/injection/InjectionOfCustomParameterInJavaConstructorWithAnnotation.java new file mode 100644 index 00000000000..96a051668d5 --- /dev/null +++ b/idea/testData/injection/InjectionOfCustomParameterInJavaConstructorWithAnnotation.java @@ -0,0 +1,6 @@ +import org.intellij.lang.annotations.Language; + +public class Test { + public Test(@Language("HTML") String str) { + } +} \ No newline at end of file diff --git a/idea/testData/injection/InjectionOfCustomParameterInJavaConstructorWithAnnotation.kt b/idea/testData/injection/InjectionOfCustomParameterInJavaConstructorWithAnnotation.kt new file mode 100644 index 00000000000..8bb9092d289 --- /dev/null +++ b/idea/testData/injection/InjectionOfCustomParameterInJavaConstructorWithAnnotation.kt @@ -0,0 +1,3 @@ +fun bar() { + Test("some") +} \ No newline at end of file diff --git a/idea/testData/injection/InjectionOfCustomParameterJavaWithAnnotation.java b/idea/testData/injection/InjectionOfCustomParameterJavaWithAnnotation.java new file mode 100644 index 00000000000..d507e5b8df1 --- /dev/null +++ b/idea/testData/injection/InjectionOfCustomParameterJavaWithAnnotation.java @@ -0,0 +1,5 @@ +import org.intellij.lang.annotations.Language; + +public class Test { + public static void foo(@Language("HTML") String str) {} +} \ No newline at end of file diff --git a/idea/testData/injection/InjectionOfCustomParameterJavaWithAnnotation.kt b/idea/testData/injection/InjectionOfCustomParameterJavaWithAnnotation.kt new file mode 100644 index 00000000000..e1a2d430a79 --- /dev/null +++ b/idea/testData/injection/InjectionOfCustomParameterJavaWithAnnotation.kt @@ -0,0 +1,3 @@ +import some.Test + +fun bar() { Test.foo("some") } \ No newline at end of file diff --git a/idea/tests/org/jetbrains/kotlin/psi/AbstractInjectionTest.kt b/idea/tests/org/jetbrains/kotlin/psi/AbstractInjectionTest.kt index 506559c7fba..2209bb683d9 100644 --- a/idea/tests/org/jetbrains/kotlin/psi/AbstractInjectionTest.kt +++ b/idea/tests/org/jetbrains/kotlin/psi/AbstractInjectionTest.kt @@ -39,15 +39,19 @@ abstract class AbstractInjectionTest : KotlinLightCodeInsightFixtureTestCase() { } } - protected fun assertInjectionPresent(text: String, languageId: String? = null, unInjectShouldBePresent: Boolean = true) { + protected fun doInjectionPresentTest(text: String, languageId: String? = null, unInjectShouldBePresent: Boolean = true) { myFixture.configureByText("${getTestName(true)}.kt", text.trimIndent()) + assertInjectionPresent(languageId, unInjectShouldBePresent) + } + + protected fun assertInjectionPresent(languageId: String?, unInjectShouldBePresent: Boolean) { TestCase.assertFalse("Injection action is available. There's probably no injection at caret place", InjectLanguageAction().isAvailable(project, myFixture.editor, myFixture.file)) if (languageId != null) { val injectedFile = (editor as? EditorWindow)?.injectedFile - KotlinLightCodeInsightFixtureTestCaseBase.assertEquals("Wrong injection language", languageId, injectedFile?.language?.id) + assertEquals("Wrong injection language", languageId, injectedFile?.language?.id) } if (unInjectShouldBePresent) { diff --git a/idea/tests/org/jetbrains/kotlin/psi/KotlinInjectionTest.kt b/idea/tests/org/jetbrains/kotlin/psi/KotlinInjectionTest.kt index 000b8af438c..dd6215f936e 100644 --- a/idea/tests/org/jetbrains/kotlin/psi/KotlinInjectionTest.kt +++ b/idea/tests/org/jetbrains/kotlin/psi/KotlinInjectionTest.kt @@ -44,7 +44,7 @@ class KotlinInjectionTest : AbstractInjectionTest() { TestCase.assertNull(myFixture.getReferenceAtCaretPosition()) } - fun testInjectionOnJavaPredefinedMethodWithAnnotation() = assertInjectionPresent( + fun testInjectionOnJavaPredefinedMethodWithAnnotation() = doInjectionPresentTest( """ val test1 = java.util.regex.Pattern.compile("pattern") """, @@ -63,7 +63,7 @@ class KotlinInjectionTest : AbstractInjectionTest() { try { Configuration.getInstance().replaceInjections(listOf(customInjection), listOf(), true) - assertInjectionPresent( + doInjectionPresentTest( """ val stringBuilder = StringBuilder().replace(0, 0, "") """, @@ -76,20 +76,20 @@ class KotlinInjectionTest : AbstractInjectionTest() { } } - fun testInjectionWithCommentOnProperty() = assertInjectionPresent( + fun testInjectionWithCommentOnProperty() = doInjectionPresentTest( """ //language=file-reference val test = "simple" """) - fun testInjectionWithUsageOnReceiverWithRuntime() = assertInjectionPresent( + fun testInjectionWithUsageOnReceiverWithRuntime() = doInjectionPresentTest( """ val test = "some" fun foo() = test.toRegex() """, languageId = RegExpLanguage.INSTANCE.id, unInjectShouldBePresent = false) - fun testInjectionWithUsageInParameterWithRuntime() = assertInjectionPresent( + fun testInjectionWithUsageInParameterWithRuntime() = doInjectionPresentTest( """ val test = "some" fun foo() = Regex(test) @@ -103,7 +103,7 @@ class KotlinInjectionTest : AbstractInjectionTest() { fun foo() = Regex(test) """) - fun testInjectionWithMultipleCommentsOnFun() = assertInjectionPresent( + fun testInjectionWithMultipleCommentsOnFun() = doInjectionPresentTest( """ // Some comment // Other comment @@ -111,7 +111,7 @@ class KotlinInjectionTest : AbstractInjectionTest() { fun test() = "simple" """) - fun testInjectionWithAnnotationOnPropertyWithAnnotation() = assertInjectionPresent( + fun testInjectionWithAnnotationOnPropertyWithAnnotation() = doInjectionPresentTest( """ @org.intellij.lang.annotations.Language("file-reference") val test = "simple" @@ -300,7 +300,7 @@ class KotlinInjectionTest : AbstractInjectionTest() { """ ) - fun testInjectionWithUsageInFunctionWithMarkedParameterWithAnnotation() = assertInjectionPresent( + fun testInjectionWithUsageInFunctionWithMarkedParameterWithAnnotation() = doInjectionPresentTest( """ import org.intellij.lang.annotations.Language @@ -312,7 +312,7 @@ class KotlinInjectionTest : AbstractInjectionTest() { """, languageId = HTMLLanguage.INSTANCE.id, unInjectShouldBePresent = false) - fun testInjectionOfCustomParameterWithAnnotation() = assertInjectionPresent( + fun testInjectionOfCustomParameterWithAnnotation() = doInjectionPresentTest( """ import org.intellij.lang.annotations.Language @@ -322,7 +322,7 @@ class KotlinInjectionTest : AbstractInjectionTest() { """, languageId = HTMLLanguage.INSTANCE.id, unInjectShouldBePresent = false) - fun testInjectionOfCustomParameterInConstructorWithAnnotation() = assertInjectionPresent( + fun testInjectionOfCustomParameterInConstructorWithAnnotation() = doInjectionPresentTest( """ import org.intellij.lang.annotations.Language @@ -332,7 +332,7 @@ class KotlinInjectionTest : AbstractInjectionTest() { """, languageId = HTMLLanguage.INSTANCE.id, unInjectShouldBePresent = false) - fun testInjectionOfCustomParameterDefaultCallWithAnnotation() = assertInjectionPresent( + fun testInjectionOfCustomParameterDefaultCallWithAnnotation() = doInjectionPresentTest( """ import org.intellij.lang.annotations.Language @@ -341,4 +341,42 @@ class KotlinInjectionTest : AbstractInjectionTest() { fun other() { foo(s = "some") } """, languageId = HTMLLanguage.INSTANCE.id, unInjectShouldBePresent = false) + +// fun testInjectionOfCustomParameterJavaWithAnnotation() = dotInjectionPresentTest( +// """ +// import some.Test +// +// fun bar() { Test.foo("some") } +// """, +// javaCode = +// """ +// package some; +// +// import org.intellij.lang.annotations.Language; +// +// public class Test { +// public static void foo(@Language("HTML") String str) {} +// } +// """, +// languageId = HTMLLanguage.INSTANCE.id, unInjectShouldBePresent = false +// ) +// +// fun testInjectionOfCustomParameterInJavaConstructorWithAnnotation() = dotInjectionPresentTest( +// """ +// import some.Test +// +// fun bar() { Test("some") } +// """, +// javaCode = +// """ +// package some; +// +// import org.intellij.lang.annotations.Language; +// +// public class Test { +// public Test(@Language("HTML") String str) {} +// } +// """, +// languageId = HTMLLanguage.INSTANCE.id, unInjectShouldBePresent = false +// ) } \ No newline at end of file diff --git a/idea/tests/org/jetbrains/kotlin/psi/KotlinMultifileInjectionTest.kt b/idea/tests/org/jetbrains/kotlin/psi/KotlinMultifileInjectionTest.kt new file mode 100644 index 00000000000..f2c9bd7c732 --- /dev/null +++ b/idea/tests/org/jetbrains/kotlin/psi/KotlinMultifileInjectionTest.kt @@ -0,0 +1,45 @@ +/* + * Copyright 2010-2016 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 org.jetbrains.kotlin.idea.test.PluginTestCaseBase + +class KotlinMultiFileInjectionTest : AbstractInjectionTest() { + public override fun setUp() { + super.setUp() + myFixture.testDataPath = PluginTestCaseBase.getTestDataPathBase() + "/injection" + } + + fun testInjectionOfCustomParameterInJavaConstructorWithAnnotation() = doMultiFileInjectionPresentTest(HTMLLanguage.INSTANCE.id, false) + + fun testInjectionOfCustomParameterJavaWithAnnotation() = doMultiFileInjectionPresentTest(HTMLLanguage.INSTANCE.id, false) + + override fun getTestDataPath(): String? { + return "idea/testData/injection/" + } + + private fun doMultiFileInjectionPresentTest(languageId: String? = null, unInjectShouldBePresent: Boolean = true) { + val testName = getTestName(false) + + myFixture.configureByFile("$testName.java") + myFixture.configureByFile("$testName.kt") + + assertInjectionPresent(languageId, unInjectShouldBePresent) + } +} + diff --git a/idea/tests/org/jetbrains/kotlin/psi/KotlinStdlibInjectionTest.kt b/idea/tests/org/jetbrains/kotlin/psi/KotlinStdlibInjectionTest.kt index 70cd3a448f3..5635a205df1 100644 --- a/idea/tests/org/jetbrains/kotlin/psi/KotlinStdlibInjectionTest.kt +++ b/idea/tests/org/jetbrains/kotlin/psi/KotlinStdlibInjectionTest.kt @@ -63,7 +63,7 @@ class KotlinStdlibInjectionTest : AbstractInjectionTest() { ) private fun assertInjectionPresent(text: String, languageId: String) { - assertInjectionPresent(text, languageId, false) + doInjectionPresentTest(text, languageId = languageId, unInjectShouldBePresent = false) } override fun getProjectDescriptor() = KotlinWithJdkAndRuntimeLightProjectDescriptor.INSTANCE