From be0894b76e54c9b55661ed3e2b7f613fd96404bf Mon Sep 17 00:00:00 2001 From: Dmitry Jemerov Date: Fri, 17 Feb 2017 17:29:52 +0100 Subject: [PATCH] UAST-based implementation of testdata navigation --- plugins/devkit/devkit.iml | 1 + .../NavigateToTestDataAction.java | 20 +-- .../TestDataAsRelatedFileProvider.java | 18 +-- .../TestDataLineMarkerProvider.java | 18 ++- .../TestDataReferenceCollector.java | 145 +++++++++--------- .../referenceCollector/AbstractMethod.java | 4 + .../referenceCollector/DoTestParameters.java | 4 + .../TestNameAsParameter.java | 4 + 8 files changed, 113 insertions(+), 101 deletions(-) diff --git a/plugins/devkit/devkit.iml b/plugins/devkit/devkit.iml index 195eb207cf03..a08d984f3612 100644 --- a/plugins/devkit/devkit.iml +++ b/plugins/devkit/devkit.iml @@ -39,5 +39,6 @@ + \ No newline at end of file diff --git a/plugins/devkit/src/testAssistant/NavigateToTestDataAction.java b/plugins/devkit/src/testAssistant/NavigateToTestDataAction.java index 388cb90d0ab9..3560055ecff4 100644 --- a/plugins/devkit/src/testAssistant/NavigateToTestDataAction.java +++ b/plugins/devkit/src/testAssistant/NavigateToTestDataAction.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2016 JetBrains s.r.o. + * Copyright 2000-2017 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. @@ -37,6 +37,7 @@ import com.intellij.testFramework.Parameterized; import com.intellij.ui.awt.RelativePoint; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import org.jetbrains.uast.*; import java.util.Collections; import java.util.List; @@ -86,15 +87,17 @@ public class NavigateToTestDataAction extends AnAction implements TestTreeViewAc if (location instanceof PsiMemberParameterizedLocation) { PsiClass containingClass = ((PsiMemberParameterizedLocation)location).getContainingClass(); if (containingClass == null) { - containingClass = PsiTreeUtil.getParentOfType(location.getPsiElement(), PsiClass.class, false); + containingClass = UastContextKt.getUastParentOfType(location.getPsiElement(), UClass.class, false); } if (containingClass != null) { - final PsiAnnotation annotation = AnnotationUtil.findAnnotationInHierarchy(containingClass, Collections.singleton(JUnitUtil.RUN_WITH)); + final UAnnotation annotation = + UastContextKt.toUElement(AnnotationUtil.findAnnotationInHierarchy(containingClass, Collections.singleton(JUnitUtil.RUN_WITH)), UAnnotation.class); if (annotation != null) { - final PsiAnnotationMemberValue memberValue = annotation.findAttributeValue("value"); - if (memberValue instanceof PsiClassObjectAccessExpression) { - final PsiTypeElement operand = ((PsiClassObjectAccessExpression)memberValue).getOperand(); - if (operand.getType().equalsToText(Parameterized.class.getName())) { + UExpression value = annotation.findAttributeValue("value"); + if (value instanceof UClassLiteralExpression) { + UClassLiteralExpression classLiteralExpression = (UClassLiteralExpression)value; + PsiType type = classLiteralExpression.getType(); + if (type != null && type.equalsToText(Parameterized.class.getName())) { final String testDataPath = TestDataLineMarkerProvider.getTestDataBasePath(containingClass); final String paramSetName = ((PsiMemberParameterizedLocation)location).getParamSetName(); final String baseFileName = StringUtil.trimEnd(StringUtil.trimStart(paramSetName, "["), "]"); @@ -126,8 +129,7 @@ public class NavigateToTestDataAction extends AnAction implements TestTreeViewAc final Editor editor = CommonDataKeys.EDITOR.getData(context); final PsiFile file = CommonDataKeys.PSI_FILE.getData(context); if (file != null && editor != null) { - PsiElement element = file.findElementAt(editor.getCaretModel().getOffset()); - return PsiTreeUtil.getParentOfType(element, PsiMethod.class); + return UastContextKt.findUElementAt(file, editor.getCaretModel().getOffset(), UMethod.class); } return null; diff --git a/plugins/devkit/src/testAssistant/TestDataAsRelatedFileProvider.java b/plugins/devkit/src/testAssistant/TestDataAsRelatedFileProvider.java index 748171721e13..94a576ccb6bc 100644 --- a/plugins/devkit/src/testAssistant/TestDataAsRelatedFileProvider.java +++ b/plugins/devkit/src/testAssistant/TestDataAsRelatedFileProvider.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2016 JetBrains s.r.o. + * Copyright 2000-2017 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. @@ -24,6 +24,8 @@ import com.intellij.openapi.project.Project; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiMethod; import org.jetbrains.annotations.NotNull; +import org.jetbrains.uast.UMethod; +import org.jetbrains.uast.UastContextKt; import java.util.Collections; import java.util.List; @@ -41,20 +43,14 @@ public class TestDataAsRelatedFileProvider extends GotoRelatedProvider { final Project project = context.getData(CommonDataKeys.PROJECT); final PsiElement element = context.getData(CommonDataKeys.PSI_ELEMENT); if (editor == null || element == null || project == null) { - return Collections.emptyList(); + return Collections.emptyList(); } - PsiMethod method = null; - for (PsiElement e = element; e != null; e = e.getParent()) { - if (e instanceof PsiMethod) { - method = (PsiMethod)e; - break; - } - } + PsiMethod method = UastContextKt.getUastParentOfType(element, UMethod.class); if (method == null) { return Collections.emptyList(); - } - + } + final List testDataFiles = NavigateToTestDataAction.findTestDataFiles(context); if (testDataFiles == null || testDataFiles.isEmpty()) { return Collections.emptyList(); diff --git a/plugins/devkit/src/testAssistant/TestDataLineMarkerProvider.java b/plugins/devkit/src/testAssistant/TestDataLineMarkerProvider.java index f6697274ef71..ec1140261f1a 100644 --- a/plugins/devkit/src/testAssistant/TestDataLineMarkerProvider.java +++ b/plugins/devkit/src/testAssistant/TestDataLineMarkerProvider.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2014 JetBrains s.r.o. + * Copyright 2000-2017 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. @@ -29,6 +29,9 @@ import com.intellij.psi.util.PsiUtilCore; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.idea.devkit.util.PsiUtil; +import org.jetbrains.uast.UAnnotation; +import org.jetbrains.uast.UExpression; +import org.jetbrains.uast.UastContextKt; import java.util.Collections; @@ -73,14 +76,13 @@ public class TestDataLineMarkerProvider extends RunLineMarkerContributor { public static String getTestDataBasePath(@Nullable PsiClass psiClass) { if (psiClass == null) return null; - final PsiAnnotation annotation = - AnnotationUtil.findAnnotationInHierarchy(psiClass, Collections.singleton(TEST_DATA_PATH_ANNOTATION_QUALIFIED_NAME)); + final UAnnotation annotation = + UastContextKt.toUElement(AnnotationUtil.findAnnotationInHierarchy(psiClass, Collections.singleton(TEST_DATA_PATH_ANNOTATION_QUALIFIED_NAME)), UAnnotation.class); if (annotation != null) { - final PsiAnnotationMemberValue value = annotation.findAttributeValue(PsiAnnotation.DEFAULT_REFERENCED_METHOD_NAME); - if (value instanceof PsiExpression) { - final Project project = value.getProject(); - final PsiConstantEvaluationHelper evaluationHelper = JavaPsiFacade.getInstance(project).getConstantEvaluationHelper(); - final Object constantValue = evaluationHelper.computeConstantExpression(value, false); + UExpression value = annotation.findAttributeValue(PsiAnnotation.DEFAULT_REFERENCED_METHOD_NAME); + if (value != null) { + final Project project = psiClass.getProject(); + final Object constantValue = value.evaluate(); if (constantValue instanceof String) { String path = (String) constantValue; if (path.contains(CONTENT_ROOT_VARIABLE)) { diff --git a/plugins/devkit/src/testAssistant/TestDataReferenceCollector.java b/plugins/devkit/src/testAssistant/TestDataReferenceCollector.java index 58c55749da4e..0e5cbdc456c5 100644 --- a/plugins/devkit/src/testAssistant/TestDataReferenceCollector.java +++ b/plugins/devkit/src/testAssistant/TestDataReferenceCollector.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2015 JetBrains s.r.o. + * Copyright 2000-2017 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. @@ -22,6 +22,14 @@ import com.intellij.psi.*; import com.intellij.testFramework.PlatformTestUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import org.jetbrains.uast.*; +import org.jetbrains.uast.evaluation.SimpleEvaluatorExtension; +import org.jetbrains.uast.evaluation.UEvaluationContextKt; +import org.jetbrains.uast.values.UBooleanConstant; +import org.jetbrains.uast.values.UConstant; +import org.jetbrains.uast.values.UStringConstant; +import org.jetbrains.uast.values.UValue; +import org.jetbrains.uast.visitor.AbstractUastVisitor; import java.io.File; import java.util.*; @@ -61,22 +69,26 @@ public class TestDataReferenceCollector { @NotNull private List collectTestDataReferences(final PsiMethod method, - final Map> argumentMap, + final Map> argumentMap, final HashSet proceed) { final List result = new ArrayList<>(); if (myTestDataPath == null) { return result; } - method.accept(new JavaRecursiveElementVisitor() { + UMethod uMethod = (UMethod)UastContextKt.toUElement(method); + if (uMethod == null) { + return result; + } + uMethod.accept(new AbstractUastVisitor() { @Override - public void visitMethodCallExpression(PsiMethodCallExpression expression) { - String callText = expression.getMethodExpression().getReferenceName(); - if (callText == null) return; - PsiMethod callee = expression.resolveMethod(); + public boolean visitCallExpression(UCallExpression expression) { + String callText = expression.getMethodName(); + if (callText == null) return true; + UMethod callee = UastContextKt.toUElement(expression.resolve(), UMethod.class); if (callee != null && callee.hasModifierProperty(PsiModifier.ABSTRACT)) { final PsiClass calleeContainingClass = callee.getContainingClass(); if (calleeContainingClass != null && myContainingClass.isInheritor(calleeContainingClass, true)) { - final PsiMethod implementation = myContainingClass.findMethodBySignature(callee, true); + final UMethod implementation = UastContextKt.toUElement(myContainingClass.findMethodBySignature(callee, true), UMethod.class); if (implementation != null) { callee = implementation; } @@ -94,91 +106,78 @@ public class TestDataReferenceCollector { haveAnnotatedParameters = true; } } - if (expression.getMethodExpression().getQualifierExpression() == null && !haveAnnotatedParameters) { + if (expression.getReceiver() == null && !haveAnnotatedParameters) { result.addAll(collectTestDataReferences(callee, buildArgumentMap(expression, callee), proceed)); } } + return true; } }); return result; } - private void processCallArgument(PsiMethodCallExpression expression, Map> argumentMap, List result, final int index) { - final PsiExpression[] arguments = expression.getArgumentList().getExpressions(); - if (arguments.length > index) { - String testDataFile = evaluate(arguments [index], argumentMap); - if (testDataFile != null) { - result.add(myTestDataPath + testDataFile); + private void processCallArgument(UCallExpression expression, + Map> argumentMap, + List result, + final int index) { + final List arguments = expression.getValueArguments(); + if (arguments.size() > index) { + UValue testDataFileValue = UEvaluationContextKt.uValueOf(arguments.get(index), new TestDataEvaluatorExtension(argumentMap)); + if (testDataFileValue instanceof UStringConstant) { + result.add(myTestDataPath + ((UStringConstant) testDataFileValue).getValue()); } } } - private Map> buildArgumentMap(PsiMethodCallExpression expression, PsiMethod method) { - Map> result = new HashMap<>(); + private Map> buildArgumentMap(UCallExpression expression, PsiMethod method) { + Map> result = new HashMap<>(); final PsiParameter[] parameters = method.getParameterList().getParameters(); - final PsiExpression[] arguments = expression.getArgumentList().getExpressions(); - for (int i = 0; i < arguments.length && i < parameters.length; i++) { + final List arguments = expression.getValueArguments(); + for (int i = 0; i < arguments.size() && i < parameters.length; i++) { final int finalI = i; result.put(parameters [i].getName(), - (NullableComputable)() -> evaluate(arguments [finalI], Collections.>emptyMap())); + (NullableComputable)() -> UEvaluationContextKt.uValueOf(arguments.get(finalI), + new TestDataEvaluatorExtension(Collections.emptyMap()))); } return result; } - @Nullable - private String evaluate(PsiExpression expression, Map> arguments) { - if (expression instanceof PsiPolyadicExpression) { - PsiPolyadicExpression binaryExpression = (PsiPolyadicExpression)expression; - if (binaryExpression.getOperationTokenType() == JavaTokenType.PLUS) { - String r = ""; - for (PsiExpression op : binaryExpression.getOperands()) { - String lhs = evaluate(op, arguments); - if (lhs == null) return null; - r += lhs; - } - return r; - } - } - else if (expression instanceof PsiLiteralExpression) { - final Object value = ((PsiLiteralExpression)expression).getValue(); - if (value instanceof String) { - return (String) value; - } - } - else if (expression instanceof PsiReferenceExpression) { - final PsiElement result = ((PsiReferenceExpression)expression).resolve(); - if (result instanceof PsiParameter) { - final String name = ((PsiParameter)result).getName(); - final Computable arg = arguments.get(name); - return arg == null ? null : arg.compute(); - } - if (result instanceof PsiVariable) { - final PsiExpression initializer = ((PsiVariable)result).getInitializer(); - if (initializer != null) { - return evaluate(initializer, arguments); - } - } - } - else if (expression instanceof PsiMethodCallExpression) { - final PsiMethodCallExpression methodCall = (PsiMethodCallExpression)expression; - final String callText = methodCall.getMethodExpression().getText(); - if (callText.equals("getTestName")) { - final PsiExpression[] psiExpressions = methodCall.getArgumentList().getExpressions(); - if (psiExpressions.length == 1) { - if ("true".equals(psiExpressions[0].getText()) && !StringUtil.isEmpty(myTestName)) { - return PlatformTestUtil.lowercaseFirstLetter(myTestName, true); - } - return myTestName; - } - } - } - if (expression != null) { - myLogMessages.add("Failed to evaluate " + expression.getText()); - } - return null; - } - public String getLog() { return StringUtil.join(myLogMessages, "\n"); } + + private class TestDataEvaluatorExtension extends SimpleEvaluatorExtension { + private final Map> myArguments; + + private TestDataEvaluatorExtension(Map> arguments) { + myArguments = arguments; + } + + @Override + public Object evaluateMethodCall(PsiMethod target, List argumentValues) { + if (target.getName().equals("getTestName") && argumentValues.size() == 1) { + UValue lowercaseArg = argumentValues.get(0); + boolean lowercaseArgValue = lowercaseArg instanceof UBooleanConstant && ((UBooleanConstant) lowercaseArg).getValue(); + if (lowercaseArgValue && !StringUtil.isEmpty(myTestName)) { + return PlatformTestUtil.lowercaseFirstLetter(myTestName, true); + } + return myTestName; + + } + return super.evaluateMethodCall(target, argumentValues); + } + + @Override + public Object evaluateVariable(UVariable variable) { + if (variable instanceof UParameter) { + Computable value = myArguments.get(variable.getName()); + if (value != null) { + UValue computedValue = value.compute(); + UConstant constant = computedValue.toConstant(); + return constant != null ? constant.getValue() : super.evaluateVariable(variable); + } + } + return super.evaluateVariable(variable); + } + } } diff --git a/plugins/devkit/testData/referenceCollector/AbstractMethod.java b/plugins/devkit/testData/referenceCollector/AbstractMethod.java index 58bd43af2a80..371a852f380e 100644 --- a/plugins/devkit/testData/referenceCollector/AbstractMethod.java +++ b/plugins/devkit/testData/referenceCollector/AbstractMethod.java @@ -17,4 +17,8 @@ class ATest extends AbstractClass { private void configureByFile(@com.intellij.testFramework.TestDataFile String file) { } + + private String getTestName(boolean toUpperCase) { + return null; + } } \ No newline at end of file diff --git a/plugins/devkit/testData/referenceCollector/DoTestParameters.java b/plugins/devkit/testData/referenceCollector/DoTestParameters.java index c591ea3e31bc..d70ca597091a 100644 --- a/plugins/devkit/testData/referenceCollector/DoTestParameters.java +++ b/plugins/devkit/testData/referenceCollector/DoTestParameters.java @@ -14,4 +14,8 @@ public class ATest extends LightCodeInsightFixtureTestCase { private void configureByFile(@com.intellij.testFramework.TestDataFile String file) { } + + private String getTestName(boolean toUpperCase) { + return null; + } } diff --git a/plugins/devkit/testData/referenceCollector/TestNameAsParameter.java b/plugins/devkit/testData/referenceCollector/TestNameAsParameter.java index 0096df84e175..b80f293838bd 100644 --- a/plugins/devkit/testData/referenceCollector/TestNameAsParameter.java +++ b/plugins/devkit/testData/referenceCollector/TestNameAsParameter.java @@ -10,4 +10,8 @@ public class ATest extends LightCodeInsightFixtureTestCase { private void configureByFile(@com.intellij.testFramework.TestDataFile String file) { } + + private String getTestName(boolean toUpperCase) { + return null; + } }