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">
|
||||
<languageSupport implementation="org.jetbrains.kotlin.idea.injection.KotlinLanguageInjectionSupport"/>
|
||||
<injectionConfig config="injection/kotlinInjections.xml"/>
|
||||
</extensions>
|
||||
</idea-plugin>
|
||||
|
||||
@@ -24,7 +24,6 @@ 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
|
||||
@@ -32,6 +31,7 @@ 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.patterns.KotlinPatterns
|
||||
import org.jetbrains.kotlin.idea.util.addAnnotation
|
||||
import org.jetbrains.kotlin.idea.util.application.executeWriteCommand
|
||||
import org.jetbrains.kotlin.idea.util.findAnnotation
|
||||
@@ -48,10 +48,12 @@ 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 getPatternClasses() = arrayOf(KotlinPatterns::class.java)
|
||||
|
||||
override fun isApplicableTo(host: PsiLanguageInjectionHost?) = host is KtElement
|
||||
|
||||
override fun useDefaultInjector(host: PsiLanguageInjectionHost?): Boolean = false
|
||||
|
||||
override fun addInjectionInPlace(language: Language?, host: PsiLanguageInjectionHost?): Boolean {
|
||||
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.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.KtValueArgument
|
||||
import org.jetbrains.kotlin.psi.KtValueArgumentList
|
||||
import org.jetbrains.kotlin.psi.*
|
||||
import java.util.*
|
||||
|
||||
class KotlinLanguageInjector : LanguageInjector {
|
||||
@@ -66,19 +63,50 @@ class KotlinLanguageInjector : LanguageInjector {
|
||||
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)
|
||||
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)
|
||||
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
|
||||
|
||||
@@ -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.jetbrains.kotlin.idea.test.KotlinLightCodeInsightFixtureTestCase
|
||||
import org.jetbrains.kotlin.idea.test.KotlinLightProjectDescriptor
|
||||
import org.jetbrains.kotlin.idea.test.KotlinWithJdkAndRuntimeLightProjectDescriptor
|
||||
|
||||
class KotlinInjectionTest : KotlinLightCodeInsightFixtureTestCase() {
|
||||
fun testInjectUnInjectOnSimpleString() {
|
||||
@@ -57,6 +58,14 @@ class KotlinInjectionTest : KotlinLightCodeInsightFixtureTestCase() {
|
||||
unInjectShouldBePresent = false
|
||||
)
|
||||
|
||||
fun testInjectionOnKotlinPredefinedMethodWithRuntime() = testInjectionPresent(
|
||||
"""
|
||||
|val test1 = kotlin.text.Regex("<caret>some")
|
||||
""",
|
||||
RegExpLanguage.INSTANCE.id,
|
||||
unInjectShouldBePresent = false
|
||||
)
|
||||
|
||||
fun testInjectionOnJavaCustomInjectionWithAnnotation() {
|
||||
val customInjection = BaseInjection("java")
|
||||
customInjection.injectedLanguageId = HTMLLanguage.INSTANCE.id
|
||||
@@ -285,11 +294,12 @@ class KotlinInjectionTest : KotlinLightCodeInsightFixtureTestCase() {
|
||||
)
|
||||
|
||||
override fun getProjectDescriptor(): LightProjectDescriptor {
|
||||
if (getTestName(true).endsWith("WithAnnotation")) {
|
||||
return KotlinLightProjectDescriptor.INSTANCE
|
||||
val testName = getTestName(true)
|
||||
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) {
|
||||
|
||||
Reference in New Issue
Block a user