First version of Kotlin patterns in injection

#KT-8088 In Progress
This commit is contained in:
Nikolay Krasko
2016-04-19 20:01:46 +03:00
parent a12faf5baf
commit c6e48d8e59
6 changed files with 192 additions and 21 deletions
@@ -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>
+1
View File
@@ -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) {