IDEA-178996 References for @TestDataFile-annotated method arguments for @TestDataPath-annotated testcases

This commit is contained in:
Yaroslav Pankratyev
2017-10-03 16:37:14 +07:00
parent d21042b24f
commit 56ab51289b
5 changed files with 170 additions and 8 deletions
@@ -174,6 +174,7 @@
<implicitUsageProvider implementation="org.jetbrains.idea.devkit.inspections.DevKitImplicitUsageProvider"/>
<psi.referenceContributor language="XML" implementation="org.jetbrains.idea.devkit.dom.impl.I18nReferenceContributor"/>
<psi.referenceContributor implementation="org.jetbrains.idea.devkit.references.IconsReferencesContributor"/>
<psi.referenceContributor implementation="org.jetbrains.idea.devkit.references.TestDataFilesReferencesContributor" />
<referencesSearch implementation="org.jetbrains.idea.devkit.references.IconsReferencesContributor"/>
<unusedDeclarationFixProvider implementation="org.jetbrains.idea.devkit.inspections.quickfix.RegisterExtensionFixProvider"/>
@@ -0,0 +1,145 @@
// Copyright 2000-2017 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.references;
import com.intellij.openapi.vfs.LocalFileSystem;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.patterns.PsiJavaElementPattern;
import com.intellij.psi.*;
import com.intellij.psi.impl.source.resolve.reference.impl.providers.FileReferenceSet;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.testFramework.TestDataFile;
import com.intellij.util.PathUtil;
import com.intellij.util.ProcessingContext;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.idea.devkit.testAssistant.TestDataNavigationHandler;
import java.util.Collections;
import java.util.List;
import static com.intellij.patterns.PsiJavaPatterns.psiElement;
import static com.intellij.patterns.PsiJavaPatterns.psiMethod;
public class TestDataFilesReferencesContributor extends PsiReferenceContributor {
private static final String TEST_DATA_FILE_ANNOTATION_QUALIFIED_NAME = TestDataFile.class.getCanonicalName();
@Override
public void registerReferenceProviders(@NotNull PsiReferenceRegistrar registrar) {
PsiJavaElementPattern.Capture<PsiElement> capture = psiElement().methodCallParameter(psiMethod());
registrar.registerReferenceProvider(capture, new PsiReferenceProvider() {
@NotNull
@Override
public PsiReference[] getReferencesByElement(@NotNull PsiElement element, @NotNull ProcessingContext context) {
PsiLiteralExpression expression = (PsiLiteralExpression)element;
PsiElement expressionParent = expression.getParent();
if (!(expressionParent instanceof PsiExpressionList)) {
return PsiReference.EMPTY_ARRAY;
}
PsiExpressionList expressionList = (PsiExpressionList)expressionParent;
PsiExpression[] expressions = expressionList.getExpressions();
int index = getExpressionIndex(expression, expressions);
if (index < 0) {
// shouldn't happen
return PsiReference.EMPTY_ARRAY;
}
PsiMethodCallExpression methodCallExpression = getMethodCallExpression(expression);
PsiParameter targetParameter = getTargetParameter(index, methodCallExpression);
if (!checkTestDataFileAnnotationPresent(targetParameter)) {
return PsiReference.EMPTY_ARRAY;
}
String testDataFilePath = getTestDataFilePath(expression, methodCallExpression);
if (testDataFilePath == null) {
return PsiReference.EMPTY_ARRAY;
}
String directory = PathUtil.getParentPath(testDataFilePath);
FileReferenceSet fileReferenceSet = new FileReferenceSet(element);
fileReferenceSet.addCustomization(
FileReferenceSet.DEFAULT_PATH_EVALUATOR_OPTION,
ignore -> {
VirtualFile file = LocalFileSystem.getInstance().findFileByPath(directory);
return file == null ? null : Collections.singleton(element.getManager().findDirectory(file));
});
return fileReferenceSet.getAllReferences();
}
});
}
private static int getExpressionIndex(@NotNull PsiLiteralExpression expression, @NotNull PsiExpression[] expressions) {
int index = -1;
for (int i = 0; i < expressions.length; i++) {
PsiExpression e = expressions[i];
if (expression.equals(e)) {
index = i;
break;
}
}
return index;
}
@Nullable
private static PsiMethodCallExpression getMethodCallExpression(@NotNull PsiLiteralExpression expression) {
PsiElement expressionContext = expression.getContext();
if (expressionContext == null) {
return null;
}
PsiElement methodCallExpression = expressionContext.getContext();
if (!(methodCallExpression instanceof PsiMethodCallExpression)) {
return null;
}
return (PsiMethodCallExpression)methodCallExpression;
}
@Nullable
private static PsiParameter getTargetParameter(int index, @Nullable PsiMethodCallExpression methodCallExpression) {
if (methodCallExpression == null) {
return null;
}
PsiMethod callMethod = methodCallExpression.resolveMethod();
if (callMethod == null) {
return null;
}
PsiParameter[] parameters = callMethod.getParameterList().getParameters();
// index may be greater than parameters length in case of varargs
return index >= parameters.length ? parameters[parameters.length - 1] : parameters[index];
}
private static boolean checkTestDataFileAnnotationPresent(@Nullable PsiParameter targetParameter) {
if (targetParameter == null) {
return false;
}
PsiAnnotation[] annotations = targetParameter.getAnnotations();
for (PsiAnnotation annotation : annotations) {
if (TEST_DATA_FILE_ANNOTATION_QUALIFIED_NAME.equals(annotation.getQualifiedName())) {
return true;
}
}
return false;
}
@Nullable
private static String getTestDataFilePath(@NotNull PsiLiteralExpression expression,
@NotNull PsiMethodCallExpression methodCallExpression) {
Object value = expression.getValue();
if (!(value instanceof String)) {
return null;
}
String relativePath = (String)value;
PsiMethod testMethod = PsiTreeUtil.getParentOfType(methodCallExpression, PsiMethod.class);
if (testMethod == null) {
return null;
}
List<String> filePaths = TestDataNavigationHandler.fastGetTestDataPathsByRelativePath(relativePath, testMethod);
if (filePaths.size() != 1) {
return null;
}
return filePaths.get(0);
}
}
@@ -74,11 +74,11 @@ public class TestDataGuessByExistingFilesUtil {
* @return collection of paths to the test data files for the given test if it's possible to guess them;
* {@code null} otherwise
*/
@Nullable
@NotNull
static List<String> collectTestDataByExistingFiles(@NotNull PsiMethod psiMethod) {
TestDataDescriptor descriptor = buildDescriptorFromExistingTestData(psiMethod);
if (descriptor == null || !descriptor.isComplete()) {
return null;
return Collections.emptyList();
}
return descriptor.generate();
}
@@ -108,7 +108,7 @@ public class TestDataGuessByExistingFilesUtil {
@Nullable
private static String getFilePath(PsiMethod psiMethod, String testName) {
List<String> strings = collectTestDataByExistingFiles(psiMethod);
if (strings != null && !strings.isEmpty()) {
if (!strings.isEmpty()) {
String s = strings.get(0);
return new File(new File(s).getParent(), testName + "." + FileUtilRt.getExtension(new File(s).getName())).getPath();
}
@@ -26,35 +26,43 @@ import com.intellij.ui.awt.RelativePoint;
import com.intellij.ui.components.JBList;
import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.idea.devkit.testAssistant.vfs.TestDataGroupVirtualFile;
import javax.swing.*;
import java.awt.event.MouseEvent;
import java.util.*;
import java.util.stream.Collectors;
public class TestDataNavigationHandler implements GutterIconNavigationHandler<PsiMethod> {
@Override
public void navigate(MouseEvent e, PsiMethod elt) {
List<String> fileNames = getFileNames(elt);
if (fileNames == null || fileNames.isEmpty()) {
if (fileNames.isEmpty()) {
return;
}
navigate(new RelativePoint(e), fileNames, elt.getProject());
}
@Nullable
@NotNull
static List<String> getFileNames(PsiMethod method) {
return getFileNames(method, true);
}
@NotNull
static List<String> getFileNames(PsiMethod method, boolean collectByExistingFiles) {
List<String> fileNames = null;
String testDataPath = TestDataLineMarkerProvider.getTestDataBasePath(method.getContainingClass());
if (testDataPath != null) {
fileNames = new TestDataReferenceCollector(testDataPath, method.getName().substring(4)).collectTestDataReferences(method);
}
if (fileNames == null || fileNames.isEmpty()) {
if (collectByExistingFiles && (fileNames == null || fileNames.isEmpty())) {
fileNames = TestDataGuessByExistingFilesUtil.collectTestDataByExistingFiles(method);
}
if (fileNames == null) {
fileNames = Collections.emptyList();
}
return fileNames;
}
@@ -75,6 +83,14 @@ public class TestDataNavigationHandler implements GutterIconNavigationHandler<Ps
}
}
@NotNull
public static List<String> fastGetTestDataPathsByRelativePath(@NotNull String testDataFileRelativePath, PsiMethod method) {
return getFileNames(method, false).stream()
.filter(path -> path.endsWith(testDataFileRelativePath.startsWith("/") ? testDataFileRelativePath : "/" + testDataFileRelativePath))
.distinct()
.collect(Collectors.toList());
}
/**
* Shows navigation popup with list of testdata files and (optionally) "Create missing files" option.
* @param project project.
@@ -54,7 +54,7 @@ public class TestDataReferenceCollector {
myTestName = testName;
}
@Nullable
@NotNull
List<String> collectTestDataReferences(@NotNull final PsiMethod method) {
myContainingClass = method.getContainingClass();
List<String> result = collectTestDataReferences(method, new HashMap<>(), new HashSet<>());