Enable injecting predefined and custom Java places in Kotlin
This commit is contained in:
@@ -16,12 +16,20 @@
|
|||||||
|
|
||||||
package org.jetbrains.kotlin.idea.injection
|
package org.jetbrains.kotlin.idea.injection
|
||||||
|
|
||||||
|
import com.intellij.openapi.progress.ProgressManager
|
||||||
import com.intellij.openapi.util.TextRange
|
import com.intellij.openapi.util.TextRange
|
||||||
import com.intellij.psi.InjectedLanguagePlaces
|
import com.intellij.psi.InjectedLanguagePlaces
|
||||||
import com.intellij.psi.LanguageInjector
|
import com.intellij.psi.LanguageInjector
|
||||||
import com.intellij.psi.PsiLanguageInjectionHost
|
import com.intellij.psi.PsiLanguageInjectionHost
|
||||||
|
import com.intellij.psi.PsiMethod
|
||||||
|
import com.intellij.psi.util.PsiTreeUtil
|
||||||
|
import org.intellij.plugins.intelliLang.Configuration
|
||||||
import org.intellij.plugins.intelliLang.inject.InjectorUtils
|
import org.intellij.plugins.intelliLang.inject.InjectorUtils
|
||||||
|
import org.intellij.plugins.intelliLang.inject.java.JavaLanguageInjectionSupport
|
||||||
|
import org.jetbrains.kotlin.psi.KtCallExpression
|
||||||
import org.jetbrains.kotlin.psi.KtElement
|
import org.jetbrains.kotlin.psi.KtElement
|
||||||
|
import org.jetbrains.kotlin.psi.KtValueArgument
|
||||||
|
import org.jetbrains.kotlin.psi.KtValueArgumentList
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
class KotlinLanguageInjector : LanguageInjector {
|
class KotlinLanguageInjector : LanguageInjector {
|
||||||
@@ -30,14 +38,51 @@ class KotlinLanguageInjector : LanguageInjector {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun getLanguagesToInject(host: PsiLanguageInjectionHost, injectionPlacesRegistrar: InjectedLanguagePlaces) {
|
override fun getLanguagesToInject(host: PsiLanguageInjectionHost, injectionPlacesRegistrar: InjectedLanguagePlaces) {
|
||||||
if (host !is KtElement) return
|
|
||||||
if (!host.isValidHost) return
|
if (!host.isValidHost) return
|
||||||
val support = kotlinSupport ?: return
|
val ktHost: KtElement = host as? KtElement ?: return
|
||||||
|
|
||||||
val languageId = support.findAnnotationInjectionLanguageId(host) ?: return
|
val injectionInfo =
|
||||||
|
injectWithExplicitCodeInstruction(ktHost)
|
||||||
|
?: injectWithCall(ktHost)
|
||||||
|
?: return
|
||||||
|
|
||||||
val language = InjectorUtils.getLanguageByString(languageId) ?: return
|
val language = InjectorUtils.getLanguageByString(injectionInfo.languageId) ?: return
|
||||||
|
injectionPlacesRegistrar.addPlace(language, TextRange.from(0, ktHost.textLength), injectionInfo.prefix, injectionInfo.suffix)
|
||||||
injectionPlacesRegistrar.addPlace(language, TextRange.from(0, host.getTextLength()), null, null)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun injectWithExplicitCodeInstruction(host: KtElement): InjectionInfo? {
|
||||||
|
val support = kotlinSupport ?: return null
|
||||||
|
val languageId = support.findAnnotationInjectionLanguageId(host) ?: return null
|
||||||
|
return InjectionInfo(languageId, null, null)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun injectWithCall(host: KtElement): InjectionInfo? {
|
||||||
|
val ktHost: KtElement = host
|
||||||
|
val argument = ktHost.parent as? KtValueArgument ?: return null
|
||||||
|
|
||||||
|
val callExpression = PsiTreeUtil.getParentOfType(ktHost, KtCallExpression::class.java) ?: return null
|
||||||
|
val callee = callExpression.calleeExpression ?: return null
|
||||||
|
|
||||||
|
for (reference in callee.references) {
|
||||||
|
ProgressManager.checkCanceled()
|
||||||
|
|
||||||
|
val javaMethod = reference.resolve()
|
||||||
|
if (javaMethod !is PsiMethod) continue
|
||||||
|
|
||||||
|
val argumentIndex = (argument.parent as KtValueArgumentList).arguments.indexOf(argument)
|
||||||
|
val psiParameter = javaMethod.parameterList.parameters.getOrNull(argumentIndex) ?: continue
|
||||||
|
|
||||||
|
val injections = Configuration.getInstance().getInjections(JavaLanguageInjectionSupport.JAVA_SUPPORT_ID)
|
||||||
|
|
||||||
|
for (injection in injections) {
|
||||||
|
if (injection.acceptsPsiElement(psiParameter)) {
|
||||||
|
return InjectionInfo(injection.injectedLanguageId, injection.prefix, injection.suffix)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
private class InjectionInfo(val languageId: String?, val prefix: String?, val suffix: String?)
|
||||||
}
|
}
|
||||||
@@ -16,22 +16,21 @@
|
|||||||
|
|
||||||
package org.jetbrains.kotlin.psi
|
package org.jetbrains.kotlin.psi
|
||||||
|
|
||||||
import com.intellij.openapi.module.Module
|
import com.intellij.injected.editor.EditorWindow
|
||||||
import com.intellij.openapi.roots.ContentEntry
|
import com.intellij.lang.html.HTMLLanguage
|
||||||
import com.intellij.openapi.roots.ModifiableRootModel
|
|
||||||
import com.intellij.openapi.roots.OrderRootType
|
|
||||||
import com.intellij.openapi.vfs.VfsUtil
|
|
||||||
import com.intellij.psi.impl.source.resolve.reference.impl.providers.FileReference
|
import com.intellij.psi.impl.source.resolve.reference.impl.providers.FileReference
|
||||||
import com.intellij.psi.injection.Injectable
|
import com.intellij.psi.injection.Injectable
|
||||||
import com.intellij.testFramework.LightProjectDescriptor
|
import com.intellij.testFramework.LightProjectDescriptor
|
||||||
import com.intellij.testFramework.fixtures.DefaultLightProjectDescriptor
|
|
||||||
import junit.framework.TestCase
|
import junit.framework.TestCase
|
||||||
|
import org.intellij.lang.regexp.RegExpLanguage
|
||||||
import org.intellij.plugins.intelliLang.Configuration
|
import org.intellij.plugins.intelliLang.Configuration
|
||||||
import org.intellij.plugins.intelliLang.inject.InjectLanguageAction
|
import org.intellij.plugins.intelliLang.inject.InjectLanguageAction
|
||||||
import org.intellij.plugins.intelliLang.inject.UnInjectLanguageAction
|
import org.intellij.plugins.intelliLang.inject.UnInjectLanguageAction
|
||||||
|
import org.intellij.plugins.intelliLang.inject.config.BaseInjection
|
||||||
|
import org.intellij.plugins.intelliLang.inject.config.InjectionPlace
|
||||||
import org.intellij.plugins.intelliLang.references.FileReferenceInjector
|
import org.intellij.plugins.intelliLang.references.FileReferenceInjector
|
||||||
import org.jetbrains.kotlin.idea.test.KotlinLightCodeInsightFixtureTestCase
|
import org.jetbrains.kotlin.idea.test.KotlinLightCodeInsightFixtureTestCase
|
||||||
import java.io.File
|
import org.jetbrains.kotlin.idea.test.KotlinLightProjectDescriptor
|
||||||
|
|
||||||
class KotlinInjectionTest : KotlinLightCodeInsightFixtureTestCase() {
|
class KotlinInjectionTest : KotlinLightCodeInsightFixtureTestCase() {
|
||||||
fun testInjectUnInjectOnSimpleString() {
|
fun testInjectUnInjectOnSimpleString() {
|
||||||
@@ -50,13 +49,45 @@ class KotlinInjectionTest : KotlinLightCodeInsightFixtureTestCase() {
|
|||||||
TestCase.assertNull(myFixture.getReferenceAtCaretPosition())
|
TestCase.assertNull(myFixture.getReferenceAtCaretPosition())
|
||||||
}
|
}
|
||||||
|
|
||||||
fun testInjectionWithCommentOnProperty() = doTestInjection(
|
fun testInjectionOnJavaPredefinedMethodWithAnnotation() = testInjectionPresent(
|
||||||
|
"""
|
||||||
|
|val test1 = java.util.regex.Pattern.compile("<caret>pattern")
|
||||||
|
""",
|
||||||
|
RegExpLanguage.INSTANCE.id,
|
||||||
|
unInjectShouldBePresent = false
|
||||||
|
)
|
||||||
|
|
||||||
|
fun testInjectionOnJavaCustomInjectionWithAnnotation() {
|
||||||
|
val customInjection = BaseInjection("java")
|
||||||
|
customInjection.injectedLanguageId = HTMLLanguage.INSTANCE.id
|
||||||
|
val elementPattern = customInjection.compiler.createElementPattern(
|
||||||
|
"""psiParameter().ofMethod(2, psiMethod().withName("replace").withParameters("int", "int", "java.lang.String").definedInClass("java.lang.StringBuilder"))""",
|
||||||
|
"HTML temp rule")
|
||||||
|
customInjection.setInjectionPlaces(InjectionPlace(elementPattern, true))
|
||||||
|
|
||||||
|
try {
|
||||||
|
Configuration.getInstance().replaceInjections(listOf(customInjection), listOf(), true)
|
||||||
|
|
||||||
|
testInjectionPresent(
|
||||||
|
"""
|
||||||
|
|val stringBuilder = StringBuilder().replace(0, 0, "<caret><html></html>")
|
||||||
|
""",
|
||||||
|
HTMLLanguage.INSTANCE.id,
|
||||||
|
unInjectShouldBePresent = false
|
||||||
|
)
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
Configuration.getInstance().replaceInjections(listOf(), listOf(customInjection), true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun testInjectionWithCommentOnProperty() = testInjectionPresent(
|
||||||
"""
|
"""
|
||||||
|//language=file-reference
|
|//language=file-reference
|
||||||
|val test = "<caret>simple"
|
|val test = "<caret>simple"
|
||||||
""")
|
""")
|
||||||
|
|
||||||
fun testInjectionWithMultipleCommentsOnFun() = doTestInjection(
|
fun testInjectionWithMultipleCommentsOnFun() = testInjectionPresent(
|
||||||
"""
|
"""
|
||||||
|// Some comment
|
|// Some comment
|
||||||
|// Other comment
|
|// Other comment
|
||||||
@@ -64,7 +95,7 @@ class KotlinInjectionTest : KotlinLightCodeInsightFixtureTestCase() {
|
|||||||
|fun test() = "<caret>simple"
|
|fun test() = "<caret>simple"
|
||||||
""")
|
""")
|
||||||
|
|
||||||
fun testInjectionWithAnnotationOnPropertyWithAnnotation() = doTestInjection(
|
fun testInjectionWithAnnotationOnPropertyWithAnnotation() = testInjectionPresent(
|
||||||
"""
|
"""
|
||||||
|@org.intellij.lang.annotations.Language("file-reference")
|
|@org.intellij.lang.annotations.Language("file-reference")
|
||||||
|val test = "<caret>simple"
|
|val test = "<caret>simple"
|
||||||
@@ -189,6 +220,18 @@ class KotlinInjectionTest : KotlinLightCodeInsightFixtureTestCase() {
|
|||||||
// |
|
// |
|
||||||
// |fun template(): String = "<caret><html></html>"
|
// |fun template(): String = "<caret><html></html>"
|
||||||
// """
|
// """
|
||||||
|
// )
|
||||||
|
|
||||||
|
// fun testRemoveInjectionOnQualifiedNameWithAnnotation() = doRemoveInjectionTest(
|
||||||
|
// """
|
||||||
|
// |import org.intellij.lang.annotations.Language
|
||||||
|
// |
|
||||||
|
// |@Language("RegExp")
|
||||||
|
// |val s = java.util.regex.Pattern.compile("Hi")
|
||||||
|
// """,
|
||||||
|
// """
|
||||||
|
// |val test1 = java.util.regex.Pattern.compile("Hi")
|
||||||
|
// """
|
||||||
// )
|
// )
|
||||||
|
|
||||||
fun testRemoveInjectionWithComment() = doRemoveInjectionTest(
|
fun testRemoveInjectionWithComment() = doRemoveInjectionTest(
|
||||||
@@ -243,17 +286,27 @@ class KotlinInjectionTest : KotlinLightCodeInsightFixtureTestCase() {
|
|||||||
|
|
||||||
override fun getProjectDescriptor(): LightProjectDescriptor {
|
override fun getProjectDescriptor(): LightProjectDescriptor {
|
||||||
if (getTestName(true).endsWith("WithAnnotation")) {
|
if (getTestName(true).endsWith("WithAnnotation")) {
|
||||||
return PROJECT_DESCRIPTOR_WITH_LANGUAGE_ANNOTATION_LIB
|
return KotlinLightProjectDescriptor.INSTANCE
|
||||||
}
|
}
|
||||||
|
|
||||||
return JAVA_LATEST
|
return JAVA_LATEST
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun doTestInjection(text: String) {
|
private fun testInjectionPresent(text: String, languageId: String? = null, unInjectShouldBePresent: Boolean = true) {
|
||||||
myFixture.configureByText("${getTestName(true)}.kt", text.trimMargin())
|
myFixture.configureByText("${getTestName(true)}.kt", text.trimMargin())
|
||||||
|
|
||||||
TestCase.assertFalse(InjectLanguageAction().isAvailable(project, myFixture.editor, myFixture.file))
|
TestCase.assertFalse("Injection action is available. There's probably no injection at caret place",
|
||||||
TestCase.assertTrue(UnInjectLanguageAction().isAvailable(project, myFixture.editor, myFixture.file))
|
InjectLanguageAction().isAvailable(project, myFixture.editor, myFixture.file))
|
||||||
|
|
||||||
|
if (languageId != null) {
|
||||||
|
val injectedFile = (editor as? EditorWindow)?.injectedFile
|
||||||
|
assertEquals("Wrong injection language", languageId, injectedFile?.language?.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (unInjectShouldBePresent) {
|
||||||
|
TestCase.assertTrue("UnInjection action is not available. There's no injection at caret place or some other troubles.",
|
||||||
|
UnInjectLanguageAction().isAvailable(project, myFixture.editor, myFixture.file))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun doRemoveInjectionTest(before: String, after: String) {
|
private fun doRemoveInjectionTest(before: String, after: String) {
|
||||||
@@ -285,16 +338,4 @@ class KotlinInjectionTest : KotlinLightCodeInsightFixtureTestCase() {
|
|||||||
configuration.isSourceModificationAllowed = allowed
|
configuration.isSourceModificationAllowed = allowed
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private val PROJECT_DESCRIPTOR_WITH_LANGUAGE_ANNOTATION_LIB = object : DefaultLightProjectDescriptor() {
|
|
||||||
override fun configureModule(module: Module, model: ModifiableRootModel, contentEntry: ContentEntry) {
|
|
||||||
super.configureModule(module, model, contentEntry)
|
|
||||||
|
|
||||||
val jarUrl = VfsUtil.getUrlForLibraryRoot(File("ideaSDK/lib/annotations.jar"))
|
|
||||||
val libraryModel = model.moduleLibraryTable.modifiableModel.createLibrary("annotations").modifiableModel
|
|
||||||
libraryModel.addRoot(jarUrl, OrderRootType.CLASSES)
|
|
||||||
|
|
||||||
libraryModel.commit()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user