From d9498601b27c41d8e178abc7ec4509f061072d69 Mon Sep 17 00:00:00 2001 From: adelf Date: Thu, 3 Jun 2021 21:24:08 +0300 Subject: [PATCH] kotlin support GitOrigin-RevId: 2c7a580713b05e4bf885cc9d5dd229b35c6f589d --- .../dotenv/java/JavaEnvironmentClasses.java | 25 +++ .../adelf/idea/dotenv/java/JavaPsiHelper.java | 72 +------ .../kotlin/KotlinCompletionConfidence.java | 25 +++ .../KotlinEnvCompletionContributor.java | 70 +++++++ .../kotlin/KotlinEnvironmentCallsVisitor.java | 33 ++++ ...linEnvironmentVariablesUsagesProvider.java | 36 ++++ .../idea/dotenv/kotlin/KotlinPsiHelper.java | 178 ++++++++++++++++++ .../src/main/resources/META-INF/kotlin.xml | 12 ++ .../src/main/resources/META-INF/plugin.xml | 1 + .../dotenv/tests/usages/KotlinUsagesTest.java | 17 ++ .../usages/fixtures/kotlin/KotlinUsages.kt | 25 +++ 11 files changed, 424 insertions(+), 70 deletions(-) create mode 100644 plugins/env-files-support/src/main/java/ru/adelf/idea/dotenv/java/JavaEnvironmentClasses.java create mode 100644 plugins/env-files-support/src/main/java/ru/adelf/idea/dotenv/kotlin/KotlinCompletionConfidence.java create mode 100644 plugins/env-files-support/src/main/java/ru/adelf/idea/dotenv/kotlin/KotlinEnvCompletionContributor.java create mode 100644 plugins/env-files-support/src/main/java/ru/adelf/idea/dotenv/kotlin/KotlinEnvironmentCallsVisitor.java create mode 100644 plugins/env-files-support/src/main/java/ru/adelf/idea/dotenv/kotlin/KotlinEnvironmentVariablesUsagesProvider.java create mode 100644 plugins/env-files-support/src/main/java/ru/adelf/idea/dotenv/kotlin/KotlinPsiHelper.java create mode 100644 plugins/env-files-support/src/main/resources/META-INF/kotlin.xml create mode 100644 plugins/env-files-support/src/test/java/ru/adelf/idea/dotenv/tests/usages/KotlinUsagesTest.java create mode 100644 plugins/env-files-support/src/test/java/ru/adelf/idea/dotenv/tests/usages/fixtures/kotlin/KotlinUsages.kt diff --git a/plugins/env-files-support/src/main/java/ru/adelf/idea/dotenv/java/JavaEnvironmentClasses.java b/plugins/env-files-support/src/main/java/ru/adelf/idea/dotenv/java/JavaEnvironmentClasses.java new file mode 100644 index 000000000000..d4899263e2cb --- /dev/null +++ b/plugins/env-files-support/src/main/java/ru/adelf/idea/dotenv/java/JavaEnvironmentClasses.java @@ -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 getClassNames(String methodName) { + switch (methodName) { + case "get": + return Arrays.asList("Dotenv", "DotEnv"); + case "getProperty": + return Collections.singletonList("System"); + } + + return null; + } +} diff --git a/plugins/env-files-support/src/main/java/ru/adelf/idea/dotenv/java/JavaPsiHelper.java b/plugins/env-files-support/src/main/java/ru/adelf/idea/dotenv/java/JavaPsiHelper.java index 5994e099e726..eba52d8554b9 100644 --- a/plugins/env-files-support/src/main/java/ru/adelf/idea/dotenv/java/JavaPsiHelper.java +++ b/plugins/env-files-support/src/main/java/ru/adelf/idea/dotenv/java/JavaPsiHelper.java @@ -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 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 classNames = getClassNames(methodName); + List 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 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 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; - }*/ } diff --git a/plugins/env-files-support/src/main/java/ru/adelf/idea/dotenv/kotlin/KotlinCompletionConfidence.java b/plugins/env-files-support/src/main/java/ru/adelf/idea/dotenv/kotlin/KotlinCompletionConfidence.java new file mode 100644 index 000000000000..37dfc7bb9802 --- /dev/null +++ b/plugins/env-files-support/src/main/java/ru/adelf/idea/dotenv/kotlin/KotlinCompletionConfidence.java @@ -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; + } +} \ No newline at end of file diff --git a/plugins/env-files-support/src/main/java/ru/adelf/idea/dotenv/kotlin/KotlinEnvCompletionContributor.java b/plugins/env-files-support/src/main/java/ru/adelf/idea/dotenv/kotlin/KotlinEnvCompletionContributor.java new file mode 100644 index 000000000000..8cc407f4300b --- /dev/null +++ b/plugins/env-files-support/src/main/java/ru/adelf/idea/dotenv/kotlin/KotlinEnvCompletionContributor.java @@ -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() { + @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; + } +} diff --git a/plugins/env-files-support/src/main/java/ru/adelf/idea/dotenv/kotlin/KotlinEnvironmentCallsVisitor.java b/plugins/env-files-support/src/main/java/ru/adelf/idea/dotenv/kotlin/KotlinEnvironmentCallsVisitor.java new file mode 100644 index 000000000000..e96142ec649f --- /dev/null +++ b/plugins/env-files-support/src/main/java/ru/adelf/idea/dotenv/kotlin/KotlinEnvironmentCallsVisitor.java @@ -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> { + @Override + public Void visitCallExpression(@NotNull KtCallExpression expression, Set 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 data) { + KeyUsagePsiElement keyUsage = KotlinPsiHelper.getKeyUsageFromArrayAccess(expression); + + if (keyUsage != null) { + data.add(keyUsage); + } + + return super.visitArrayAccessExpression(expression, data); + } +} diff --git a/plugins/env-files-support/src/main/java/ru/adelf/idea/dotenv/kotlin/KotlinEnvironmentVariablesUsagesProvider.java b/plugins/env-files-support/src/main/java/ru/adelf/idea/dotenv/kotlin/KotlinEnvironmentVariablesUsagesProvider.java new file mode 100644 index 000000000000..aef733752a8f --- /dev/null +++ b/plugins/env-files-support/src/main/java/ru/adelf/idea/dotenv/kotlin/KotlinEnvironmentVariablesUsagesProvider.java @@ -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 getUsages(PsiFile psiFile) { + if (psiFile instanceof KtFile) { + Set result = new HashSet<>(); + + KotlinEnvironmentCallsVisitor visitor = new KotlinEnvironmentCallsVisitor(); + ((KtFile) psiFile).acceptChildren(visitor, result); + + return result; + } + + return Collections.emptyList(); + } +} diff --git a/plugins/env-files-support/src/main/java/ru/adelf/idea/dotenv/kotlin/KotlinPsiHelper.java b/plugins/env-files-support/src/main/java/ru/adelf/idea/dotenv/kotlin/KotlinPsiHelper.java new file mode 100644 index 000000000000..4c31eb7f8990 --- /dev/null +++ b/plugins/env-files-support/src/main/java/ru/adelf/idea/dotenv/kotlin/KotlinPsiHelper.java @@ -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 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 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 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 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; + }); + } +} diff --git a/plugins/env-files-support/src/main/resources/META-INF/kotlin.xml b/plugins/env-files-support/src/main/resources/META-INF/kotlin.xml new file mode 100644 index 000000000000..db13b2e386dc --- /dev/null +++ b/plugins/env-files-support/src/main/resources/META-INF/kotlin.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/plugins/env-files-support/src/main/resources/META-INF/plugin.xml b/plugins/env-files-support/src/main/resources/META-INF/plugin.xml index 25001be6641e..3f77a255b1df 100644 --- a/plugins/env-files-support/src/main/resources/META-INF/plugin.xml +++ b/plugins/env-files-support/src/main/resources/META-INF/plugin.xml @@ -66,6 +66,7 @@ com.intellij.modules.platform com.intellij.java + org.jetbrains.kotlin com.jetbrains.php com.intellij.modules.ruby com.intellij.modules.python diff --git a/plugins/env-files-support/src/test/java/ru/adelf/idea/dotenv/tests/usages/KotlinUsagesTest.java b/plugins/env-files-support/src/test/java/ru/adelf/idea/dotenv/tests/usages/KotlinUsagesTest.java new file mode 100644 index 000000000000..679338c003f3 --- /dev/null +++ b/plugins/env-files-support/src/test/java/ru/adelf/idea/dotenv/tests/usages/KotlinUsagesTest.java @@ -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"); + } +} diff --git a/plugins/env-files-support/src/test/java/ru/adelf/idea/dotenv/tests/usages/fixtures/kotlin/KotlinUsages.kt b/plugins/env-files-support/src/test/java/ru/adelf/idea/dotenv/tests/usages/fixtures/kotlin/KotlinUsages.kt new file mode 100644 index 000000000000..45a1e0b76a05 --- /dev/null +++ b/plugins/env-files-support/src/test/java/ru/adelf/idea/dotenv/tests/usages/fixtures/kotlin/KotlinUsages.kt @@ -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 ""; + } +} \ No newline at end of file