First version of Kotlin patterns in injection
#KT-8088 In Progress
This commit is contained in:
@@ -0,0 +1,9 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<component name="LanguageInjectionConfiguration">
|
||||||
|
<injection language="RegExp" injector-id="kotlin">
|
||||||
|
<display-name>Kotlin</display-name>
|
||||||
|
<place><![CDATA[
|
||||||
|
psiParameter().ofMethod(0, psiMethod().withName("Regex").withParameters("kotlin.String").definedInClass("kotlin.text.Regex"))
|
||||||
|
]]></place>
|
||||||
|
</injection>
|
||||||
|
</component>
|
||||||
@@ -5,5 +5,6 @@
|
|||||||
|
|
||||||
<extensions defaultExtensionNs="org.intellij.intelliLang">
|
<extensions defaultExtensionNs="org.intellij.intelliLang">
|
||||||
<languageSupport implementation="org.jetbrains.kotlin.idea.injection.KotlinLanguageInjectionSupport"/>
|
<languageSupport implementation="org.jetbrains.kotlin.idea.injection.KotlinLanguageInjectionSupport"/>
|
||||||
|
<injectionConfig config="injection/kotlinInjections.xml"/>
|
||||||
</extensions>
|
</extensions>
|
||||||
</idea-plugin>
|
</idea-plugin>
|
||||||
|
|||||||
@@ -24,7 +24,6 @@ import com.intellij.psi.*
|
|||||||
import com.intellij.psi.search.GlobalSearchScope
|
import com.intellij.psi.search.GlobalSearchScope
|
||||||
import com.intellij.psi.util.PsiTreeUtil
|
import com.intellij.psi.util.PsiTreeUtil
|
||||||
import com.intellij.psi.util.PsiTreeUtil.getDeepestLast
|
import com.intellij.psi.util.PsiTreeUtil.getDeepestLast
|
||||||
import com.intellij.util.ArrayUtil
|
|
||||||
import com.intellij.util.Processor
|
import com.intellij.util.Processor
|
||||||
import org.intellij.plugins.intelliLang.Configuration
|
import org.intellij.plugins.intelliLang.Configuration
|
||||||
import org.intellij.plugins.intelliLang.inject.AbstractLanguageInjectionSupport
|
import org.intellij.plugins.intelliLang.inject.AbstractLanguageInjectionSupport
|
||||||
@@ -32,6 +31,7 @@ import org.intellij.plugins.intelliLang.inject.InjectLanguageAction
|
|||||||
import org.intellij.plugins.intelliLang.inject.InjectedLanguage
|
import org.intellij.plugins.intelliLang.inject.InjectedLanguage
|
||||||
import org.intellij.plugins.intelliLang.inject.TemporaryPlacesRegistry
|
import org.intellij.plugins.intelliLang.inject.TemporaryPlacesRegistry
|
||||||
import org.jetbrains.annotations.NonNls
|
import org.jetbrains.annotations.NonNls
|
||||||
|
import org.jetbrains.kotlin.idea.patterns.KotlinPatterns
|
||||||
import org.jetbrains.kotlin.idea.util.addAnnotation
|
import org.jetbrains.kotlin.idea.util.addAnnotation
|
||||||
import org.jetbrains.kotlin.idea.util.application.executeWriteCommand
|
import org.jetbrains.kotlin.idea.util.application.executeWriteCommand
|
||||||
import org.jetbrains.kotlin.idea.util.findAnnotation
|
import org.jetbrains.kotlin.idea.util.findAnnotation
|
||||||
@@ -48,10 +48,12 @@ val LINE_LANGUAGE_REGEXP_COMMENT = Regex("//\\s*language=[\\w-]+")
|
|||||||
class KotlinLanguageInjectionSupport : AbstractLanguageInjectionSupport() {
|
class KotlinLanguageInjectionSupport : AbstractLanguageInjectionSupport() {
|
||||||
override fun getId(): String = KOTLIN_SUPPORT_ID
|
override fun getId(): String = KOTLIN_SUPPORT_ID
|
||||||
|
|
||||||
override fun getPatternClasses() = ArrayUtil.EMPTY_CLASS_ARRAY
|
override fun getPatternClasses() = arrayOf(KotlinPatterns::class.java)
|
||||||
|
|
||||||
override fun isApplicableTo(host: PsiLanguageInjectionHost?) = host is KtElement
|
override fun isApplicableTo(host: PsiLanguageInjectionHost?) = host is KtElement
|
||||||
|
|
||||||
|
override fun useDefaultInjector(host: PsiLanguageInjectionHost?): Boolean = false
|
||||||
|
|
||||||
override fun addInjectionInPlace(language: Language?, host: PsiLanguageInjectionHost?): Boolean {
|
override fun addInjectionInPlace(language: Language?, host: PsiLanguageInjectionHost?): Boolean {
|
||||||
if (language == null || host == null) return false
|
if (language == null || host == null) return false
|
||||||
|
|
||||||
|
|||||||
@@ -26,10 +26,7 @@ import com.intellij.psi.util.PsiTreeUtil
|
|||||||
import org.intellij.plugins.intelliLang.Configuration
|
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.intellij.plugins.intelliLang.inject.java.JavaLanguageInjectionSupport
|
||||||
import org.jetbrains.kotlin.psi.KtCallExpression
|
import org.jetbrains.kotlin.psi.*
|
||||||
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 {
|
||||||
@@ -66,19 +63,50 @@ class KotlinLanguageInjector : LanguageInjector {
|
|||||||
for (reference in callee.references) {
|
for (reference in callee.references) {
|
||||||
ProgressManager.checkCanceled()
|
ProgressManager.checkCanceled()
|
||||||
|
|
||||||
val javaMethod = reference.resolve()
|
val resolvedTo = reference.resolve()
|
||||||
if (javaMethod !is PsiMethod) continue
|
if (resolvedTo is PsiMethod) {
|
||||||
|
val injectionForJavaMethod = injectionForJavaMethod(argument, resolvedTo)
|
||||||
val argumentIndex = (argument.parent as KtValueArgumentList).arguments.indexOf(argument)
|
if (injectionForJavaMethod != null) {
|
||||||
val psiParameter = javaMethod.parameterList.parameters.getOrNull(argumentIndex) ?: continue
|
return injectionForJavaMethod
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else if (resolvedTo is KtFunction) {
|
||||||
|
val injectionForJavaMethod = injectionForKotlinCall(argument, resolvedTo)
|
||||||
|
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 injections = Configuration.getInstance().getInjections(JavaLanguageInjectionSupport.JAVA_SUPPORT_ID)
|
||||||
|
|
||||||
|
for (injection in injections) {
|
||||||
|
if (injection.acceptsPsiElement(psiParameter)) {
|
||||||
|
// ?? if (!processXmlInjections(injection, owner, method, paramIndex)) {
|
||||||
|
return InjectionInfo(injection.injectedLanguageId, injection.prefix, injection.suffix)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun injectionForKotlinCall(argument: KtValueArgument, ktFunction: KtFunction): InjectionInfo? {
|
||||||
|
val argumentIndex = (argument.parent as KtValueArgumentList).arguments.indexOf(argument)
|
||||||
|
val ktParameter = ktFunction.valueParameters.getOrNull(argumentIndex) ?: return null
|
||||||
|
|
||||||
|
val injections = Configuration.getInstance().getInjections(KOTLIN_SUPPORT_ID)
|
||||||
|
|
||||||
|
for (injection in injections) {
|
||||||
|
if (injection.acceptsPsiElement(ktParameter)) {
|
||||||
|
return InjectionInfo(injection.injectedLanguageId, injection.prefix, injection.suffix)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return null
|
return null
|
||||||
|
|||||||
@@ -0,0 +1,121 @@
|
|||||||
|
/*
|
||||||
|
* 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.patterns
|
||||||
|
|
||||||
|
import com.intellij.patterns.*
|
||||||
|
import com.intellij.util.PairProcessor
|
||||||
|
import com.intellij.util.ProcessingContext
|
||||||
|
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
|
||||||
|
import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptorIfAny
|
||||||
|
import org.jetbrains.kotlin.psi.KtFile
|
||||||
|
import org.jetbrains.kotlin.psi.KtFunction
|
||||||
|
import org.jetbrains.kotlin.psi.KtParameter
|
||||||
|
import org.jetbrains.kotlin.psi.KtTypeReference
|
||||||
|
import org.jetbrains.kotlin.psi.psiUtil.containingClassOrObject
|
||||||
|
import org.jetbrains.kotlin.renderer.DescriptorRenderer
|
||||||
|
|
||||||
|
// Methods in this class are used through reflection
|
||||||
|
@Suppress("unused")
|
||||||
|
object KotlinPatterns: StandardPatterns() {
|
||||||
|
@JvmStatic fun psiParameter() = KtParameterPattern()
|
||||||
|
@JvmStatic fun psiMethod() = KotlinFunctionPattern()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Methods in this class are used through reflection during pattern construction
|
||||||
|
@Suppress("unused")
|
||||||
|
open class KotlinFunctionPattern : PsiElementPattern<KtFunction, KotlinFunctionPattern>(KtFunction::class.java) {
|
||||||
|
fun withParameters(vararg parameterTypes: String): KotlinFunctionPattern {
|
||||||
|
return withPatternCondition("kotlinFunctionPattern-withParameters") { function, context ->
|
||||||
|
if (function.valueParameters.size != parameterTypes.size) return@withPatternCondition false
|
||||||
|
|
||||||
|
val descriptor = function.resolveToDescriptorIfAny() as? FunctionDescriptor ?: return@withPatternCondition false
|
||||||
|
val valueParameters = descriptor.valueParameters
|
||||||
|
|
||||||
|
if (valueParameters.size != parameterTypes.size) return@withPatternCondition false
|
||||||
|
for (i in 0..valueParameters.size - 1) {
|
||||||
|
val expectedTypeString = parameterTypes[i]
|
||||||
|
val actualParameterDescriptor = valueParameters[i]
|
||||||
|
|
||||||
|
if (DescriptorRenderer.FQ_NAMES_IN_TYPES.renderType(actualParameterDescriptor.type) != expectedTypeString) {
|
||||||
|
return@withPatternCondition false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun withReceiver(receiverFqName: String): KotlinFunctionPattern {
|
||||||
|
return withPatternCondition("kotlinFunctionPattern-withReceiver") { function, context ->
|
||||||
|
if (function.receiverTypeReference == null) return@withPatternCondition false
|
||||||
|
if (receiverFqName == "?") return@withPatternCondition true
|
||||||
|
|
||||||
|
val descriptor = function.resolveToDescriptorIfAny() as? FunctionDescriptor ?: return@withPatternCondition false
|
||||||
|
val receiver = descriptor.extensionReceiverParameter ?: return@withPatternCondition false
|
||||||
|
|
||||||
|
DescriptorRenderer.FQ_NAMES_IN_TYPES.renderType(receiver.type) == receiverFqName
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun definedInClass(fqName: String): KotlinFunctionPattern {
|
||||||
|
return withPatternCondition("kotlinFunctionPattern-definedInClass") { function, context ->
|
||||||
|
if (function.parent is KtFile) return@withPatternCondition false
|
||||||
|
|
||||||
|
function.containingClassOrObject?.fqName?.asString() == fqName
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun definedInPackage(packageFqName: String): KotlinFunctionPattern {
|
||||||
|
return withPatternCondition("kotlinFunctionPattern-definedInPackage") { function, context ->
|
||||||
|
if (function.parent !is KtFile) return@withPatternCondition false
|
||||||
|
|
||||||
|
function.getContainingKtFile().packageFqName.asString() == packageFqName
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun withPatternCondition(debugName: String, condition: (KtFunction, ProcessingContext?) -> Boolean): KotlinFunctionPattern {
|
||||||
|
return with(object: PatternCondition<KtFunction>(debugName) {
|
||||||
|
override fun accepts(function: KtFunction, context: ProcessingContext?): Boolean {
|
||||||
|
return condition(function, context)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Methods in this class are used through reflection during pattern construction
|
||||||
|
@Suppress("unused")
|
||||||
|
class KtParameterPattern : PsiElementPattern<KtParameter, KtParameterPattern>(KtParameter::class.java) {
|
||||||
|
fun ofMethod(index: Int, pattern: ElementPattern<Any>): KtParameterPattern {
|
||||||
|
return with(object : PatternConditionPlus<KtParameter, KtFunction>("KtParameterPattern-ofMethod", pattern) {
|
||||||
|
override fun processValues(ktParameter: KtParameter,
|
||||||
|
context: ProcessingContext,
|
||||||
|
processor: PairProcessor<KtFunction, ProcessingContext>): Boolean {
|
||||||
|
return processor.process(ktParameter.ownerFunction, context)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun accepts(ktParameter: KtParameter, context: ProcessingContext): Boolean {
|
||||||
|
val ktFunction = ktParameter.ownerFunction ?: return false
|
||||||
|
|
||||||
|
val parameters = ktFunction.valueParameters
|
||||||
|
if (index < 0 || index >= parameters.size || ktParameter != parameters[index]) return false
|
||||||
|
|
||||||
|
return super.accepts(ktParameter, context)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -31,6 +31,7 @@ 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 org.jetbrains.kotlin.idea.test.KotlinLightProjectDescriptor
|
import org.jetbrains.kotlin.idea.test.KotlinLightProjectDescriptor
|
||||||
|
import org.jetbrains.kotlin.idea.test.KotlinWithJdkAndRuntimeLightProjectDescriptor
|
||||||
|
|
||||||
class KotlinInjectionTest : KotlinLightCodeInsightFixtureTestCase() {
|
class KotlinInjectionTest : KotlinLightCodeInsightFixtureTestCase() {
|
||||||
fun testInjectUnInjectOnSimpleString() {
|
fun testInjectUnInjectOnSimpleString() {
|
||||||
@@ -57,6 +58,14 @@ class KotlinInjectionTest : KotlinLightCodeInsightFixtureTestCase() {
|
|||||||
unInjectShouldBePresent = false
|
unInjectShouldBePresent = false
|
||||||
)
|
)
|
||||||
|
|
||||||
|
fun testInjectionOnKotlinPredefinedMethodWithRuntime() = testInjectionPresent(
|
||||||
|
"""
|
||||||
|
|val test1 = kotlin.text.Regex("<caret>some")
|
||||||
|
""",
|
||||||
|
RegExpLanguage.INSTANCE.id,
|
||||||
|
unInjectShouldBePresent = false
|
||||||
|
)
|
||||||
|
|
||||||
fun testInjectionOnJavaCustomInjectionWithAnnotation() {
|
fun testInjectionOnJavaCustomInjectionWithAnnotation() {
|
||||||
val customInjection = BaseInjection("java")
|
val customInjection = BaseInjection("java")
|
||||||
customInjection.injectedLanguageId = HTMLLanguage.INSTANCE.id
|
customInjection.injectedLanguageId = HTMLLanguage.INSTANCE.id
|
||||||
@@ -285,11 +294,12 @@ class KotlinInjectionTest : KotlinLightCodeInsightFixtureTestCase() {
|
|||||||
)
|
)
|
||||||
|
|
||||||
override fun getProjectDescriptor(): LightProjectDescriptor {
|
override fun getProjectDescriptor(): LightProjectDescriptor {
|
||||||
if (getTestName(true).endsWith("WithAnnotation")) {
|
val testName = getTestName(true)
|
||||||
return KotlinLightProjectDescriptor.INSTANCE
|
return when {
|
||||||
|
testName.endsWith("WithAnnotation") -> KotlinLightProjectDescriptor.INSTANCE
|
||||||
|
testName.endsWith("WithRuntime") -> KotlinWithJdkAndRuntimeLightProjectDescriptor.INSTANCE
|
||||||
|
else -> JAVA_LATEST
|
||||||
}
|
}
|
||||||
|
|
||||||
return JAVA_LATEST
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun testInjectionPresent(text: String, languageId: String? = null, unInjectShouldBePresent: Boolean = true) {
|
private fun testInjectionPresent(text: String, languageId: String? = null, unInjectShouldBePresent: Boolean = true) {
|
||||||
|
|||||||
Reference in New Issue
Block a user