Enable injecting predefined and custom Java places in Kotlin

This commit is contained in:
Nikolay Krasko
2016-04-06 18:51:04 +03:00
parent fff4aa8f12
commit 233635e0d8
2 changed files with 119 additions and 33 deletions
@@ -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()
}
}