172: Revert 173 specific changes in language injection
Revert "`KotlinLanguageInjector` using Registry to enable annotation injections" This reverts commit c16a1d598c2bacc849dd16fed974ccdbf8b80754. Revert "`KotlinLanguageInjector#injectInAnnotationCall` optimization: using `PsiClassNamePatternCondition` to avoid calling `getResolvedCall`" This reverts commit f9eaaeab61b1dd50de9341846e129b58eb47f247. Revert "`KotlinLanguageInjector` can inject into files modified for autocompletion" This reverts commit 7212b22b083023201700696d775355051321b120. Revert "`KotlinLanguageInjector` understands patterns-injections into Java annotations" This reverts commit 45847b515bdfd27cfa5d23ff8b9147054ffa6f60.
This commit is contained in:
@@ -0,0 +1,353 @@
|
|||||||
|
/*
|
||||||
|
* 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.injection.MultiHostInjector
|
||||||
|
import com.intellij.lang.injection.MultiHostRegistrar
|
||||||
|
import com.intellij.openapi.application.ApplicationManager
|
||||||
|
import com.intellij.openapi.progress.ProgressManager
|
||||||
|
import com.intellij.openapi.project.Project
|
||||||
|
import com.intellij.openapi.util.Key
|
||||||
|
import com.intellij.psi.*
|
||||||
|
import com.intellij.psi.impl.source.tree.injected.InjectedLanguageUtil
|
||||||
|
import com.intellij.psi.search.LocalSearchScope
|
||||||
|
import com.intellij.psi.search.searches.ReferencesSearch
|
||||||
|
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.LanguageInjectionSupport
|
||||||
|
import org.intellij.plugins.intelliLang.inject.TemporaryPlacesRegistry
|
||||||
|
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.references.mainReference
|
||||||
|
import org.jetbrains.kotlin.idea.runInReadActionWithWriteActionPriority
|
||||||
|
import org.jetbrains.kotlin.idea.util.ProjectRootsUtil
|
||||||
|
import org.jetbrains.kotlin.name.FqName
|
||||||
|
import org.jetbrains.kotlin.psi.*
|
||||||
|
import org.jetbrains.kotlin.resolve.annotations.argumentValue
|
||||||
|
import org.jetbrains.kotlin.resolve.constants.StringValue
|
||||||
|
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode
|
||||||
|
import java.util.*
|
||||||
|
import kotlin.collections.ArrayList
|
||||||
|
import org.jetbrains.kotlin.util.aliasImportMap
|
||||||
|
import org.jetbrains.kotlin.utils.SmartList
|
||||||
|
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull
|
||||||
|
import org.jetbrains.kotlin.utils.addToStdlib.safeAs
|
||||||
|
|
||||||
|
class KotlinLanguageInjector(
|
||||||
|
val configuration: Configuration,
|
||||||
|
val project: Project,
|
||||||
|
val temporaryPlacesRegistry: TemporaryPlacesRegistry
|
||||||
|
) : MultiHostInjector {
|
||||||
|
companion object {
|
||||||
|
private val STRING_LITERALS_REGEXP = "\"([^\"]*)\"".toRegex()
|
||||||
|
private val ABSENT_KOTLIN_INJECTION = BaseInjection("ABSENT_KOTLIN_BASE_INJECTION")
|
||||||
|
}
|
||||||
|
|
||||||
|
val kotlinSupport: KotlinLanguageInjectionSupport? by lazy {
|
||||||
|
ArrayList(InjectorUtils.getActiveInjectionSupports()).filterIsInstance(KotlinLanguageInjectionSupport::class.java).firstOrNull()
|
||||||
|
}
|
||||||
|
|
||||||
|
private data class KotlinCachedInjection(val modificationCount: Long, val baseInjection: BaseInjection)
|
||||||
|
private var KtStringTemplateExpression.cachedInjectionWithModification: KotlinCachedInjection? by UserDataProperty(
|
||||||
|
Key.create<KotlinCachedInjection>("CACHED_INJECTION_WITH_MODIFICATION"))
|
||||||
|
|
||||||
|
override fun getLanguagesToInject(registrar: MultiHostRegistrar, context: PsiElement) {
|
||||||
|
val ktHost: KtStringTemplateExpression = context as? KtStringTemplateExpression ?: return
|
||||||
|
if (!context.isValidHost) return
|
||||||
|
|
||||||
|
val support = kotlinSupport ?: return
|
||||||
|
|
||||||
|
if (!ProjectRootsUtil.isInProjectOrLibSource(ktHost)) return
|
||||||
|
|
||||||
|
val needImmediateAnswer = with(ApplicationManager.getApplication()) { isDispatchThread && !isUnitTestMode }
|
||||||
|
val kotlinCachedInjection = ktHost.cachedInjectionWithModification
|
||||||
|
val modificationCount = PsiManager.getInstance(project).modificationTracker.modificationCount
|
||||||
|
|
||||||
|
val baseInjection = when {
|
||||||
|
needImmediateAnswer -> {
|
||||||
|
// Can't afford long counting or typing will be laggy. Force cache reuse even if it's outdated.
|
||||||
|
kotlinCachedInjection?.baseInjection ?: ABSENT_KOTLIN_INJECTION
|
||||||
|
}
|
||||||
|
kotlinCachedInjection != null && (modificationCount == kotlinCachedInjection.modificationCount) ->
|
||||||
|
// Cache is up-to-date
|
||||||
|
kotlinCachedInjection.baseInjection
|
||||||
|
else -> {
|
||||||
|
fun computeAndCache(): BaseInjection {
|
||||||
|
val computedInjection = computeBaseInjection(ktHost, support, registrar) ?: ABSENT_KOTLIN_INJECTION
|
||||||
|
ktHost.cachedInjectionWithModification = KotlinCachedInjection(modificationCount, computedInjection)
|
||||||
|
return computedInjection
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ApplicationManager.getApplication().isReadAccessAllowed && ProgressManager.getInstance().progressIndicator == null) {
|
||||||
|
// The action cannot be canceled by caller and by internal checkCanceled() calls.
|
||||||
|
// Force creating new indicator that is canceled on write action start, otherwise there might be lags in typing.
|
||||||
|
runInReadActionWithWriteActionPriority(::computeAndCache) ?: kotlinCachedInjection?.baseInjection ?: ABSENT_KOTLIN_INJECTION
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
computeAndCache()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (baseInjection == ABSENT_KOTLIN_INJECTION) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
val language = InjectorUtils.getLanguageByString(baseInjection.injectedLanguageId) ?: return
|
||||||
|
|
||||||
|
if (ktHost.hasInterpolation()) {
|
||||||
|
val file = ktHost.containingKtFile
|
||||||
|
val parts = splitLiteralToInjectionParts(baseInjection, ktHost) ?: return
|
||||||
|
|
||||||
|
if (parts.ranges.isEmpty()) return
|
||||||
|
|
||||||
|
InjectorUtils.registerInjection(language, parts.ranges, file, registrar)
|
||||||
|
InjectorUtils.registerSupport(support, false, registrar)
|
||||||
|
InjectorUtils.putInjectedFileUserData(registrar, InjectedLanguageUtil.FRANKENSTEIN_INJECTION,
|
||||||
|
if (parts.isUnparsable) java.lang.Boolean.TRUE else null)
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
InjectorUtils.registerInjectionSimple(ktHost, baseInjection, support, registrar)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("FoldInitializerAndIfToElvis")
|
||||||
|
private fun computeBaseInjection(
|
||||||
|
ktHost: KtStringTemplateExpression,
|
||||||
|
support: KotlinLanguageInjectionSupport,
|
||||||
|
registrar: MultiHostRegistrar): BaseInjection? {
|
||||||
|
val containingFile = ktHost.containingFile
|
||||||
|
|
||||||
|
val tempInjectedLanguage = temporaryPlacesRegistry.getLanguageFor(ktHost, containingFile)
|
||||||
|
if (tempInjectedLanguage != null) {
|
||||||
|
InjectorUtils.putInjectedFileUserData(registrar, LanguageInjectionSupport.TEMPORARY_INJECTED_LANGUAGE, tempInjectedLanguage)
|
||||||
|
return BaseInjection(support.id).apply {
|
||||||
|
injectedLanguageId = tempInjectedLanguage.id
|
||||||
|
prefix = tempInjectedLanguage.prefix
|
||||||
|
suffix = tempInjectedLanguage.suffix
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return findInjectionInfo(ktHost)?.toBaseInjection(support)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun elementsToInjectIn(): List<Class<out PsiElement>> {
|
||||||
|
return listOf(KtStringTemplateExpression::class.java)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun findInjectionInfo(place: KtElement, originalHost: Boolean = true): InjectionInfo? {
|
||||||
|
return injectWithExplicitCodeInstruction(place)
|
||||||
|
?: injectWithCall(place)
|
||||||
|
?: injectWithReceiver(place)
|
||||||
|
?: injectWithVariableUsage(place, originalHost)
|
||||||
|
?: injectWithAnnotationEntry(place)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun injectWithExplicitCodeInstruction(host: KtElement): InjectionInfo? {
|
||||||
|
val support = kotlinSupport ?: return null
|
||||||
|
return InjectionInfo.fromBaseInjection(support.findCommentInjection(host)) ?: support.findAnnotationInjectionLanguageId(host)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun injectWithReceiver(host: KtElement): InjectionInfo? {
|
||||||
|
val qualifiedExpression = host.parent as? KtDotQualifiedExpression ?: return null
|
||||||
|
if (qualifiedExpression.receiverExpression != host) return null
|
||||||
|
|
||||||
|
val callExpression = qualifiedExpression.selectorExpression as? KtCallExpression ?: return null
|
||||||
|
val callee = callExpression.calleeExpression ?: return null
|
||||||
|
|
||||||
|
if (isAnalyzeOff(qualifiedExpression.project)) return null
|
||||||
|
|
||||||
|
val kotlinInjections = Configuration.getInstance().getInjections(KOTLIN_SUPPORT_ID)
|
||||||
|
|
||||||
|
val calleeName = callee.text
|
||||||
|
val possibleNames = collectPossibleNames(kotlinInjections)
|
||||||
|
|
||||||
|
if (calleeName !in possibleNames) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
for (reference in callee.references) {
|
||||||
|
ProgressManager.checkCanceled()
|
||||||
|
|
||||||
|
val resolvedTo = reference.resolve()
|
||||||
|
if (resolvedTo is KtFunction) {
|
||||||
|
val injectionInfo = findInjection(resolvedTo.receiverTypeReference, kotlinInjections)
|
||||||
|
if (injectionInfo != null) {
|
||||||
|
return injectionInfo
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun collectPossibleNames(injections: List<BaseInjection>): Set<String> {
|
||||||
|
val result = HashSet<String>()
|
||||||
|
|
||||||
|
for (injection in injections) {
|
||||||
|
val injectionPlaces = injection.injectionPlaces
|
||||||
|
for (place in injectionPlaces) {
|
||||||
|
val placeStr = place.toString()
|
||||||
|
val literals = STRING_LITERALS_REGEXP.findAll(placeStr).map { it.groupValues[1] }
|
||||||
|
result.addAll(literals)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun injectWithVariableUsage(host: KtElement, originalHost: Boolean): InjectionInfo? {
|
||||||
|
// Given place is not original host of the injection so we stop to prevent stepping through indirect references
|
||||||
|
if (!originalHost) return null
|
||||||
|
|
||||||
|
val ktHost: KtElement = host
|
||||||
|
val ktProperty = host.parent as? KtProperty ?: return null
|
||||||
|
if (ktProperty.initializer != host) return null
|
||||||
|
|
||||||
|
if (isAnalyzeOff(ktHost.project)) return null
|
||||||
|
|
||||||
|
val searchScope = LocalSearchScope(arrayOf(ktProperty.containingFile), "", true)
|
||||||
|
return ReferencesSearch.search(ktProperty, searchScope).asSequence().mapNotNull { psiReference ->
|
||||||
|
val element = psiReference.element as? KtElement ?: return@mapNotNull null
|
||||||
|
findInjectionInfo(element, false)
|
||||||
|
}.firstOrNull()
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
if (isAnalyzeOff(ktHost.project)) return null
|
||||||
|
|
||||||
|
for (reference in callee.references) {
|
||||||
|
ProgressManager.checkCanceled()
|
||||||
|
|
||||||
|
val resolvedTo = reference.resolve()
|
||||||
|
if (resolvedTo is PsiMethod) {
|
||||||
|
val injectionForJavaMethod = injectionForJavaMethod(argument, resolvedTo)
|
||||||
|
if (injectionForJavaMethod != null) {
|
||||||
|
return injectionForJavaMethod
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (resolvedTo is KtFunction) {
|
||||||
|
val injectionForJavaMethod = injectionForKotlinCall(argument, resolvedTo, reference)
|
||||||
|
if (injectionForJavaMethod != null) {
|
||||||
|
return injectionForJavaMethod
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun injectionForJavaMethod(argument: KtValueArgument, javaMethod: PsiMethod): InjectionInfo? {
|
||||||
|
val argumentIndex = (argument.parent as KtValueArgumentList).arguments.indexOf(argument)
|
||||||
|
val psiParameter = javaMethod.parameterList.parameters.getOrNull(argumentIndex) ?: return null
|
||||||
|
|
||||||
|
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.isNotEmpty()) {
|
||||||
|
return processAnnotationInjectionInner(annotations)
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
val patternInjection = findInjection(ktParameter, Configuration.getInstance().getInjections(KOTLIN_SUPPORT_ID))
|
||||||
|
if (patternInjection != null) {
|
||||||
|
return patternInjection
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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")?.safeAs<StringValue>()?.value ?: return null
|
||||||
|
return InjectionInfo(languageId, null, null)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun injectWithAnnotationEntry(host: KtElement): InjectionInfo? {
|
||||||
|
val argument = host.parent as? KtValueArgument ?: return null
|
||||||
|
val annotationEntry = PsiTreeUtil.getParentOfType(host, KtAnnotationEntry::class.java) ?: return null
|
||||||
|
if (isAnalyzeOff(host.project)) return null
|
||||||
|
|
||||||
|
val calleeReference = annotationEntry.calleeExpression?.constructorReferenceExpression?.mainReference
|
||||||
|
val callee = calleeReference?.resolve()
|
||||||
|
return when (callee) {
|
||||||
|
is KtFunction -> injectionForKotlinCall(argument, callee, calleeReference)
|
||||||
|
is PsiClass -> {
|
||||||
|
// Look for java injections for the PsiAnnotationMethod.
|
||||||
|
(argument.reference ?: argument.getArgumentName()?.referenceExpression?.mainReference)
|
||||||
|
?.let { it.resolve() as? PsiMethod }
|
||||||
|
?.let { findInjection(it, Configuration.getInstance().getInjections(JavaLanguageInjectionSupport.JAVA_SUPPORT_ID)) }
|
||||||
|
?.takeIf { injectionInfo ->
|
||||||
|
// Temporary forbid injection for SpEL because of inspections that gives warnings when host language is not Java.
|
||||||
|
injectionInfo.languageId != "SpEL"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun findInjection(element: PsiElement?, injections: List<BaseInjection>): InjectionInfo? {
|
||||||
|
for (injection in injections) {
|
||||||
|
if (injection.acceptsPsiElement(element)) {
|
||||||
|
return InjectionInfo(injection.injectedLanguageId, injection.prefix, injection.suffix)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun isAnalyzeOff(project: Project): Boolean {
|
||||||
|
return Configuration.getProjectInstance(project).advancedConfiguration.dfaOption == Configuration.DfaOption.OFF
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun processAnnotationInjectionInner(annotations: Array<PsiAnnotation>): InjectionInfo? {
|
||||||
|
val id = AnnotationUtilEx.calcAnnotationValue(annotations, "value")
|
||||||
|
val prefix = AnnotationUtilEx.calcAnnotationValue(annotations, "prefix")
|
||||||
|
val suffix = AnnotationUtilEx.calcAnnotationValue(annotations, "suffix")
|
||||||
|
|
||||||
|
return InjectionInfo(id, prefix, suffix)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,509 @@
|
|||||||
|
/*
|
||||||
|
* 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 com.intellij.openapi.fileTypes.PlainTextLanguage
|
||||||
|
import org.intellij.lang.regexp.RegExpLanguage
|
||||||
|
import org.intellij.plugins.intelliLang.Configuration
|
||||||
|
import org.intellij.plugins.intelliLang.inject.config.BaseInjection
|
||||||
|
import org.intellij.plugins.intelliLang.inject.config.InjectionPlace
|
||||||
|
|
||||||
|
class KotlinInjectionTest : AbstractInjectionTest() {
|
||||||
|
fun testInjectionOnJavaPredefinedMethodWithAnnotation() = doInjectionPresentTest(
|
||||||
|
"""
|
||||||
|
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)
|
||||||
|
|
||||||
|
doInjectionPresentTest(
|
||||||
|
"""
|
||||||
|
val stringBuilder = StringBuilder().replace(0, 0, "<caret><html></html>")
|
||||||
|
""",
|
||||||
|
HTMLLanguage.INSTANCE.id,
|
||||||
|
unInjectShouldBePresent = false
|
||||||
|
)
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
Configuration.getInstance().replaceInjections(listOf(), listOf(customInjection), true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun testInjectionWithCommentOnProperty() = doInjectionPresentTest(
|
||||||
|
"""
|
||||||
|
//language=file-reference
|
||||||
|
val test = "<caret>simple"
|
||||||
|
""")
|
||||||
|
|
||||||
|
fun testInjectionWithUsageOnReceiverWithRuntime() = doInjectionPresentTest(
|
||||||
|
"""
|
||||||
|
val test = "<caret>some"
|
||||||
|
fun foo() = test.toRegex()
|
||||||
|
""",
|
||||||
|
languageId = RegExpLanguage.INSTANCE.id, unInjectShouldBePresent = false)
|
||||||
|
|
||||||
|
fun testInjectionWithUsageInParameterWithRuntime() = doInjectionPresentTest(
|
||||||
|
"""
|
||||||
|
val test = "<caret>some"
|
||||||
|
fun foo() = Regex(test)
|
||||||
|
""",
|
||||||
|
languageId = RegExpLanguage.INSTANCE.id, unInjectShouldBePresent = false)
|
||||||
|
|
||||||
|
fun testNoInjectionThoughSeveralAssignmentsWithRuntime() = assertNoInjection(
|
||||||
|
"""
|
||||||
|
val first = "<caret>some"
|
||||||
|
val test = first
|
||||||
|
fun foo() = Regex(test)
|
||||||
|
""")
|
||||||
|
|
||||||
|
fun testInjectionWithMultipleCommentsOnFun() = doInjectionPresentTest(
|
||||||
|
"""
|
||||||
|
// Some comment
|
||||||
|
// Other comment
|
||||||
|
//language=file-reference
|
||||||
|
fun test() = "<caret>simple"
|
||||||
|
""")
|
||||||
|
|
||||||
|
fun testInjectionWithAnnotationOnPropertyWithAnnotation() = doInjectionPresentTest(
|
||||||
|
"""
|
||||||
|
@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"
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
fun testInjectionWithUsageInFunctionWithMarkedParameterWithAnnotation() = doInjectionPresentTest(
|
||||||
|
"""
|
||||||
|
import org.intellij.lang.annotations.Language
|
||||||
|
|
||||||
|
val v = "<caret>some"
|
||||||
|
|
||||||
|
fun foo(@Language("HTML") s: String) {}
|
||||||
|
|
||||||
|
fun other() { foo(v) }
|
||||||
|
""",
|
||||||
|
languageId = HTMLLanguage.INSTANCE.id, unInjectShouldBePresent = false)
|
||||||
|
|
||||||
|
fun testInjectionOfCustomParameterWithAnnotation() = doInjectionPresentTest(
|
||||||
|
"""
|
||||||
|
import org.intellij.lang.annotations.Language
|
||||||
|
|
||||||
|
fun foo(@Language("HTML") s: String) {}
|
||||||
|
|
||||||
|
fun other() { foo("<caret>some") }
|
||||||
|
""",
|
||||||
|
languageId = HTMLLanguage.INSTANCE.id, unInjectShouldBePresent = false)
|
||||||
|
|
||||||
|
fun testInjectionOfCustomParameterInConstructorWithAnnotation() = doInjectionPresentTest(
|
||||||
|
"""
|
||||||
|
import org.intellij.lang.annotations.Language
|
||||||
|
|
||||||
|
class Test(@Language("HTML") val s: String)
|
||||||
|
|
||||||
|
fun other() { Test("<caret>some") }
|
||||||
|
""",
|
||||||
|
languageId = HTMLLanguage.INSTANCE.id, unInjectShouldBePresent = false)
|
||||||
|
|
||||||
|
fun testInjectionOfCustomParameterDefaultCallWithAnnotation() = doInjectionPresentTest(
|
||||||
|
"""
|
||||||
|
import org.intellij.lang.annotations.Language
|
||||||
|
|
||||||
|
fun foo(@Language("HTML") s: String) {}
|
||||||
|
|
||||||
|
fun other() { foo(s = "<caret>some") }
|
||||||
|
""",
|
||||||
|
languageId = HTMLLanguage.INSTANCE.id, unInjectShouldBePresent = false)
|
||||||
|
|
||||||
|
fun testInjectionOfCustomParameterInJavaConstructorWithAnnotationWithAnnotation() = doInjectionPresentTest(
|
||||||
|
"""
|
||||||
|
fun bar() { Test("<caret>some") }
|
||||||
|
""",
|
||||||
|
javaText =
|
||||||
|
"""
|
||||||
|
import org.intellij.lang.annotations.Language;
|
||||||
|
|
||||||
|
public class Test {
|
||||||
|
public Test(@Language("HTML") String str) {}
|
||||||
|
}
|
||||||
|
""",
|
||||||
|
languageId = HTMLLanguage.INSTANCE.id, unInjectShouldBePresent = false
|
||||||
|
)
|
||||||
|
|
||||||
|
fun testInjectionOfCustomParameterJavaWithAnnotation() = doInjectionPresentTest(
|
||||||
|
"""
|
||||||
|
fun bar() { Test.foo("<caret>some") }
|
||||||
|
""",
|
||||||
|
javaText =
|
||||||
|
"""
|
||||||
|
import org.intellij.lang.annotations.Language;
|
||||||
|
|
||||||
|
public class Test {
|
||||||
|
public static void foo(@Language("HTML") String str) {}
|
||||||
|
}
|
||||||
|
""",
|
||||||
|
languageId = HTMLLanguage.INSTANCE.id, unInjectShouldBePresent = false
|
||||||
|
)
|
||||||
|
|
||||||
|
fun testInjectionOnInterpolationWithAnnotation() = doInjectionPresentTest(
|
||||||
|
"""
|
||||||
|
val b = 2
|
||||||
|
|
||||||
|
@org.intellij.lang.annotations.Language("HTML")
|
||||||
|
val test = "<caret>simple${'$'}{b}.kt"
|
||||||
|
""",
|
||||||
|
unInjectShouldBePresent = false,
|
||||||
|
shreds = listOf(
|
||||||
|
ShredInfo(range(0, 6), hostRange=range(1, 7)),
|
||||||
|
ShredInfo(range(6, 21), hostRange=range(11, 14), prefix="missingValue")
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
fun testInjectionOnInterpolatedStringWithComment() = doInjectionPresentTest(
|
||||||
|
"""
|
||||||
|
val some = 42
|
||||||
|
// language=HTML
|
||||||
|
val test = "<ht<caret>ml>${'$'}some</html>"
|
||||||
|
""",
|
||||||
|
languageId = HTMLLanguage.INSTANCE.id, unInjectShouldBePresent = false,
|
||||||
|
shreds = listOf(
|
||||||
|
ShredInfo(range(0, 6), hostRange = range(1, 7)),
|
||||||
|
ShredInfo(range(6, 17), hostRange = range(12, 19), prefix="some"))
|
||||||
|
)
|
||||||
|
|
||||||
|
fun testEditorShortShreadsInInterpolatedInjection() = doInjectionPresentTest(
|
||||||
|
"""
|
||||||
|
val s = 42
|
||||||
|
// language=TEXT
|
||||||
|
val test = "${'$'}s <caret>text ${'$'}s${'$'}{s}${'$'}s text ${'$'}s"
|
||||||
|
""",
|
||||||
|
languageId = PlainTextLanguage.INSTANCE.id, unInjectShouldBePresent = false,
|
||||||
|
shreds = listOf(
|
||||||
|
ShredInfo(range(0, 0), hostRange=range(1, 1)),
|
||||||
|
ShredInfo(range(0, 7), hostRange=range(3, 9), prefix="s"),
|
||||||
|
ShredInfo(range(7, 8), hostRange=range(11, 11), prefix="s"),
|
||||||
|
ShredInfo(range(8, 20), hostRange=range(15, 15), prefix="missingValue"),
|
||||||
|
ShredInfo(range(20, 27), hostRange=range(17, 23), prefix="s"),
|
||||||
|
ShredInfo(range(27, 28), hostRange=range(25, 25), prefix="s")
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
fun testEditorLongShreadsInInterpolatedInjection() = doInjectionPresentTest(
|
||||||
|
"""
|
||||||
|
val s = 42
|
||||||
|
// language=TEXT
|
||||||
|
val test = "${'$'}{s} <caret>text ${'$'}{s}${'$'}s${'$'}{s} text ${'$'}{s}"
|
||||||
|
""",
|
||||||
|
languageId = PlainTextLanguage.INSTANCE.id, unInjectShouldBePresent = false,
|
||||||
|
shreds = listOf(
|
||||||
|
ShredInfo(range(0, 0), hostRange=range(1, 1)),
|
||||||
|
ShredInfo(range(0, 18), hostRange=range(5, 11), prefix="missingValue"),
|
||||||
|
ShredInfo(range(18, 30), hostRange=range(15, 15), prefix="missingValue"),
|
||||||
|
ShredInfo(range(30, 31), hostRange=range(17, 17), prefix="s"),
|
||||||
|
ShredInfo(range(31, 49), hostRange=range(21, 27), prefix="missingValue"),
|
||||||
|
ShredInfo(range(49, 61), hostRange=range(31, 31), prefix="missingValue")
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
fun testEditorShreadsWithEscapingInjection() = doInjectionPresentTest(
|
||||||
|
"""
|
||||||
|
// language=TEXT
|
||||||
|
val test = "\rte<caret>xt\ttext\n\t"
|
||||||
|
""",
|
||||||
|
languageId = PlainTextLanguage.INSTANCE.id, unInjectShouldBePresent = false,
|
||||||
|
shreds = listOf(
|
||||||
|
ShredInfo(range(0, 12), hostRange=range(1, 17))
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
fun testEditorShreadsInInterpolatedWithEscapingInjection() = doInjectionPresentTest(
|
||||||
|
"""
|
||||||
|
val s = 1
|
||||||
|
// language=TEXT
|
||||||
|
val test = "\r${'$'}s te<caret>xt${'$'}s\ttext\n\t"
|
||||||
|
""",
|
||||||
|
languageId = PlainTextLanguage.INSTANCE.id, unInjectShouldBePresent = false,
|
||||||
|
shreds = listOf(
|
||||||
|
ShredInfo(range(0, 1), hostRange=range(1, 3)),
|
||||||
|
ShredInfo(range(1, 7), hostRange=range(5, 10), prefix="s"),
|
||||||
|
ShredInfo(range(7, 15), hostRange=range(12, 22), prefix="s")
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
fun testSuffixPrefixWithAnnotation() = doInjectionPresentTest(
|
||||||
|
"""
|
||||||
|
@org.intellij.lang.annotations.Language("TEXT", prefix = "abc", suffix = "ghi")
|
||||||
|
val test = "<caret>def"
|
||||||
|
""",
|
||||||
|
languageId = PlainTextLanguage.INSTANCE.id, unInjectShouldBePresent = false,
|
||||||
|
shreds = listOf(ShredInfo(range(0, 9), hostRange=range(1, 4), prefix = "abc", suffix = "ghi"))
|
||||||
|
)
|
||||||
|
|
||||||
|
fun testSuffixPrefixInComment() = doInjectionPresentTest(
|
||||||
|
"""
|
||||||
|
// language="TEXT" prefix="abc" suffix=ghi
|
||||||
|
val test = "<caret>def"
|
||||||
|
""",
|
||||||
|
languageId = PlainTextLanguage.INSTANCE.id, unInjectShouldBePresent = false,
|
||||||
|
shreds = listOf(ShredInfo(range(0, 9), hostRange=range(1, 4), prefix = "abc", suffix = "ghi"))
|
||||||
|
)
|
||||||
|
|
||||||
|
fun testJavaAnnotationsPattern() {
|
||||||
|
val customInjection = BaseInjection("java")
|
||||||
|
customInjection.injectedLanguageId = RegExpLanguage.INSTANCE.id
|
||||||
|
val elementPattern = customInjection.compiler.createElementPattern(
|
||||||
|
"""psiMethod().withName("value").withParameters().definedInClass("Matches")""",
|
||||||
|
"temp rule")
|
||||||
|
customInjection.setInjectionPlaces(InjectionPlace(elementPattern, true))
|
||||||
|
|
||||||
|
try {
|
||||||
|
Configuration.getInstance().replaceInjections(listOf(customInjection), listOf(), true)
|
||||||
|
|
||||||
|
doInjectionPresentTest(
|
||||||
|
javaText = """
|
||||||
|
@interface Matches { String value(); }
|
||||||
|
""",
|
||||||
|
text = """
|
||||||
|
@Matches("[A-Z]<caret>[a-z]+")
|
||||||
|
val name = "John"
|
||||||
|
""",
|
||||||
|
languageId = RegExpLanguage.INSTANCE.id,
|
||||||
|
unInjectShouldBePresent = false
|
||||||
|
)
|
||||||
|
|
||||||
|
doInjectionPresentTest(
|
||||||
|
javaText = """
|
||||||
|
@interface Matches { String value(); }
|
||||||
|
""",
|
||||||
|
text = """
|
||||||
|
@Matches(value = "[A-Z]<caret>[a-z]+")
|
||||||
|
val name = "John"
|
||||||
|
""",
|
||||||
|
languageId = RegExpLanguage.INSTANCE.id,
|
||||||
|
unInjectShouldBePresent = false
|
||||||
|
)
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
Configuration.getInstance().replaceInjections(listOf(), listOf(customInjection), true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun testKotlinAnnotationsPattern() {
|
||||||
|
val customInjection = BaseInjection("kotlin")
|
||||||
|
customInjection.injectedLanguageId = RegExpLanguage.INSTANCE.id
|
||||||
|
val elementPattern = customInjection.compiler.createElementPattern(
|
||||||
|
"""kotlinParameter().ofFunction(0, kotlinFunction().withName("Matches").definedInClass("Matches"))""".trimIndent(),
|
||||||
|
"temp rule")
|
||||||
|
customInjection.setInjectionPlaces(InjectionPlace(elementPattern, true))
|
||||||
|
|
||||||
|
try {
|
||||||
|
Configuration.getInstance().replaceInjections(listOf(customInjection), listOf(), true)
|
||||||
|
|
||||||
|
doInjectionPresentTest(
|
||||||
|
text = """
|
||||||
|
annotation class Matches(val pattern: String)
|
||||||
|
|
||||||
|
@Matches("[A-Z]<caret>[a-z]+")
|
||||||
|
val name = "John"
|
||||||
|
""",
|
||||||
|
languageId = RegExpLanguage.INSTANCE.id,
|
||||||
|
unInjectShouldBePresent = false
|
||||||
|
)
|
||||||
|
|
||||||
|
doInjectionPresentTest(
|
||||||
|
text = """
|
||||||
|
annotation class Matches(val pattern: String)
|
||||||
|
|
||||||
|
@Matches(pattern = "[A-Z]<caret>[a-z]+")
|
||||||
|
val name = "John"
|
||||||
|
""",
|
||||||
|
languageId = RegExpLanguage.INSTANCE.id,
|
||||||
|
unInjectShouldBePresent = false
|
||||||
|
)
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
Configuration.getInstance().replaceInjections(listOf(), listOf(customInjection), true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user