diff --git a/plugins/devkit/resources/META-INF/plugin.xml b/plugins/devkit/resources/META-INF/plugin.xml
index 1bca4d7de9a9..41c8c17c9254 100644
--- a/plugins/devkit/resources/META-INF/plugin.xml
+++ b/plugins/devkit/resources/META-INF/plugin.xml
@@ -174,6 +174,7 @@
+
diff --git a/plugins/devkit/src/references/TestDataFilesReferencesContributor.java b/plugins/devkit/src/references/TestDataFilesReferencesContributor.java
new file mode 100644
index 000000000000..55d9c7b8a608
--- /dev/null
+++ b/plugins/devkit/src/references/TestDataFilesReferencesContributor.java
@@ -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 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 filePaths = TestDataNavigationHandler.fastGetTestDataPathsByRelativePath(relativePath, testMethod);
+ if (filePaths.size() != 1) {
+ return null;
+ }
+ return filePaths.get(0);
+ }
+}
diff --git a/plugins/devkit/src/testAssistant/TestDataGuessByExistingFilesUtil.java b/plugins/devkit/src/testAssistant/TestDataGuessByExistingFilesUtil.java
index de171ed7d751..3c8ed104fda6 100644
--- a/plugins/devkit/src/testAssistant/TestDataGuessByExistingFilesUtil.java
+++ b/plugins/devkit/src/testAssistant/TestDataGuessByExistingFilesUtil.java
@@ -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 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 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();
}
diff --git a/plugins/devkit/src/testAssistant/TestDataNavigationHandler.java b/plugins/devkit/src/testAssistant/TestDataNavigationHandler.java
index b0683a110f0d..50f68a99be93 100644
--- a/plugins/devkit/src/testAssistant/TestDataNavigationHandler.java
+++ b/plugins/devkit/src/testAssistant/TestDataNavigationHandler.java
@@ -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 {
@Override
public void navigate(MouseEvent e, PsiMethod elt) {
List fileNames = getFileNames(elt);
- if (fileNames == null || fileNames.isEmpty()) {
+ if (fileNames.isEmpty()) {
return;
}
navigate(new RelativePoint(e), fileNames, elt.getProject());
}
- @Nullable
+ @NotNull
static List getFileNames(PsiMethod method) {
+ return getFileNames(method, true);
+ }
+
+ @NotNull
+ static List getFileNames(PsiMethod method, boolean collectByExistingFiles) {
List 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 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.
diff --git a/plugins/devkit/src/testAssistant/TestDataReferenceCollector.java b/plugins/devkit/src/testAssistant/TestDataReferenceCollector.java
index dfcfe9a2041f..41287b1c1dc4 100644
--- a/plugins/devkit/src/testAssistant/TestDataReferenceCollector.java
+++ b/plugins/devkit/src/testAssistant/TestDataReferenceCollector.java
@@ -54,7 +54,7 @@ public class TestDataReferenceCollector {
myTestName = testName;
}
- @Nullable
+ @NotNull
List collectTestDataReferences(@NotNull final PsiMethod method) {
myContainingClass = method.getContainingClass();
List result = collectTestDataReferences(method, new HashMap<>(), new HashSet<>());