IDEA-141739 Devkit: resolve languages in 'plugin.xml' defined in Kotlin classes, objects

GitOrigin-RevId: 0d9a7fb42a01e44c77cfaaf316191ea1d001dd56
This commit is contained in:
Vladislav Rassokhin
2020-01-29 17:23:25 +03:00
committed by intellij-monorepo-bot
parent 31e3bdfe89
commit 3abfa769c4
6 changed files with 160 additions and 31 deletions

View File

@@ -1,4 +1,4 @@
// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package org.jetbrains.idea.devkit.dom.impl;
import com.intellij.codeInsight.completion.CompletionConfidenceEP;
@@ -29,6 +29,7 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.idea.devkit.dom.Extension;
import org.jetbrains.idea.devkit.dom.ExtensionPoint;
import org.jetbrains.uast.*;
import javax.swing.*;
import java.util.*;
@@ -109,7 +110,7 @@ class LanguageResolvingUtil {
languageId = computeConstantReturnValue(language, "getID");
}
if (StringUtil.isEmpty(languageId)) {
return Result.create((LanguageDefinition)null, language);
return Result.create(null, language);
}
String displayName = computeConstantReturnValue(language, "getDisplayName");
@@ -131,14 +132,16 @@ class LanguageResolvingUtil {
}
private static String computeConstantSuperCtorCallParameter(PsiClass languagePsiClass, int index) {
if (languagePsiClass instanceof PsiAnonymousClass) {
return getStringConstantExpression(((PsiAnonymousClass)languagePsiClass).getArgumentList(), index);
UClass languageClass = UastContextKt.toUElement(languagePsiClass, UClass.class);
if (languageClass == null) return null;
if (languageClass instanceof UAnonymousClass) {
return getStringConstantExpression(UastUtils.findContaining(languageClass.getSourcePsi(), UObjectLiteralExpression.class), index);
}
PsiMethod defaultConstructor = null;
for (PsiMethod constructor : languagePsiClass.getConstructors()) {
if (constructor.getParameterList().isEmpty()) {
defaultConstructor = constructor;
UMethod defaultConstructor = null;
for (UMethod method : languageClass.getMethods()) {
if (method.isConstructor() && method.getUastParameters().isEmpty()) {
defaultConstructor = method;
break;
}
}
@@ -146,41 +149,46 @@ class LanguageResolvingUtil {
return null;
}
final PsiCodeBlock body = defaultConstructor.getBody();
if (body == null) {
return null;
}
final PsiStatement[] statements = body.getStatements();
if (statements.length < 1) {
final UExpression body = defaultConstructor.getUastBody();
if (!(body instanceof UBlockExpression)) {
return null;
}
final List<UExpression> expressions = ((UBlockExpression)body).getExpressions();
// super() must be first
PsiStatement statement = statements[0];
if (!(statement instanceof PsiExpressionStatement)) {
UExpression expression = ContainerUtil.getFirstItem(expressions);
if (!(expression instanceof UCallExpression)) {
return null;
}
PsiExpression expression = ((PsiExpressionStatement)statement).getExpression();
if (!(expression instanceof PsiMethodCallExpression)) {
UCallExpression methodCallExpression = (UCallExpression)expression;
if (!isSuperConstructorCall(methodCallExpression)) {
return null;
}
PsiMethodCallExpression methodCallExpression = (PsiMethodCallExpression)expression;
PsiExpressionList expressionList = methodCallExpression.getArgumentList();
return getStringConstantExpression(expressionList, index);
return getStringConstantExpression(methodCallExpression, index);
}
private static boolean isSuperConstructorCall(@Nullable UCallExpression callExpression) {
if (callExpression == null) return false;
UastCallKind kind = callExpression.getKind();
String name = callExpression.getMethodName();
// TODO: Simplify once IDEA-229756 fixed
return kind == UastCallKind.CONSTRUCTOR_CALL && "<init>".equals(name) // Kotlin way
|| kind == UastCallKind.METHOD_CALL && "super".equals(name); // Java way
}
@Nullable
private static String getStringConstantExpression(@Nullable PsiExpressionList expressionList, int index) {
if (expressionList == null) {
private static String getStringConstantExpression(@Nullable UCallExpression callExpression, int index) {
if (callExpression == null) {
return null;
}
final PsiExpression[] argumentExpressions = expressionList.getExpressions();
if (argumentExpressions.length < index + 1) {
UExpression argument = callExpression.getArgumentForParameter(index);
if (argument == null) {
return null;
}
return getStringConstantExpression(argumentExpressions[index]);
return UastUtils.evaluateString(argument);
}
@Nullable

View File

@@ -1,10 +1,13 @@
public class MyLanguage extends com.intellij.lang.Language {
public static final String PREFIX = "My"
public static final String ID = "LanguageID"
public static final String ANONYMOUS_ID = "AnonymousLanguageID"
public static final com.intellij.lang.Language ANONYMOUS_LANGUAGE =
new MySubLanguage("MyAnonymousLanguageID", "MyDisplayName") {};
new MySubLanguage(PREFIX + ANONYMOUS_ID, "MyDisplayName") {};
public MyLanguage() {
super("MyLanguageID");
super(PREFIX + ID);
}
private static class MySubLanguage extends com.intellij.lang.Language {

View File

@@ -0,0 +1,20 @@
@file:Suppress("unused")
class MyLanguage : com.intellij.lang.Language {
companion object {
const val PREFIX = "My"
const val ID = "LanguageID"
const val ANONYMOUS_ID = "AnonymousLanguageID"
val ANONYMOUS_LANGUAGE: com.intellij.lang.Language = object : MySubLanguage(PREFIX + ANONYMOUS_ID, "MyDisplayName") {}
}
@Suppress("ConvertSecondaryConstructorToPrimary")
constructor() : super(PREFIX + ID)
private open class MySubLanguage(id: String?, private val myName: String) : com.intellij.lang.Language(id!!) {
override fun getDisplayName(): String = myName
}
abstract class AbstractLanguage protected constructor() : com.intellij.lang.Language("AbstractLanguageIDMustNotBeVisible")
}

View File

@@ -0,0 +1,4 @@
class MyLanguageAttributeEPBean {
@com.intellij.util.xmlb.annotations.Attribute("language")
lateinit var language: String
}

View File

@@ -0,0 +1,19 @@
<idea-plugin>
<id>com.intellij.myPlugin</id>
<vendor>JetBrains</vendor>
<version>1.0</version>
<extensionPoints>
<extensionPoint name="myLanguageEP" beanClass="MyLanguageAttributeEPBean"/>
</extensionPoints>
<extensions defaultExtensionNs="com.intellij.myPlugin">
<myLanguageEP language="My<caret>LanguageID"/>
<myLanguageEP language="MyAnonymousLanguageID"/>
<myLanguageEP language="<error descr="Cannot resolve language with id ''INVALID_VALUE''">INVALID_VALUE</error>"/>
<myLanguageEP language="<error descr="Cannot resolve language with id ''AbstractLanguageIDMustNotBeVisible''">AbstractLanguageIDMustNotBeVisible</error>"/>
</extensions>
</idea-plugin>

View File

@@ -0,0 +1,75 @@
// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package org.jetbrains.idea.devkit.kotlin.codeInsight
import com.intellij.codeInsight.completion.CompletionContributorEP
import com.intellij.codeInsight.completion.CompletionType
import com.intellij.codeInsight.lookup.LookupElement
import com.intellij.codeInsight.lookup.LookupElementPresentation
import com.intellij.testFramework.TestDataPath
import com.intellij.testFramework.builders.JavaModuleFixtureBuilder
import com.intellij.testFramework.fixtures.JavaCodeInsightFixtureTestCase
import com.intellij.util.PathUtil
import com.intellij.util.xmlb.annotations.XCollection
import groovy.transform.CompileStatic
import org.jetbrains.idea.devkit.inspections.PluginXmlDomInspection
import org.jetbrains.idea.devkit.kotlin.DevkitKtTestsUtil
@TestDataPath("\$CONTENT_ROOT/testData/codeInsight")
@CompileStatic
class KtPluginXmlFunctionalTest extends JavaCodeInsightFixtureTestCase {
@Override
protected void setUp() throws Exception {
super.setUp()
myFixture.enableInspections(PluginXmlDomInspection.class)
}
@Override
protected String getBasePath() {
return DevkitKtTestsUtil.TESTDATA_PATH + "codeInsight"
}
@Override
protected void tuneFixture(JavaModuleFixtureBuilder moduleBuilder) throws Exception {
String pathForClass = PathUtil.getJarPathForClass(XCollection.class)
moduleBuilder.addLibrary("util", pathForClass)
String langApiJar = PathUtil.getJarPathForClass(CompletionContributorEP.class)
moduleBuilder.addLibrary("lang-api", langApiJar)
}
private void doHighlightingTest(String... filePaths) {
myFixture.testHighlighting(true, false, false, filePaths)
}
void testLanguageAttributeHighlighting() {
configureLanguageAttributeTest()
doHighlightingTest("languageAttribute.xml", "MyLanguageAttributeEPBean.kt")
}
void testLanguageAttributeCompletion() {
configureLanguageAttributeTest()
myFixture.allowTreeAccessForFile(myFixture.copyFileToProject("MyLanguageAttributeEPBean.kt"))
myFixture.configureByFile("languageAttribute.xml")
def lookupElements = myFixture.complete(CompletionType.BASIC).sort { it.lookupString }
assertEquals(2, lookupElements.length)
assertLookupElement(lookupElements[0], "MyAnonymousLanguageID", "MyLanguage.MySubLanguage")
assertLookupElement(lookupElements[1], "MyLanguageID", "MyLanguage")
}
private static void assertLookupElement(LookupElement element, String lookupString, String typeText) {
def presentation = new LookupElementPresentation()
element.renderElement(presentation)
assertEquals(lookupString, presentation.itemText)
assertEquals(typeText, presentation.typeText)
}
private void configureLanguageAttributeTest() {
myFixture.addClass("package com.intellij.lang; " +
"public class Language { " +
" protected Language(String id) {}" +
"}")
myFixture.allowTreeAccessForFile(myFixture.copyFileToProject("MyLanguage.kt"))
}
}