kotlin support

GitOrigin-RevId: 2c7a580713b05e4bf885cc9d5dd229b35c6f589d
This commit is contained in:
adelf
2021-06-03 21:24:08 +03:00
committed by intellij-monorepo-bot
parent 5dc2eefe89
commit d9498601b2
11 changed files with 424 additions and 70 deletions
@@ -0,0 +1,25 @@
package ru.adelf.idea.dotenv.java;
import org.jetbrains.annotations.Nullable;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
public class JavaEnvironmentClasses {
public static boolean isDirectMethodCall(String methodName) {
return methodName.equals("getenv") || methodName.equals("getEnv");
}
@Nullable
public static List<String> getClassNames(String methodName) {
switch (methodName) {
case "get":
return Arrays.asList("Dotenv", "DotEnv");
case "getProperty":
return Collections.singletonList("System");
}
return null;
}
}
@@ -1,18 +1,10 @@
package ru.adelf.idea.dotenv.java;
import com.intellij.psi.*;
import com.jetbrains.php.lang.psi.elements.ArrayAccessExpression;
import com.jetbrains.php.lang.psi.elements.PhpPsiElement;
import com.jetbrains.php.lang.psi.elements.Variable;
import org.jetbrains.annotations.Nullable;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
class JavaPsiHelper {
public static final List<String> ARRAY_NAMES = Arrays.asList("_ENV", "_SERVER");
/**
* Checks that this element environment string
*
@@ -52,11 +44,11 @@ class JavaPsiHelper {
String methodName = nameElement.getText();
if (isDirectMethodCall(methodName)) {
if (JavaEnvironmentClasses.isDirectMethodCall(methodName)) {
return true;
}
List<String> classNames = getClassNames(methodName);
List<String> classNames = JavaEnvironmentClasses.getClassNames(methodName);
if (classNames == null) {
return false;
@@ -74,64 +66,4 @@ class JavaPsiHelper {
return false;
}
private static boolean isDirectMethodCall(String methodName) {
return methodName.equals("getenv") || methodName.equals("getEnv");
}
@Nullable
private static List<String> getClassNames(String methodName) {
switch (methodName) {
case "get":
return Arrays.asList("Dotenv", "DotEnv");
case "getProperty":
return Collections.singletonList("System");
}
return null;
}
/**
* Checks whether this array access is environment call
*
* @param arrayAccess Checking array
* @return true if condition filled
*/
static boolean isEnvArrayCall(ArrayAccessExpression arrayAccess) {
PhpPsiElement variable = arrayAccess.getValue();
if (!(variable instanceof Variable)) return false;
return (variable.getName() != null && ARRAY_NAMES.contains(variable.getName()));
}
/* @SuppressWarnings("SameParameterValue")
private static boolean isFunctionParameter(PsiElement psiElement, int parameterIndex, List<String> functions) {
PsiElement variableContext = psiElement.getContext();
if(!(variableContext instanceof ParameterList)) {
return false;
} else {
ParameterList parameterList = (ParameterList) variableContext;
PsiElement context = parameterList.getContext();
if(!(context instanceof FunctionReference)) {
return false;
} else {
FunctionReference methodReference = (FunctionReference) context;
String name = methodReference.getName();
return (name != null && functions.contains(name) && getParameterIndex(parameterList, psiElement) == parameterIndex);
}
}
}*/
/* private static int getParameterIndex(ParameterList parameterList, PsiElement parameter) {
PsiElement[] parameters = parameterList.getParameters();
for(int i = 0; i < parameters.length; i = i + 1) {
if(parameters[i].equals(parameter)) {
return i;
}
}
return -1;
}*/
}
@@ -0,0 +1,25 @@
package ru.adelf.idea.dotenv.kotlin;
import com.intellij.codeInsight.completion.CompletionConfidence;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.util.ThreeState;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.kotlin.psi.KtLiteralStringTemplateEntry;
public class KotlinCompletionConfidence extends CompletionConfidence {
@NotNull
@Override
public ThreeState shouldSkipAutopopup(@NotNull PsiElement contextElement, @NotNull PsiFile psiFile, int offset) {
PsiElement literal = contextElement.getContext();
if (!(literal instanceof KtLiteralStringTemplateEntry)) {
return ThreeState.UNSURE;
}
if (KotlinPsiHelper.isEnvStringLiteral((KtLiteralStringTemplateEntry) literal)) {
return ThreeState.NO;
}
return ThreeState.UNSURE;
}
}
@@ -0,0 +1,70 @@
package ru.adelf.idea.dotenv.kotlin;
import com.intellij.codeInsight.completion.CompletionParameters;
import com.intellij.codeInsight.completion.CompletionProvider;
import com.intellij.codeInsight.completion.CompletionResultSet;
import com.intellij.codeInsight.completion.CompletionType;
import com.intellij.codeInsight.navigation.actions.GotoDeclarationHandler;
import com.intellij.openapi.actionSystem.DataContext;
import com.intellij.openapi.editor.Editor;
import com.intellij.patterns.PlatformPatterns;
import com.intellij.psi.PsiElement;
import com.intellij.util.ProcessingContext;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.kotlin.psi.KtLiteralStringTemplateEntry;
import ru.adelf.idea.dotenv.api.EnvironmentVariablesApi;
import ru.adelf.idea.dotenv.common.BaseEnvCompletionProvider;
public class KotlinEnvCompletionContributor extends BaseEnvCompletionProvider implements GotoDeclarationHandler {
public KotlinEnvCompletionContributor() {
extend(CompletionType.BASIC, PlatformPatterns.psiElement().withParent(KtLiteralStringTemplateEntry.class), new CompletionProvider<CompletionParameters>() {
@Override
protected void addCompletions(@NotNull CompletionParameters completionParameters, @NotNull ProcessingContext processingContext, @NotNull CompletionResultSet completionResultSet) {
PsiElement psiElement = completionParameters.getOriginalPosition();
if (psiElement == null || getStringLiteral(psiElement) == null) {
return;
}
fillCompletionResultSet(completionResultSet, psiElement.getProject());
}
});
}
@Nullable
@Override
public PsiElement[] getGotoDeclarationTargets(@Nullable PsiElement psiElement, int i, Editor editor) {
if (psiElement == null) {
return PsiElement.EMPTY_ARRAY;
}
KtLiteralStringTemplateEntry stringLiteral = getStringLiteral(psiElement);
if (stringLiteral == null) {
return PsiElement.EMPTY_ARRAY;
}
return EnvironmentVariablesApi.getKeyDeclarations(psiElement.getProject(), stringLiteral.getText());
}
@Nullable
private KtLiteralStringTemplateEntry getStringLiteral(@NotNull PsiElement psiElement) {
PsiElement parent = psiElement.getParent();
if (!(parent instanceof KtLiteralStringTemplateEntry)) {
return null;
}
if (!KotlinPsiHelper.isEnvStringLiteral((KtLiteralStringTemplateEntry) parent)) {
return null;
}
return (KtLiteralStringTemplateEntry) parent;
}
@Nullable
@Override
public String getActionText(@NotNull DataContext dataContext) {
return null;
}
}
@@ -0,0 +1,33 @@
package ru.adelf.idea.dotenv.kotlin;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.kotlin.psi.KtArrayAccessExpression;
import org.jetbrains.kotlin.psi.KtCallExpression;
import org.jetbrains.kotlin.psi.KtTreeVisitor;
import ru.adelf.idea.dotenv.models.KeyUsagePsiElement;
import java.util.Set;
class KotlinEnvironmentCallsVisitor extends KtTreeVisitor<Set<KeyUsagePsiElement>> {
@Override
public Void visitCallExpression(@NotNull KtCallExpression expression, Set<KeyUsagePsiElement> data) {
KeyUsagePsiElement keyUsage = KotlinPsiHelper.getKeyUsageFromCall(expression);
if (keyUsage != null) {
data.add(keyUsage);
}
return super.visitCallExpression(expression, data);
}
@Override
public Void visitArrayAccessExpression(@NotNull KtArrayAccessExpression expression, Set<KeyUsagePsiElement> data) {
KeyUsagePsiElement keyUsage = KotlinPsiHelper.getKeyUsageFromArrayAccess(expression);
if (keyUsage != null) {
data.add(keyUsage);
}
return super.visitArrayAccessExpression(expression, data);
}
}
@@ -0,0 +1,36 @@
package ru.adelf.idea.dotenv.kotlin;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiFile;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.kotlin.idea.KotlinFileType;
import org.jetbrains.kotlin.psi.KtFile;
import ru.adelf.idea.dotenv.api.EnvironmentVariablesUsagesProvider;
import ru.adelf.idea.dotenv.models.KeyUsagePsiElement;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
public class KotlinEnvironmentVariablesUsagesProvider implements EnvironmentVariablesUsagesProvider {
@Override
public boolean acceptFile(VirtualFile file) {
return file.getFileType().equals(KotlinFileType.INSTANCE);
}
@NotNull
@Override
public Collection<KeyUsagePsiElement> getUsages(PsiFile psiFile) {
if (psiFile instanceof KtFile) {
Set<KeyUsagePsiElement> result = new HashSet<>();
KotlinEnvironmentCallsVisitor visitor = new KotlinEnvironmentCallsVisitor();
((KtFile) psiFile).acceptChildren(visitor, result);
return result;
}
return Collections.emptyList();
}
}
@@ -0,0 +1,178 @@
package ru.adelf.idea.dotenv.kotlin;
import com.intellij.psi.PsiClass;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiMethod;
import com.intellij.psi.util.PsiTreeUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.kotlin.psi.*;
import ru.adelf.idea.dotenv.java.JavaEnvironmentClasses;
import ru.adelf.idea.dotenv.models.KeyUsagePsiElement;
import java.util.Arrays;
import java.util.List;
class KotlinPsiHelper {
/**
* Checks that this element environment string
*
* @param literal Checking psi element
*/
static boolean isEnvStringLiteral(KtLiteralStringTemplateEntry literal) {
if (!(literal.getParent() instanceof KtStringTemplateExpression)) {
return false;
}
PsiElement parent = literal.getParent().getParent();
if (parent instanceof KtValueArgument) {
return isMethodCallLiteral((KtValueArgument) parent);
}
if (parent instanceof KtContainerNode) {
return isArrayAccessLiteral((KtContainerNode) parent);
}
return false;
}
private static boolean isMethodCallLiteral(KtValueArgument valueArgument) {
PsiElement valueArgumentList = valueArgument.getParent();
if (!(valueArgumentList instanceof KtValueArgumentList)) {
return false;
}
if (((KtValueArgumentList) valueArgumentList).getArguments().get(0) != valueArgument) {
return false;
}
PsiElement methodCall = valueArgumentList.getParent();
if (!(methodCall instanceof KtCallExpression)) return false;
return isEnvMethodCall((KtCallExpression) methodCall);
}
private static boolean isArrayAccessLiteral(KtContainerNode containerNode) {
if (!(containerNode.getParent() instanceof KtArrayAccessExpression)) {
return false;
}
return isEnvArrayAccess((KtArrayAccessExpression) containerNode.getParent());
}
/**
* Checks whether this function reference is reference for env functions, like env or getenv
*
* @param methodCallExpression Checking reference
* @return true if condition filled
*/
static boolean isEnvMethodCall(KtCallExpression methodCallExpression) {
PsiElement nameElement = methodCallExpression.getCalleeExpression();
if (!(nameElement instanceof KtNameReferenceExpression)) {
return false;
}
String methodName = ((KtNameReferenceExpression) nameElement).getReferencedName();
if (JavaEnvironmentClasses.isDirectMethodCall(methodName)) {
return true;
}
List<String> classNames = JavaEnvironmentClasses.getClassNames(methodName);
if (classNames == null) {
return false;
}
return checkReferences(methodCallExpression.getCalleeExpression(), classNames);
}
@Nullable
static KeyUsagePsiElement getKeyUsageFromCall(@NotNull KtCallExpression expression) {
if (!isEnvMethodCall(expression)) {
return null;
}
KtValueArgumentList valueArgumentList = expression.getValueArgumentList();
if (valueArgumentList == null || valueArgumentList.getArguments().isEmpty()) {
return null;
}
KtValueArgument valueArgument = valueArgumentList.getArguments().get(0);
if (valueArgument == null) {
return null;
}
return getKeyUsageFromStringTemplate(valueArgument.getFirstChild());
}
/**
* Checks whether this array access is environment call
*
* @param arrayAccess Checking array
* @return true if condition filled
*/
static boolean isEnvArrayAccess(KtArrayAccessExpression arrayAccess) {
List<String> classNames = JavaEnvironmentClasses.getClassNames("get");
if (classNames == null) {
return false;
}
return checkReferences(arrayAccess, classNames);
}
@Nullable
static KeyUsagePsiElement getKeyUsageFromArrayAccess(@NotNull KtArrayAccessExpression expression) {
if (!isEnvArrayAccess(expression)) {
return null;
}
List<KtExpression> indexExpressions = expression.getIndexExpressions();
if (indexExpressions.isEmpty()) {
return null;
}
return getKeyUsageFromStringTemplate(indexExpressions.get(0));
}
@Nullable
private static KeyUsagePsiElement getKeyUsageFromStringTemplate(PsiElement element) {
if (!(element instanceof KtStringTemplateExpression)) {
return null;
}
KtLiteralStringTemplateEntry literal = PsiTreeUtil.findChildOfType(element, KtLiteralStringTemplateEntry.class);
if (literal == null) {
return null;
}
return new KeyUsagePsiElement(literal.getText(), literal);
}
private static boolean checkReferences(PsiElement element, List<String> classNames) {
return Arrays.stream(element.getReferences()).anyMatch(psiReference -> {
PsiElement method = psiReference.resolve();
if (method instanceof KtNamedFunction) {
KtClass ktClass = PsiTreeUtil.getParentOfType(method, KtClass.class);
return ktClass != null && classNames.contains(ktClass.getName());
} else if (method instanceof PsiMethod) {
// Maybe it's a Java reference?
PsiClass psiClass = ((PsiMethod) method).getContainingClass();
return psiClass != null && classNames.contains(psiClass.getName());
}
return false;
});
}
}
@@ -0,0 +1,12 @@
<idea-plugin>
<extensions defaultExtensionNs="com.intellij">
<completion.contributor language="kotlin" implementationClass="ru.adelf.idea.dotenv.kotlin.KotlinEnvCompletionContributor"/>
<gotoDeclarationHandler implementation="ru.adelf.idea.dotenv.kotlin.KotlinEnvCompletionContributor"/>
<completion.confidence language="kotlin" implementationClass="ru.adelf.idea.dotenv.kotlin.KotlinCompletionConfidence"
id="envStringCompletionConfidence" order="first"/>
</extensions>
<extensions defaultExtensionNs="ru.adelf.idea.dotenv">
<environmentVariablesUsagesProvider implementation="ru.adelf.idea.dotenv.kotlin.KotlinEnvironmentVariablesUsagesProvider"/>
</extensions>
</idea-plugin>
@@ -66,6 +66,7 @@
<depends>com.intellij.modules.platform</depends>
<depends optional="true" config-file="java.xml">com.intellij.java</depends>
<depends optional="true" config-file="kotlin.xml">org.jetbrains.kotlin</depends>
<depends optional="true" config-file="php.xml">com.jetbrains.php</depends>
<depends optional="true" config-file="ruby.xml">com.intellij.modules.ruby</depends>
<depends optional="true" config-file="python.xml">com.intellij.modules.python</depends>
@@ -0,0 +1,17 @@
package ru.adelf.idea.dotenv.tests.usages;
public class KotlinUsagesTest extends BaseUsagesTest {
@Override
public void setUp() throws Exception {
super.setUp();
myFixture.copyDirectoryToProject("kotlin", "src/kotlin");
}
public void testGetEnvUsages() {
assertUsagesContains("KOTLIN_GET_ENV", "KOTLIN_GET_ENV2");
}
public void testDotEnvGetUsages() {
assertUsagesContains("KOTLIN_DOTENV_GET", "KOTLIN_DOTENV_GET2");
}
}
@@ -0,0 +1,25 @@
package ru.adelf.idea.dotenv.tests.usages.fixtures.kotlin
import java.lang.System.getenv
class KotlinUsages {
fun getEnv() {
getenv("KOTLIN_GET_ENV")
System.getenv("KOTLIN_GET_ENV2")
}
fun getDotenvGet() {
Dotenv().get("KOTLIN_DOTENV_GET")
val d = Dotenv()
d["KOTLIN_DOTENV_GET2"]
}
}
class Dotenv {
operator fun get(key: String): String {
return "";
}
}