Add initial support for injecting languages with comment or annotation
#KT-11472 Fixed
This commit is contained in:
@@ -1433,6 +1433,12 @@
|
|||||||
<filetype.decompiler filetype="kotlin_builtins" implementationClass="com.intellij.psi.impl.compiled.ClassFileDecompiler"/>
|
<filetype.decompiler filetype="kotlin_builtins" implementationClass="com.intellij.psi.impl.compiled.ClassFileDecompiler"/>
|
||||||
|
|
||||||
<pathMacroExpandableProtocol protocol="kotlin-js-meta"/>
|
<pathMacroExpandableProtocol protocol="kotlin-js-meta"/>
|
||||||
|
|
||||||
|
<languageInjector implementation="org.jetbrains.kotlin.idea.injection.KotlinLanguageInjector" />
|
||||||
|
</extensions>
|
||||||
|
|
||||||
|
<extensions defaultExtensionNs="org.intellij.intelliLang">
|
||||||
|
<languageSupport implementation="org.jetbrains.kotlin.idea.injection.KotlinLanguageInjectionSupport"/>
|
||||||
</extensions>
|
</extensions>
|
||||||
|
|
||||||
<xi:include href="extensions/ide.xml" xpointer="xpointer(/idea-plugin/*)"/>
|
<xi:include href="extensions/ide.xml" xpointer="xpointer(/idea-plugin/*)"/>
|
||||||
|
|||||||
@@ -0,0 +1,243 @@
|
|||||||
|
/*
|
||||||
|
* 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.idea.injection
|
||||||
|
|
||||||
|
import com.intellij.codeInsight.AnnotationUtil
|
||||||
|
import com.intellij.lang.Language
|
||||||
|
import com.intellij.openapi.module.ModuleUtilCore
|
||||||
|
import com.intellij.openapi.util.text.StringUtil
|
||||||
|
import com.intellij.psi.*
|
||||||
|
import com.intellij.psi.search.GlobalSearchScope
|
||||||
|
import com.intellij.psi.util.PsiTreeUtil
|
||||||
|
import com.intellij.psi.util.PsiTreeUtil.getDeepestLast
|
||||||
|
import com.intellij.util.ArrayUtil
|
||||||
|
import com.intellij.util.Processor
|
||||||
|
import org.intellij.plugins.intelliLang.Configuration
|
||||||
|
import org.intellij.plugins.intelliLang.inject.AbstractLanguageInjectionSupport
|
||||||
|
import org.intellij.plugins.intelliLang.inject.InjectLanguageAction
|
||||||
|
import org.intellij.plugins.intelliLang.inject.InjectedLanguage
|
||||||
|
import org.intellij.plugins.intelliLang.inject.TemporaryPlacesRegistry
|
||||||
|
import org.jetbrains.annotations.NonNls
|
||||||
|
import org.jetbrains.kotlin.idea.util.addAnnotation
|
||||||
|
import org.jetbrains.kotlin.idea.util.application.executeWriteCommand
|
||||||
|
import org.jetbrains.kotlin.idea.util.findAnnotation
|
||||||
|
import org.jetbrains.kotlin.lexer.KtTokens
|
||||||
|
import org.jetbrains.kotlin.name.FqName
|
||||||
|
import org.jetbrains.kotlin.psi.*
|
||||||
|
import org.jetbrains.kotlin.psi.psiUtil.endOffset
|
||||||
|
import org.jetbrains.kotlin.psi.psiUtil.siblings
|
||||||
|
import org.jetbrains.kotlin.psi.psiUtil.startOffset
|
||||||
|
|
||||||
|
@NonNls val KOTLIN_SUPPORT_ID = "kotlin"
|
||||||
|
val LINE_LANGUAGE_REGEXP_COMMENT = Regex("//\\s*language=[\\w-]+")
|
||||||
|
|
||||||
|
class KotlinLanguageInjectionSupport : AbstractLanguageInjectionSupport() {
|
||||||
|
override fun getId(): String = KOTLIN_SUPPORT_ID
|
||||||
|
|
||||||
|
override fun getPatternClasses() = ArrayUtil.EMPTY_CLASS_ARRAY
|
||||||
|
|
||||||
|
override fun isApplicableTo(host: PsiLanguageInjectionHost?) = host is KtElement
|
||||||
|
|
||||||
|
override fun addInjectionInPlace(language: Language?, host: PsiLanguageInjectionHost?): Boolean {
|
||||||
|
if (language == null || host == null) return false
|
||||||
|
|
||||||
|
val configuration = Configuration.getProjectInstance(host.project).advancedConfiguration
|
||||||
|
if (!configuration.isSourceModificationAllowed) {
|
||||||
|
// It's not allowed to modify code without explicit permission. Postpone adding @Inject or comment till it granted.
|
||||||
|
host.putUserData(InjectLanguageAction.FIX_KEY, Processor { host -> addInjectionInstructionInCode(language, host) })
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!addInjectionInstructionInCode(language, host)) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
TemporaryPlacesRegistry.getInstance(host.project).addHostWithUndo(host, InjectedLanguage.create(language.id))
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun removeInjectionInPlace(psiElement: PsiLanguageInjectionHost?): Boolean {
|
||||||
|
if (psiElement == null || psiElement !is KtElement) return false
|
||||||
|
|
||||||
|
val project = psiElement.getProject()
|
||||||
|
|
||||||
|
val injectionAnnotation = findAnnotationInjection(psiElement)
|
||||||
|
val injectionComment = findInjectionComment(psiElement)
|
||||||
|
|
||||||
|
val injectInstructions = listOf(injectionAnnotation, injectionComment).filterNotNull()
|
||||||
|
|
||||||
|
val configuration = Configuration.getProjectInstance(project)
|
||||||
|
|
||||||
|
TemporaryPlacesRegistry.getInstance(project).removeHostWithUndo(project, psiElement)
|
||||||
|
configuration.replaceInjectionsWithUndo(project, listOf(), listOf(), injectInstructions)
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
fun findInjectionComment(host: KtElement): PsiComment? {
|
||||||
|
if (findCommentInjection(host, null) == null) return null
|
||||||
|
|
||||||
|
val commentsToCheck: Sequence<PsiComment> =
|
||||||
|
findElementToInjectWithAnnotation(host)?.findChildrenComments() ?:
|
||||||
|
findElementToInjectWithComment(host)?.findCommentsBeforeElement() ?:
|
||||||
|
return null
|
||||||
|
|
||||||
|
return commentsToCheck.firstOrNull {
|
||||||
|
it.node.elementType == KtTokens.EOL_COMMENT && it.text.matches(LINE_LANGUAGE_REGEXP_COMMENT)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun findAnnotationInjectionLanguageId(host: KtElement): String? = doFindAnnotationInjectionLanguageId(host)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun doFindAnnotationInjectionLanguageId(host: KtElement): String? {
|
||||||
|
val annotationEntry = findAnnotationInjection(host) ?: return null
|
||||||
|
val firstArgument: ValueArgument = annotationEntry.valueArguments.firstOrNull() ?: return null
|
||||||
|
|
||||||
|
val firstStringArgument = firstArgument.getArgumentExpression() as? KtStringTemplateExpression ?: return null
|
||||||
|
val firstStringEntry = firstStringArgument.entries.singleOrNull() ?: return null
|
||||||
|
|
||||||
|
return firstStringEntry.text
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun findAnnotationInjection(host: KtElement): KtAnnotationEntry? {
|
||||||
|
val modifierListOwner = findElementToInjectWithAnnotation(host) ?: return null
|
||||||
|
|
||||||
|
val modifierList = modifierListOwner.modifierList ?: return null
|
||||||
|
|
||||||
|
// Host can't be before annotation
|
||||||
|
if (host.startOffset < modifierList.endOffset) return null
|
||||||
|
|
||||||
|
return modifierListOwner.findAnnotation(FqName(AnnotationUtil.LANGUAGE))
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun canInjectWithAnnotation(host: PsiElement): Boolean {
|
||||||
|
val module = ModuleUtilCore.findModuleForPsiElement(host) ?: return false
|
||||||
|
val javaPsiFacade = JavaPsiFacade.getInstance(module.project)
|
||||||
|
|
||||||
|
return javaPsiFacade.findClass(AnnotationUtil.LANGUAGE, GlobalSearchScope.moduleWithDependenciesAndLibrariesScope(module)) != null
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun findElementToInjectWithAnnotation(host: KtElement): KtModifierListOwner? {
|
||||||
|
return PsiTreeUtil.getParentOfType(
|
||||||
|
host,
|
||||||
|
KtModifierListOwner::class.java,
|
||||||
|
false, /* strict */
|
||||||
|
KtBlockExpression::class.java, KtParameterList::class.java, KtTypeParameterList::class.java /* Stop at */
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun findElementToInjectWithComment(host: KtElement): KtExpression? {
|
||||||
|
val parentBlockExpression = PsiTreeUtil.getParentOfType(
|
||||||
|
host,
|
||||||
|
KtBlockExpression::class.java,
|
||||||
|
true, /* strict */
|
||||||
|
KtDeclaration::class.java /* Stop at */
|
||||||
|
) ?: return null
|
||||||
|
|
||||||
|
return parentBlockExpression.statements.firstOrNull { statement ->
|
||||||
|
PsiTreeUtil.isAncestor(statement, host, false) && checkIsValidPlaceForInjectionWithLineComment(statement, host)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun addInjectionInstructionInCode(language: Language, host: PsiLanguageInjectionHost): Boolean {
|
||||||
|
val ktHost = host as? KtElement ?: return false
|
||||||
|
val project = ktHost.project
|
||||||
|
|
||||||
|
// Find the place where injection can be stated with annotation or comment
|
||||||
|
val modifierListOwner = findElementToInjectWithAnnotation(ktHost)
|
||||||
|
|
||||||
|
if (modifierListOwner != null && canInjectWithAnnotation(ktHost)) {
|
||||||
|
project.executeWriteCommand("Add injection annotation") {
|
||||||
|
modifierListOwner.addAnnotation(FqName(AnnotationUtil.LANGUAGE), "\"${language.id}\"")
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find the place where injection can be done with one-line comment
|
||||||
|
val commentBeforeAnchor: PsiElement =
|
||||||
|
modifierListOwner?.firstNonCommentChild() ?:
|
||||||
|
findElementToInjectWithComment(ktHost) ?:
|
||||||
|
return false
|
||||||
|
|
||||||
|
val psiFactory = KtPsiFactory(project)
|
||||||
|
val injectComment = psiFactory.createComment("//language=" + language.id)
|
||||||
|
|
||||||
|
project.executeWriteCommand("Add injection comment") {
|
||||||
|
commentBeforeAnchor.parent.addBefore(injectComment, commentBeforeAnchor)
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Inspired with InjectorUtils.findCommentInjection()
|
||||||
|
private fun checkIsValidPlaceForInjectionWithLineComment(statement: KtExpression, host: KtElement): Boolean {
|
||||||
|
// make sure comment is close enough and ...
|
||||||
|
val statementStartOffset = statement.startOffset
|
||||||
|
val hostStart = host.startOffset
|
||||||
|
if (hostStart < statementStartOffset || hostStart - statementStartOffset > 120) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hostStart - statementStartOffset > 2) {
|
||||||
|
// ... there's no non-empty valid host in between comment and e2
|
||||||
|
if (prevWalker(host, statement).asSequence().takeWhile { it != null }.any {
|
||||||
|
it is PsiLanguageInjectionHost && it.isValidHost && !StringUtil.isEmptyOrSpaces(it.text)
|
||||||
|
}) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun PsiElement.findChildrenComments(): Sequence<PsiComment> {
|
||||||
|
return firstChild.siblings().takeWhile { it is PsiComment || it is PsiWhiteSpace }.filterIsInstance<PsiComment>()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun PsiElement.findCommentsBeforeElement(): Sequence<PsiComment> {
|
||||||
|
return siblings(forward = false, withItself = false).takeWhile { it is PsiComment || it is PsiWhiteSpace }.filterIsInstance<PsiComment>()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun PsiElement.firstNonCommentChild(): PsiElement? {
|
||||||
|
return firstChild.siblings().dropWhile { it is PsiComment || it is PsiWhiteSpace }.firstOrNull()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Based on InjectorUtils.prevWalker
|
||||||
|
private fun prevWalker(element: PsiElement, scope: PsiElement): Iterator<PsiElement?> {
|
||||||
|
return object : Iterator<PsiElement?> {
|
||||||
|
private var e: PsiElement? = element
|
||||||
|
|
||||||
|
override fun hasNext(): Boolean = true
|
||||||
|
override fun next(): PsiElement? {
|
||||||
|
val current = e
|
||||||
|
|
||||||
|
if (current == null || current === scope) return null
|
||||||
|
val prev = current.prevSibling
|
||||||
|
if (prev != null) {
|
||||||
|
e = getDeepestLast(prev)
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
val parent = current.parent
|
||||||
|
e = if (parent === scope || parent is PsiFile) null else parent
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,43 @@
|
|||||||
|
/*
|
||||||
|
* 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.idea.injection
|
||||||
|
|
||||||
|
import com.intellij.openapi.util.TextRange
|
||||||
|
import com.intellij.psi.InjectedLanguagePlaces
|
||||||
|
import com.intellij.psi.LanguageInjector
|
||||||
|
import com.intellij.psi.PsiLanguageInjectionHost
|
||||||
|
import org.intellij.plugins.intelliLang.inject.InjectorUtils
|
||||||
|
import org.jetbrains.kotlin.psi.KtElement
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
class KotlinLanguageInjector : LanguageInjector {
|
||||||
|
val kotlinSupport: KotlinLanguageInjectionSupport? by lazy {
|
||||||
|
ArrayList(InjectorUtils.getActiveInjectionSupports()).filterIsInstance(KotlinLanguageInjectionSupport::class.java).firstOrNull()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getLanguagesToInject(host: PsiLanguageInjectionHost, injectionPlacesRegistrar: InjectedLanguagePlaces) {
|
||||||
|
if (host !is KtElement) return
|
||||||
|
if (!host.isValidHost) return
|
||||||
|
val support = kotlinSupport ?: return
|
||||||
|
|
||||||
|
val languageId = support.findAnnotationInjectionLanguageId(host) ?: return
|
||||||
|
|
||||||
|
val language = InjectorUtils.getLanguageByString(languageId) ?: return
|
||||||
|
|
||||||
|
injectionPlacesRegistrar.addPlace(language, TextRange.from(0, host.getTextLength()), null, null)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,286 @@
|
|||||||
|
/*
|
||||||
|
* 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.openapi.module.Module
|
||||||
|
import com.intellij.openapi.roots.ContentEntry
|
||||||
|
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.injection.Injectable
|
||||||
|
import com.intellij.testFramework.LightProjectDescriptor
|
||||||
|
import com.intellij.testFramework.fixtures.DefaultLightProjectDescriptor
|
||||||
|
import junit.framework.TestCase
|
||||||
|
import org.intellij.plugins.intelliLang.Configuration
|
||||||
|
import org.intellij.plugins.intelliLang.inject.InjectLanguageAction
|
||||||
|
import org.intellij.plugins.intelliLang.inject.UnInjectLanguageAction
|
||||||
|
import org.intellij.plugins.intelliLang.references.FileReferenceInjector
|
||||||
|
import org.jetbrains.kotlin.idea.test.KotlinLightCodeInsightFixtureTestCase
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
class KotlinInjectionTest : KotlinLightCodeInsightFixtureTestCase() {
|
||||||
|
fun testInjectUnInjectOnSimpleString() {
|
||||||
|
myFixture.configureByText("test.kt",
|
||||||
|
"""val test = "<caret>simple" """)
|
||||||
|
TestCase.assertTrue(InjectLanguageAction().isAvailable(project, myFixture.editor, myFixture.file))
|
||||||
|
TestCase.assertFalse(UnInjectLanguageAction().isAvailable(project, myFixture.editor, myFixture.file))
|
||||||
|
|
||||||
|
InjectLanguageAction.invokeImpl(project, myFixture.editor, myFixture.file, FileReferenceInjector())
|
||||||
|
TestCase.assertTrue(myFixture.getReferenceAtCaretPosition() is FileReference)
|
||||||
|
|
||||||
|
TestCase.assertFalse(InjectLanguageAction().isAvailable(project, myFixture.editor, myFixture.file))
|
||||||
|
TestCase.assertTrue(UnInjectLanguageAction().isAvailable(project, myFixture.editor, myFixture.file))
|
||||||
|
|
||||||
|
UnInjectLanguageAction.invokeImpl(project, myFixture.editor, myFixture.file)
|
||||||
|
TestCase.assertNull(myFixture.getReferenceAtCaretPosition())
|
||||||
|
}
|
||||||
|
|
||||||
|
fun testInjectionWithCommentOnProperty() = doTestInjection(
|
||||||
|
"""
|
||||||
|
|//language=file-reference
|
||||||
|
|val test = "<caret>simple"
|
||||||
|
""")
|
||||||
|
|
||||||
|
fun testInjectionWithMultipleCommentsOnFun() = doTestInjection(
|
||||||
|
"""
|
||||||
|
|// Some comment
|
||||||
|
|// Other comment
|
||||||
|
|//language=file-reference
|
||||||
|
|fun test() = "<caret>simple"
|
||||||
|
""")
|
||||||
|
|
||||||
|
fun testInjectionWithAnnotationOnPropertyWithAnnotation() = doTestInjection(
|
||||||
|
"""
|
||||||
|
|@org.intellij.lang.annotations.Language("file-reference")
|
||||||
|
|val test = "<caret>simple"
|
||||||
|
""")
|
||||||
|
|
||||||
|
fun testInjectWithCommentOnProperty() = doFileReferenceInjectTest(
|
||||||
|
"""
|
||||||
|
|val test = "<caret>simple"
|
||||||
|
""",
|
||||||
|
"""
|
||||||
|
|//language=file-reference
|
||||||
|
|val test = "simple"
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
fun testInjectWithCommentOnCommentedProperty() = doFileReferenceInjectTest(
|
||||||
|
"""
|
||||||
|
|// Hello
|
||||||
|
|val test = "<caret>simple"
|
||||||
|
""",
|
||||||
|
"""
|
||||||
|
|// Hello
|
||||||
|
|//language=file-reference
|
||||||
|
|val test = "simple"
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
fun testInjectWithCommentOnPropertyWithKDoc() = doFileReferenceInjectTest(
|
||||||
|
"""
|
||||||
|
|/**
|
||||||
|
| * Hi
|
||||||
|
| */
|
||||||
|
|val test = "<caret>simple"
|
||||||
|
""",
|
||||||
|
"""
|
||||||
|
|/**
|
||||||
|
| * Hi
|
||||||
|
| */
|
||||||
|
|//language=file-reference
|
||||||
|
|val test = "<caret>simple"
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
fun testInjectWithCommentOnExpression() = doFileReferenceInjectTest(
|
||||||
|
"""
|
||||||
|
|fun test() {
|
||||||
|
| "<caret>"
|
||||||
|
|}
|
||||||
|
""",
|
||||||
|
"""
|
||||||
|
|fun test() {
|
||||||
|
| //language=file-reference
|
||||||
|
| "<caret>"
|
||||||
|
|}
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
fun testInjectWithCommentOnDeepExpression() = doFileReferenceInjectTest(
|
||||||
|
"""
|
||||||
|
|fun test() {
|
||||||
|
| "" + "<caret>"
|
||||||
|
|}
|
||||||
|
""",
|
||||||
|
"""
|
||||||
|
|fun test() {
|
||||||
|
| "" + "<caret>"
|
||||||
|
|}
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
fun testInjectOnPropertyWithAnnotation() = doFileReferenceInjectTest(
|
||||||
|
"""
|
||||||
|
|val test = "<caret>simple"
|
||||||
|
""",
|
||||||
|
"""
|
||||||
|
|import org.intellij.lang.annotations.Language
|
||||||
|
|
|
||||||
|
|@Language("file-reference")
|
||||||
|
|val test = "simple"
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
fun testInjectWithOnExpressionWithAnnotation() = doFileReferenceInjectTest(
|
||||||
|
"""
|
||||||
|
|fun test() {
|
||||||
|
| "<caret>"
|
||||||
|
|}
|
||||||
|
""",
|
||||||
|
"""
|
||||||
|
|fun test() {
|
||||||
|
| //language=file-reference
|
||||||
|
| "<caret>"
|
||||||
|
|}
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
// TODO: add test for non-default language annotation
|
||||||
|
|
||||||
|
fun testRemoveInjectionWithAnnotation() = doRemoveInjectionTest(
|
||||||
|
"""
|
||||||
|
|import org.intellij.lang.annotations.Language
|
||||||
|
|
|
||||||
|
|@Language("file-reference")
|
||||||
|
|val test = "<caret>simple"
|
||||||
|
""",
|
||||||
|
"""
|
||||||
|
|import org.intellij.lang.annotations.Language
|
||||||
|
|
|
||||||
|
|val test = "simple"
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
fun testRemoveInjectionWithComment() = doRemoveInjectionTest(
|
||||||
|
"""
|
||||||
|
|//language=file-reference
|
||||||
|
|val test = "<caret>simple"
|
||||||
|
""",
|
||||||
|
"""
|
||||||
|
|val test = "simple"
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
fun testRemoveInjectionWithCommentNotFirst() = doRemoveInjectionTest(
|
||||||
|
"""
|
||||||
|
|// Some comment. To do a language injection, add a line comment language=some instruction.
|
||||||
|
|// language=file-reference
|
||||||
|
|val test = "<caret>simple"
|
||||||
|
""",
|
||||||
|
"""
|
||||||
|
|// Some comment. To do a language injection, add a line comment language=some instruction.
|
||||||
|
|val test = "simple"
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
fun testRemoveInjectionWithCommentAfterKDoc() = doRemoveInjectionTest(
|
||||||
|
"""
|
||||||
|
|/**Property*/
|
||||||
|
|// language=file-reference
|
||||||
|
|val test = "<caret>simple"
|
||||||
|
""",
|
||||||
|
"""
|
||||||
|
|/**Property*/
|
||||||
|
|val test = "simple"
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
fun testRemoveInjectionWithCommentInExpression() = doRemoveInjectionTest(
|
||||||
|
"""
|
||||||
|
|fun test() {
|
||||||
|
| // This is my favorite part
|
||||||
|
| // language=RegExp
|
||||||
|
| "<caret>something"
|
||||||
|
|}
|
||||||
|
""",
|
||||||
|
"""
|
||||||
|
|fun test() {
|
||||||
|
| // This is my favorite part
|
||||||
|
| "something"
|
||||||
|
|}
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
override fun getProjectDescriptor(): LightProjectDescriptor {
|
||||||
|
if (getTestName(true).endsWith("WithAnnotation")) {
|
||||||
|
return PROJECT_DESCRIPTOR_WITH_LANGUAGE_ANNOTATION_LIB
|
||||||
|
}
|
||||||
|
|
||||||
|
return JAVA_LATEST
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun doTestInjection(text: String) {
|
||||||
|
myFixture.configureByText("${getTestName(true)}.kt", text.trimMargin())
|
||||||
|
|
||||||
|
TestCase.assertFalse(InjectLanguageAction().isAvailable(project, myFixture.editor, myFixture.file))
|
||||||
|
TestCase.assertTrue(UnInjectLanguageAction().isAvailable(project, myFixture.editor, myFixture.file))
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun doRemoveInjectionTest(before: String, after: String) {
|
||||||
|
myFixture.setCaresAboutInjection(false)
|
||||||
|
|
||||||
|
myFixture.configureByText("${getTestName(true)}.kt", before.trimMargin())
|
||||||
|
|
||||||
|
TestCase.assertTrue(UnInjectLanguageAction().isAvailable(project, myFixture.editor, myFixture.file))
|
||||||
|
UnInjectLanguageAction.invokeImpl(project, myFixture.editor, myFixture.file)
|
||||||
|
|
||||||
|
myFixture.checkResult(after.trimMargin())
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun doFileReferenceInjectTest(before: String, after: String) {
|
||||||
|
doTest(FileReferenceInjector(), before, after)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun doTest(injectable: Injectable, before: String, after: String) {
|
||||||
|
val configuration = Configuration.getProjectInstance(project).advancedConfiguration
|
||||||
|
val allowed = configuration.isSourceModificationAllowed
|
||||||
|
|
||||||
|
configuration.isSourceModificationAllowed = true
|
||||||
|
try {
|
||||||
|
myFixture.configureByText("${getTestName(true)}.kt", before.trimMargin())
|
||||||
|
InjectLanguageAction.invokeImpl(project, myFixture.editor, myFixture.file, injectable)
|
||||||
|
myFixture.checkResult(after.trimMargin())
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
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