From 5a6b969e2a18349ce4b6fc3f0fb16cf3240ea3e9 Mon Sep 17 00:00:00 2001 From: Yaroslav Pankratyev Date: Mon, 21 Aug 2017 14:52:14 +0700 Subject: [PATCH 001/217] IDEA-141685 Fix skipping more than one testdata file --- .../src/testAssistant/TestDataReferenceCollector.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/plugins/devkit/src/testAssistant/TestDataReferenceCollector.java b/plugins/devkit/src/testAssistant/TestDataReferenceCollector.java index 587d1c212f32..473faf9f9cdd 100644 --- a/plugins/devkit/src/testAssistant/TestDataReferenceCollector.java +++ b/plugins/devkit/src/testAssistant/TestDataReferenceCollector.java @@ -17,6 +17,7 @@ package org.jetbrains.idea.devkit.testAssistant; import com.intellij.openapi.util.Computable; import com.intellij.openapi.util.NullableComputable; +import com.intellij.openapi.util.Pair; import com.intellij.openapi.util.text.StringUtil; import com.intellij.psi.*; import com.intellij.testFramework.PlatformTestUtil; @@ -70,7 +71,7 @@ public class TestDataReferenceCollector { @NotNull private List collectTestDataReferences(final PsiMethod method, final Map> argumentMap, - final HashSet proceed) { + final HashSet>> proceed) { final List result = new ArrayList<>(); if (myTestDataPath == null) { return result; @@ -94,7 +95,8 @@ public class TestDataReferenceCollector { } } } - if (callee != null && proceed.add(callee)) { + Pair> methodWithArguments = new Pair<>(callee, new HashSet<>(expression.getValueArguments())); + if (callee != null && proceed.add(methodWithArguments)) { boolean haveAnnotatedParameters = false; final PsiParameter[] psiParameters = callee.getParameterList().getParameters(); for (int i = 0, psiParametersLength = psiParameters.length; i < psiParametersLength; i++) { From 2bbc5bb96c3031eb7992ff618e95b7fa9e89b70d Mon Sep 17 00:00:00 2001 From: Yaroslav Pankratyev Date: Tue, 22 Aug 2017 16:00:48 +0700 Subject: [PATCH 002/217] IDEA-129745 Use project/module relative path for testdata files, remove duplicates, sorting --- .../TestDataNavigationHandler.java | 112 ++++++++++++++---- 1 file changed, 89 insertions(+), 23 deletions(-) diff --git a/plugins/devkit/src/testAssistant/TestDataNavigationHandler.java b/plugins/devkit/src/testAssistant/TestDataNavigationHandler.java index df14cc55ade8..eb4e98b6afdb 100644 --- a/plugins/devkit/src/testAssistant/TestDataNavigationHandler.java +++ b/plugins/devkit/src/testAssistant/TestDataNavigationHandler.java @@ -20,20 +20,26 @@ import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.fileEditor.OpenFileDescriptor; import com.intellij.openapi.fileTypes.FileType; import com.intellij.openapi.fileTypes.FileTypeManager; +import com.intellij.openapi.module.Module; +import com.intellij.openapi.module.ModuleUtilCore; import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.Messages; import com.intellij.openapi.ui.popup.PopupChooserBuilder; import com.intellij.openapi.util.Computable; +import com.intellij.openapi.util.Pair; import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vfs.LocalFileSystem; import com.intellij.openapi.vfs.VfsUtil; +import com.intellij.openapi.vfs.VfsUtilCore; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.PsiMethod; import com.intellij.ui.ColoredListCellRenderer; +import com.intellij.ui.SimpleTextAttributes; import com.intellij.ui.awt.RelativePoint; import com.intellij.ui.components.JBList; import com.intellij.util.ArrayUtil; import com.intellij.util.PathUtil; +import com.intellij.util.containers.ContainerUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -44,10 +50,9 @@ import java.io.IOException; import java.util.ArrayList; import java.util.List; -/** -* @author yole -*/ public class TestDataNavigationHandler implements GutterIconNavigationHandler { + private static final String CREATE_MISSING_FILES_OPTION = "Create Missing Files"; + public void navigate(MouseEvent e, final PsiMethod elt) { List fileNames = getFileNames(elt); @@ -72,7 +77,7 @@ public class TestDataNavigationHandler implements GutterIconNavigationHandler testDataFiles, + @NotNull List testDataFiles, final Project project) { if (testDataFiles.size() == 1) { openFileByIndex(project, testDataFiles, 0); @@ -108,38 +113,99 @@ public class TestDataNavigationHandler implements GutterIconNavigationHandler fileNames, final RelativePoint point) { - List listPaths = new ArrayList<>(fileNames); - final String CREATE_MISSING_OPTION = "Create Missing Files"; - if (fileNames.size() == 2) { - VirtualFile file1 = LocalFileSystem.getInstance().refreshAndFindFileByPath(fileNames.get(0)); - VirtualFile file2 = LocalFileSystem.getInstance().refreshAndFindFileByPath(fileNames.get(1)); + /** + * Shows navigation popup with list of testdata files and (optionally) "Create missing files" option. + * @param project project. + * @param filePaths paths of testdata files with "/" path separator. This List can be changed. + * @param point point where the popup will be shown. + */ + private static void showNavigationPopup(Project project, List filePaths, RelativePoint point) { + ContainerUtil.removeDuplicates(filePaths); + filePaths.sort((path1, path2) -> PathUtil.getFileName(path1).compareToIgnoreCase(PathUtil.getFileName(path2))); + List pathsToDisplay = new ArrayList<>(filePaths); + + if (filePaths.size() == 2) { + VirtualFile file1 = LocalFileSystem.getInstance().refreshAndFindFileByPath(filePaths.get(0)); + VirtualFile file2 = LocalFileSystem.getInstance().refreshAndFindFileByPath(filePaths.get(1)); if (file1 == null || file2 == null) { - listPaths.add(CREATE_MISSING_OPTION); + pathsToDisplay.add(CREATE_MISSING_FILES_OPTION); } } - final JList list = new JBList(ArrayUtil.toStringArray(listPaths)); - list.setCellRenderer(new ColoredListCellRenderer() { + + JList list = new JBList<>(ArrayUtil.toStringArray(pathsToDisplay)); + list.setCellRenderer(new ColoredListCellRenderer() { @Override - protected void customizeCellRenderer(@NotNull JList list, Object value, int index, boolean selected, boolean hasFocus) { - String path = (String)value; - String fileName = PathUtil.getFileName(path); - if (!fileName.equals(CREATE_MISSING_OPTION)) { - final FileType fileType = FileTypeManager.getInstance().getFileTypeByFileName(fileName); - setIcon(fileType.getIcon()); + protected void customizeCellRenderer(@NotNull JList list, String path, int index, boolean selected, boolean hasFocus) { + if (path.equals(CREATE_MISSING_FILES_OPTION)) { + append(path); + return; } - append(String.format("%s (%s)", fileName, PathUtil.getParentPath(path))); + + VirtualFile file = LocalFileSystem.getInstance().refreshAndFindFileByPath(path); + if (file == null) { + // concurrently deleted? it'll be handled on click, also no icon to indicate something's wrong + append(String.format("%s (%s)", PathUtil.getFileName(path), PathUtil.getParentPath(path))); + return; + } + + FileType fileType = FileTypeManager.getInstance().getFileTypeByFile(file); + setIcon(fileType.getIcon()); + + String fileName = file.getName(); + Pair relativePath = getModuleOrProjectRelativeParentPath(file); + if (relativePath == null) { + // cannot calculate module/project relative path, use absolute path + append(String.format("%s (%s)", fileName, PathUtil.getParentPath(path) + "/")); + } + else { + append(fileName + " ("); + append(relativePath.getFirst(), SimpleTextAttributes.REGULAR_BOLD_ATTRIBUTES); + append("/" + relativePath.getSecond() + "/)"); + } + } + + private Pair getModuleOrProjectRelativeParentPath(VirtualFile file) { + VirtualFile parent = file.getParent(); + if (parent == null) { + // shouldn't happen + return null; + } + + Module module = ModuleUtilCore.findModuleForFile(parent, project); + if (module != null) { + VirtualFile moduleFile = module.getModuleFile(); + if (moduleFile != null) { + VirtualFile moduleFileDir = moduleFile.getParent(); + if (moduleFileDir != null) { + String moduleRelativePath = VfsUtilCore.getRelativePath(parent, moduleFileDir); + if (moduleRelativePath != null) { + return new Pair<>(module.getName(), moduleRelativePath); + } + } + } + } + + VirtualFile projectDir = project.getBaseDir(); + if (projectDir != null) { + String projectRelativePath = VfsUtilCore.getRelativePath(parent, projectDir); + if (projectRelativePath != null) { + return new Pair<>(project.getName(), projectRelativePath); + } + } + + return null; } }); + PopupChooserBuilder builder = new PopupChooserBuilder(list); builder.setItemChoosenCallback(() -> { final int[] indices = list.getSelectedIndices(); - if (ArrayUtil.indexOf(indices, fileNames.size()) >= 0) { - createMissingFiles(project, fileNames); + if (ArrayUtil.indexOf(indices, filePaths.size()) >= 0) { + createMissingFiles(project, filePaths); } else { for (int index : indices) { - openFileByIndex(project, fileNames, index); + openFileByIndex(project, filePaths, index); } } }).createPopup().show(point); From b053fa30f040bba33ada91f0cec952fc61877805 Mon Sep 17 00:00:00 2001 From: Yaroslav Pankratyev Date: Tue, 29 Aug 2017 17:44:39 +0700 Subject: [PATCH 003/217] IDEA-130897 Restore testdata group editor on project open --- plugins/devkit/resources/META-INF/plugin.xml | 1 + .../TestDataGroupVirtualFile.java | 10 ++- .../vfs/TestDataGroupFileSystem.java | 70 +++++++++++++++++++ 3 files changed, 75 insertions(+), 6 deletions(-) create mode 100644 plugins/devkit/src/testAssistant/vfs/TestDataGroupFileSystem.java diff --git a/plugins/devkit/resources/META-INF/plugin.xml b/plugins/devkit/resources/META-INF/plugin.xml index 051b4be3f2dd..c3e3e57a4fd8 100644 --- a/plugins/devkit/resources/META-INF/plugin.xml +++ b/plugins/devkit/resources/META-INF/plugin.xml @@ -16,6 +16,7 @@ org.jetbrains.idea.devkit.DevKitBundle + diff --git a/plugins/devkit/src/testAssistant/TestDataGroupVirtualFile.java b/plugins/devkit/src/testAssistant/TestDataGroupVirtualFile.java index f40aa30bebb3..cd65fc57667b 100644 --- a/plugins/devkit/src/testAssistant/TestDataGroupVirtualFile.java +++ b/plugins/devkit/src/testAssistant/TestDataGroupVirtualFile.java @@ -18,12 +18,10 @@ package org.jetbrains.idea.devkit.testAssistant; import com.intellij.ide.presentation.Presentation; import com.intellij.openapi.fileTypes.FileType; import com.intellij.openapi.util.text.StringUtil; -import com.intellij.openapi.vfs.LocalFileSystem; -import com.intellij.openapi.vfs.VirtualFile; -import com.intellij.openapi.vfs.VirtualFileSystem; -import com.intellij.openapi.vfs.VirtualFileWithId; +import com.intellij.openapi.vfs.*; import com.intellij.util.ArrayUtil; import org.jetbrains.annotations.NotNull; +import org.jetbrains.idea.devkit.testAssistant.vfs.TestDataGroupFileSystem; import java.io.IOException; import java.io.InputStream; @@ -63,13 +61,13 @@ public class TestDataGroupVirtualFile extends VirtualFile { @NotNull @Override public VirtualFileSystem getFileSystem() { - return LocalFileSystem.getInstance(); + return TestDataGroupFileSystem.getTestDataGroupFileSystem(); } @NotNull @Override public String getPath() { - return myBeforeFile.getPath(); + return TestDataGroupFileSystem.getPath(myBeforeFile, myAfterFile); } @Override diff --git a/plugins/devkit/src/testAssistant/vfs/TestDataGroupFileSystem.java b/plugins/devkit/src/testAssistant/vfs/TestDataGroupFileSystem.java new file mode 100644 index 000000000000..750b96efc560 --- /dev/null +++ b/plugins/devkit/src/testAssistant/vfs/TestDataGroupFileSystem.java @@ -0,0 +1,70 @@ +/* + * 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jetbrains.idea.devkit.testAssistant.vfs; + +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.openapi.vfs.LocalFileSystem; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.openapi.vfs.VirtualFileManager; +import com.intellij.openapi.vfs.ex.dummy.DummyCachingFileSystem; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.idea.devkit.testAssistant.TestDataGroupVirtualFile; + +public class TestDataGroupFileSystem extends DummyCachingFileSystem { + /** + * We must have a separator for two arbitrary file paths, considering that almost all symbols are possible in Unix paths. + * It is very unlikely that this UUID will be present in file path so it's a pretty reliable separator. + */ + private static final String GROUP_FILES_SEPARATOR = "33d0ee30-8c8f-11e7-bb31-be2e44b06b34"; + private static final String PROTOCOL = "testdata"; + + public TestDataGroupFileSystem() { + super(PROTOCOL); + } + + + public static TestDataGroupFileSystem getTestDataGroupFileSystem() { + return (TestDataGroupFileSystem)VirtualFileManager.getInstance().getFileSystem(PROTOCOL); + } + + public static String getPath(VirtualFile beforeFile, VirtualFile afterFile) { + return beforeFile.getPath() + GROUP_FILES_SEPARATOR + afterFile.getPath(); + } + + + @Override + protected VirtualFile findFileByPathInner(@NotNull String path) { + String[] parts = path.split(GROUP_FILES_SEPARATOR); + if (parts.length != 2) { + return null; + } + + String beforePath = parts[0]; + String afterPath = parts[1]; + if (StringUtil.isEmpty(beforePath) || StringUtil.isEmpty(afterPath)) { + return null; + } + + LocalFileSystem localFileSystem = LocalFileSystem.getInstance(); + VirtualFile beforeFile = localFileSystem.refreshAndFindFileByPath(beforePath); + VirtualFile afterFile = localFileSystem.refreshAndFindFileByPath(afterPath); + if (beforeFile == null || afterFile == null) { + return null; + } + + return new TestDataGroupVirtualFile(beforeFile, afterFile); + } +} From a8917c1a7cc1ef16cf752d4bf28aa0d3af485385 Mon Sep 17 00:00:00 2001 From: Yaroslav Pankratyev Date: Wed, 30 Aug 2017 15:43:43 +0700 Subject: [PATCH 004/217] IDEA-141685 Handle varargs when collecting testdata files --- .../TestDataReferenceCollector.java | 46 +++++++++++++------ 1 file changed, 32 insertions(+), 14 deletions(-) diff --git a/plugins/devkit/src/testAssistant/TestDataReferenceCollector.java b/plugins/devkit/src/testAssistant/TestDataReferenceCollector.java index 473faf9f9cdd..dfcfe9a2041f 100644 --- a/plugins/devkit/src/testAssistant/TestDataReferenceCollector.java +++ b/plugins/devkit/src/testAssistant/TestDataReferenceCollector.java @@ -85,6 +85,7 @@ public class TestDataReferenceCollector { public boolean visitCallExpression(@NotNull 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(); @@ -95,6 +96,7 @@ public class TestDataReferenceCollector { } } } + Pair> methodWithArguments = new Pair<>(callee, new HashSet<>(expression.getValueArguments())); if (callee != null && proceed.add(methodWithArguments)) { boolean haveAnnotatedParameters = false; @@ -104,7 +106,12 @@ public class TestDataReferenceCollector { final PsiModifierList modifierList = psiParameter.getModifierList(); if (modifierList != null && modifierList.findAnnotation(TEST_DATA_FILE_ANNOTATION_QUALIFIED_NAME) != null) { myFoundTestDataParameters = true; - processCallArgument(expression, argumentMap, result, i); + if (psiParameter.isVarArgs()) { + processVarargCallArgument(expression, argumentMap, result); + } + else { + processCallArgument(expression, argumentMap, result, i); + } haveAnnotatedParameters = true; } } @@ -114,21 +121,32 @@ public class TestDataReferenceCollector { } return true; } - }); - return result; - } - 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 void processCallArgument(UCallExpression expression, Map> argumentMap, + Collection result, int index) { + List arguments = expression.getValueArguments(); + if (arguments.size() > index) { + handleArgument(arguments.get(index), argumentMap, result); + } } - } + + private void processVarargCallArgument(UCallExpression expression, Map> argumentMap, + Collection result) { + List arguments = expression.getValueArguments(); + for (UExpression argument : arguments) { + handleArgument(argument, argumentMap, result); + } + } + + private void handleArgument(UExpression argument, Map> argumentMap, Collection result) { + UValue testDataFileValue = UEvaluationContextKt.uValueOf(argument, new TestDataEvaluatorExtension(argumentMap)); + if (testDataFileValue instanceof UStringConstant) { + result.add(myTestDataPath + ((UStringConstant) testDataFileValue).getValue()); + } + } + }); + + return result; } private Map> buildArgumentMap(UCallExpression expression, PsiMethod method) { From cc114e6624c0c33c6332cd376f7be557353500cb Mon Sep 17 00:00:00 2001 From: Yaroslav Pankratyev Date: Fri, 1 Sep 2017 16:58:33 +0700 Subject: [PATCH 005/217] IDEA-178261 Working prototype of navigating to testdata group where there're other testdata files --- .../TestDataGroupVirtualFile.java | 2 + .../TestDataNavigationElement.java | 35 +++ .../TestDataNavigationElementFactory.java | 199 ++++++++++++++++++ .../TestDataNavigationHandler.java | 191 ++++------------- .../src/testAssistant/TestDataUtil.java | 149 +++++++++++++ 5 files changed, 425 insertions(+), 151 deletions(-) create mode 100644 plugins/devkit/src/testAssistant/TestDataNavigationElement.java create mode 100644 plugins/devkit/src/testAssistant/TestDataNavigationElementFactory.java create mode 100644 plugins/devkit/src/testAssistant/TestDataUtil.java diff --git a/plugins/devkit/src/testAssistant/TestDataGroupVirtualFile.java b/plugins/devkit/src/testAssistant/TestDataGroupVirtualFile.java index cd65fc57667b..70784a5c5480 100644 --- a/plugins/devkit/src/testAssistant/TestDataGroupVirtualFile.java +++ b/plugins/devkit/src/testAssistant/TestDataGroupVirtualFile.java @@ -50,10 +50,12 @@ public class TestDataGroupVirtualFile extends VirtualFile { return prefix + "." + myBeforeFile.getExtension(); } + @NotNull public VirtualFile getBeforeFile() { return myBeforeFile; } + @NotNull public VirtualFile getAfterFile() { return myAfterFile; } diff --git a/plugins/devkit/src/testAssistant/TestDataNavigationElement.java b/plugins/devkit/src/testAssistant/TestDataNavigationElement.java new file mode 100644 index 000000000000..9d8572fd48ae --- /dev/null +++ b/plugins/devkit/src/testAssistant/TestDataNavigationElement.java @@ -0,0 +1,35 @@ +/* + * 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jetbrains.idea.devkit.testAssistant; + +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.Pair; +import com.intellij.ui.SimpleTextAttributes; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import javax.swing.*; +import java.util.List; + +public interface TestDataNavigationElement { + void performAction(@NotNull Project project); + + @Nullable + Icon getIcon(); + + @NotNull + List> getTitleFragments(); +} diff --git a/plugins/devkit/src/testAssistant/TestDataNavigationElementFactory.java b/plugins/devkit/src/testAssistant/TestDataNavigationElementFactory.java new file mode 100644 index 000000000000..c68d1a8ef404 --- /dev/null +++ b/plugins/devkit/src/testAssistant/TestDataNavigationElementFactory.java @@ -0,0 +1,199 @@ +/* + * 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jetbrains.idea.devkit.testAssistant; + +import com.google.common.collect.ImmutableList; +import com.intellij.icons.AllIcons; +import com.intellij.openapi.fileEditor.OpenFileDescriptor; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.Pair; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.openapi.vfs.LocalFileSystem; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.ui.SimpleTextAttributes; +import com.intellij.util.PathUtil; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import javax.swing.*; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public class TestDataNavigationElementFactory { + private TestDataNavigationElementFactory() { + } + + @NotNull + public static TestDataNavigationElement createForFile(@NotNull Project project, @NotNull String path) { + return new TestDataFileNavigationElement(project, path); + } + + @NotNull + public static TestDataNavigationElement createForGroup(@NotNull Project project, @NotNull TestDataGroupVirtualFile group) { + return new TestDataGroupNavigationElement(project, group); + } + + @NotNull + public static TestDataNavigationElement createForCreateMissingFilesOption(@NotNull List filePaths) { + return new CreateMissingTestDataFilesNavigationElement(filePaths); + } + + + private static class CreateMissingTestDataFilesNavigationElement implements TestDataNavigationElement { + private final List myFilePaths; + + private CreateMissingTestDataFilesNavigationElement(List filePaths) { + myFilePaths = filePaths; + } + + @Override + public void performAction(@NotNull Project project) { + for (String name : myFilePaths) { + if (LocalFileSystem.getInstance().refreshAndFindFileByPath(name) == null) { + TestDataUtil.createFileByName(project, name); + } + } + TestDataGroupVirtualFile testDataGroup = TestDataUtil.getTestDataGroup(myFilePaths); + if (testDataGroup != null) { + new OpenFileDescriptor(project, testDataGroup).navigate(true); + } + } + + @Override + public Icon getIcon() { + return null; + } + + @NotNull + @Override + public List> getTitleFragments() { + //TODO externalize + return Collections.singletonList(new Pair<>("Create Missing Files", SimpleTextAttributes.REGULAR_ITALIC_ATTRIBUTES)); + } + } + + private static class TestDataGroupNavigationElement implements TestDataNavigationElement { + private final Project myProject; + private final TestDataGroupVirtualFile myGroup; + + private TestDataGroupNavigationElement(Project project, TestDataGroupVirtualFile group) { + myProject = project; + myGroup = group; + } + + @Override + public void performAction(@NotNull Project project) { + new OpenFileDescriptor(project, myGroup).navigate(true); + } + + @Nullable + @Override + public Icon getIcon() { + return AllIcons.Nodes.TestSourceFolder; + } + + @NotNull + @Override + public List> getTitleFragments() { + VirtualFile beforeFile = myGroup.getBeforeFile(); + VirtualFile afterFile = myGroup.getAfterFile(); + String beforeName = beforeFile.getName(); + String afterName = afterFile.getName(); + + List> result = new ArrayList<>(); + result.add(new Pair<>("<" + beforeName + ", " + afterName + "> (", SimpleTextAttributes.REGULAR_ATTRIBUTES)); + + Pair beforeRelativePath = TestDataUtil.getModuleOrProjectRelativeParentPath(myProject, beforeFile); + Pair afterRelativePath = TestDataUtil.getModuleOrProjectRelativeParentPath(myProject, afterFile); + if (beforeRelativePath != null && afterRelativePath != null) { + String beforeBase = beforeRelativePath.getFirst(); + String afterBase = afterRelativePath.getFirst(); + String beforeBaseRelativePath = beforeRelativePath.getSecond(); + String afterBaseRelativePath = afterRelativePath.getSecond(); + + if (beforeBase.equals(afterBase)) { + result.add(new Pair<>(beforeBase, SimpleTextAttributes.REGULAR_BOLD_ATTRIBUTES)); + if (beforeBaseRelativePath.equals(afterBaseRelativePath)) { // same dir + result.add(new Pair<>("/" + beforeBaseRelativePath + "/)", SimpleTextAttributes.REGULAR_ATTRIBUTES)); + } + else { // same base but different dirs + result.add(new Pair<>("/", SimpleTextAttributes.REGULAR_ATTRIBUTES)); + String commonPrefix = StringUtil.commonPrefix(beforeBaseRelativePath, afterBaseRelativePath); + if (!commonPrefix.isEmpty()) { + result.add(new Pair<>(commonPrefix, SimpleTextAttributes.REGULAR_ATTRIBUTES)); + } + String beforeUniqueSuffix = beforeBaseRelativePath.substring(commonPrefix.length()); + String afterUniqueSuffix = afterBaseRelativePath.substring(commonPrefix.length()); + result.add(new Pair<>("<" + beforeUniqueSuffix + "/, " + afterUniqueSuffix + "/>", SimpleTextAttributes.REGULAR_ATTRIBUTES)); + } + } + else { // different bases + result.add(new Pair<>(beforeBase, SimpleTextAttributes.REGULAR_BOLD_ATTRIBUTES)); + result.add(new Pair<>("/" + beforeBaseRelativePath + "/, ", SimpleTextAttributes.REGULAR_ATTRIBUTES)); + result.add(new Pair<>(afterBase, SimpleTextAttributes.REGULAR_BOLD_ATTRIBUTES)); + result.add(new Pair<>("/" + afterBaseRelativePath + "/", SimpleTextAttributes.REGULAR_ATTRIBUTES)); + } + } + + return result; + } + } + + private static class TestDataFileNavigationElement implements TestDataNavigationElement { + private final Project myProject; + private final String myPath; + + private TestDataFileNavigationElement(Project project, String path) { + myProject = project; + myPath = path; + } + + @Override + public void performAction(@NotNull Project project) { + TestDataUtil.openOrAskToCreateFile(project, myPath); + } + + @Nullable + @Override + public Icon getIcon() { + return TestDataUtil.getIcon(myPath); + } + + @NotNull + @Override + public List> getTitleFragments() { + VirtualFile file = TestDataUtil.getFileByPath(myPath); + if (file == null) { + return Collections.singletonList(new Pair<>( + String.format("%s (%s)", PathUtil.getFileName(myPath), PathUtil.getParentPath(myPath)), + SimpleTextAttributes.GRAYED_ATTRIBUTES)); + } + + Pair relativePath = TestDataUtil.getModuleOrProjectRelativeParentPath(myProject, file); + if (relativePath == null) { + // cannot calculate module/project relative path, use absolute path + return Collections.singletonList(new Pair<>( + String.format("%s (%s)", file.getName(), PathUtil.getParentPath(myPath) + "/"), + SimpleTextAttributes.REGULAR_ATTRIBUTES)); + } + + return ImmutableList.of(new Pair<>(file.getName() + " (", SimpleTextAttributes.REGULAR_ATTRIBUTES), + new Pair<>(relativePath.getFirst(), SimpleTextAttributes.REGULAR_BOLD_ATTRIBUTES), + new Pair<>("/" + relativePath.getSecond() + "/)", SimpleTextAttributes.REGULAR_ATTRIBUTES)); + } + } +} diff --git a/plugins/devkit/src/testAssistant/TestDataNavigationHandler.java b/plugins/devkit/src/testAssistant/TestDataNavigationHandler.java index eb4e98b6afdb..b765eafbdab3 100644 --- a/plugins/devkit/src/testAssistant/TestDataNavigationHandler.java +++ b/plugins/devkit/src/testAssistant/TestDataNavigationHandler.java @@ -16,28 +16,15 @@ package org.jetbrains.idea.devkit.testAssistant; import com.intellij.codeInsight.daemon.GutterIconNavigationHandler; -import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.fileEditor.OpenFileDescriptor; -import com.intellij.openapi.fileTypes.FileType; -import com.intellij.openapi.fileTypes.FileTypeManager; -import com.intellij.openapi.module.Module; -import com.intellij.openapi.module.ModuleUtilCore; import com.intellij.openapi.project.Project; -import com.intellij.openapi.ui.Messages; import com.intellij.openapi.ui.popup.PopupChooserBuilder; -import com.intellij.openapi.util.Computable; -import com.intellij.openapi.util.Pair; -import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vfs.LocalFileSystem; -import com.intellij.openapi.vfs.VfsUtil; -import com.intellij.openapi.vfs.VfsUtilCore; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.PsiMethod; import com.intellij.ui.ColoredListCellRenderer; -import com.intellij.ui.SimpleTextAttributes; import com.intellij.ui.awt.RelativePoint; import com.intellij.ui.components.JBList; -import com.intellij.util.ArrayUtil; import com.intellij.util.PathUtil; import com.intellij.util.containers.ContainerUtil; import org.jetbrains.annotations.NotNull; @@ -45,15 +32,13 @@ import org.jetbrains.annotations.Nullable; import javax.swing.*; import java.awt.event.MouseEvent; -import java.io.File; -import java.io.IOException; import java.util.ArrayList; import java.util.List; +import java.util.ListIterator; public class TestDataNavigationHandler implements GutterIconNavigationHandler { - private static final String CREATE_MISSING_FILES_OPTION = "Create Missing Files"; - - public void navigate(MouseEvent e, final PsiMethod elt) { + @Override + public void navigate(MouseEvent e, PsiMethod elt) { List fileNames = getFileNames(elt); if (fileNames == null || fileNames.isEmpty()) { @@ -76,14 +61,14 @@ public class TestDataNavigationHandler implements GutterIconNavigationHandler testDataFiles, - final Project project) { + Project project) { if (testDataFiles.size() == 1) { - openFileByIndex(project, testDataFiles, 0); + TestDataUtil.openOrAskToCreateFile(project, testDataFiles.get(0)); } else if (testDataFiles.size() > 1) { - TestDataGroupVirtualFile groupFile = getTestDataGroup(testDataFiles); + TestDataGroupVirtualFile groupFile = TestDataUtil.getTestDataGroup(testDataFiles); if (groupFile != null) { new OpenFileDescriptor(project, groupFile).navigate(true); } @@ -93,26 +78,6 @@ public class TestDataNavigationHandler implements GutterIconNavigationHandler fileNames) { - if (fileNames.size() != 2) { - return null; - } - VirtualFile file1 = LocalFileSystem.getInstance().refreshAndFindFileByPath(fileNames.get(0)); - VirtualFile file2 = LocalFileSystem.getInstance().refreshAndFindFileByPath(fileNames.get(1)); - if (file1 == null || file2 == null) { - return null; - } - final int commonPrefixLength = StringUtil.commonPrefixLength(file1.getName(), file2.getName()); - if (file1.getName().substring(commonPrefixLength).toLowerCase().contains("after")) { - return new TestDataGroupVirtualFile(file2, file1); - } - if (file2.getName().substring(commonPrefixLength).toLowerCase().contains("after")) { - return new TestDataGroupVirtualFile(file1, file2); - } - return null; - } - /** * Shows navigation popup with list of testdata files and (optionally) "Create missing files" option. * @param project project. @@ -122,136 +87,60 @@ public class TestDataNavigationHandler implements GutterIconNavigationHandler filePaths, RelativePoint point) { ContainerUtil.removeDuplicates(filePaths); filePaths.sort((path1, path2) -> PathUtil.getFileName(path1).compareToIgnoreCase(PathUtil.getFileName(path2))); - List pathsToDisplay = new ArrayList<>(filePaths); + List elementsToDisplay = getElementsToDisplay(project, filePaths); + + //TODO review - why "Create Missing Files" option is added only when there're two files? if (filePaths.size() == 2) { VirtualFile file1 = LocalFileSystem.getInstance().refreshAndFindFileByPath(filePaths.get(0)); VirtualFile file2 = LocalFileSystem.getInstance().refreshAndFindFileByPath(filePaths.get(1)); if (file1 == null || file2 == null) { - pathsToDisplay.add(CREATE_MISSING_FILES_OPTION); + elementsToDisplay.add(TestDataNavigationElementFactory.createForCreateMissingFilesOption(filePaths)); } } - JList list = new JBList<>(ArrayUtil.toStringArray(pathsToDisplay)); - list.setCellRenderer(new ColoredListCellRenderer() { + JList list = new JBList<>(elementsToDisplay); + list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); + list.setCellRenderer(new ColoredListCellRenderer() { @Override - protected void customizeCellRenderer(@NotNull JList list, String path, int index, boolean selected, boolean hasFocus) { - if (path.equals(CREATE_MISSING_FILES_OPTION)) { - append(path); - return; - } - - VirtualFile file = LocalFileSystem.getInstance().refreshAndFindFileByPath(path); - if (file == null) { - // concurrently deleted? it'll be handled on click, also no icon to indicate something's wrong - append(String.format("%s (%s)", PathUtil.getFileName(path), PathUtil.getParentPath(path))); - return; - } - - FileType fileType = FileTypeManager.getInstance().getFileTypeByFile(file); - setIcon(fileType.getIcon()); - - String fileName = file.getName(); - Pair relativePath = getModuleOrProjectRelativeParentPath(file); - if (relativePath == null) { - // cannot calculate module/project relative path, use absolute path - append(String.format("%s (%s)", fileName, PathUtil.getParentPath(path) + "/")); - } - else { - append(fileName + " ("); - append(relativePath.getFirst(), SimpleTextAttributes.REGULAR_BOLD_ATTRIBUTES); - append("/" + relativePath.getSecond() + "/)"); - } - } - - private Pair getModuleOrProjectRelativeParentPath(VirtualFile file) { - VirtualFile parent = file.getParent(); - if (parent == null) { - // shouldn't happen - return null; - } - - Module module = ModuleUtilCore.findModuleForFile(parent, project); - if (module != null) { - VirtualFile moduleFile = module.getModuleFile(); - if (moduleFile != null) { - VirtualFile moduleFileDir = moduleFile.getParent(); - if (moduleFileDir != null) { - String moduleRelativePath = VfsUtilCore.getRelativePath(parent, moduleFileDir); - if (moduleRelativePath != null) { - return new Pair<>(module.getName(), moduleRelativePath); - } - } - } - } - - VirtualFile projectDir = project.getBaseDir(); - if (projectDir != null) { - String projectRelativePath = VfsUtilCore.getRelativePath(parent, projectDir); - if (projectRelativePath != null) { - return new Pair<>(project.getName(), projectRelativePath); - } - } - - return null; + protected void customizeCellRenderer(@NotNull JList list, TestDataNavigationElement element, int index, + boolean selected, boolean hasFocus) { + element.getTitleFragments().forEach(pair -> append(pair.getFirst(), pair.getSecond())); + setIcon(element.getIcon()); } }); PopupChooserBuilder builder = new PopupChooserBuilder(list); builder.setItemChoosenCallback(() -> { - final int[] indices = list.getSelectedIndices(); - if (ArrayUtil.indexOf(indices, filePaths.size()) >= 0) { - createMissingFiles(project, filePaths); - } - else { - for (int index : indices) { - openFileByIndex(project, filePaths, index); - } + TestDataNavigationElement selectedElement = list.getSelectedValue(); + if (selectedElement != null) { + selectedElement.performAction(project); } }).createPopup().show(point); } - private static void createMissingFiles(Project project, List fileNames) { - for (String name : fileNames) { - if (LocalFileSystem.getInstance().refreshAndFindFileByPath(name) == null) { - createFileByName(project, name); - } - } - final TestDataGroupVirtualFile testDataGroup = getTestDataGroup(fileNames); - if (testDataGroup != null) { - new OpenFileDescriptor(project, testDataGroup).navigate(true); - } - } + private static List getElementsToDisplay(Project project, List filePaths) { + List result = new ArrayList<>(); + for (ListIterator iterator = filePaths.listIterator(); iterator.hasNext(); ) { + String path = iterator.next(); - private static void openFileByIndex(final Project project, final List fileNames, final int index) { - final String path = fileNames.get(index); - final VirtualFile file = LocalFileSystem.getInstance().refreshAndFindFileByPath(path); - if (file != null) { - new OpenFileDescriptor(project, file).navigate(true); - } - else { - int rc = Messages.showYesNoDialog(project, "The referenced testdata file " + path + " does not exist. Would you like to create it?", - "Create Testdata File", Messages.getQuestionIcon()); - if (rc == Messages.YES) { - VirtualFile vFile = createFileByName(project, path); - new OpenFileDescriptor(project, vFile).navigate(true); - } - } - } - - private static VirtualFile createFileByName(final Project project, final String path) { - return ApplicationManager.getApplication().runWriteAction(new Computable() { - public VirtualFile compute() { - try { - final File file = new File(path); - final VirtualFile parent = VfsUtil.createDirectories(file.getParent()); - return parent.createChildData(this, file.getName()); + //FIXME it's not guaranteed that even in sorted list of file paths group files will be placed one after another + // check if there's a testdata group + if (iterator.hasNext()) { + String nextPath = iterator.next(); + TestDataGroupVirtualFile group = TestDataUtil.getTestDataGroup(path, nextPath); + if (group != null) { + result.add(TestDataNavigationElementFactory.createForGroup(project, group)); + continue; } - catch (IOException e) { - Messages.showErrorDialog(project, e.getMessage(), "Create Testdata File"); - return null; + else { + iterator.previous(); } } - }); + + result.add(TestDataNavigationElementFactory.createForFile(project, path)); + } + + return result; } } diff --git a/plugins/devkit/src/testAssistant/TestDataUtil.java b/plugins/devkit/src/testAssistant/TestDataUtil.java new file mode 100644 index 000000000000..edefdc399da3 --- /dev/null +++ b/plugins/devkit/src/testAssistant/TestDataUtil.java @@ -0,0 +1,149 @@ +/* + * 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jetbrains.idea.devkit.testAssistant; + +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.fileEditor.OpenFileDescriptor; +import com.intellij.openapi.fileTypes.FileType; +import com.intellij.openapi.fileTypes.FileTypeManager; +import com.intellij.openapi.module.Module; +import com.intellij.openapi.module.ModuleUtilCore; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.Messages; +import com.intellij.openapi.util.Computable; +import com.intellij.openapi.util.Pair; +import com.intellij.openapi.util.text.StringUtil; +import com.intellij.openapi.vfs.LocalFileSystem; +import com.intellij.openapi.vfs.VfsUtil; +import com.intellij.openapi.vfs.VfsUtilCore; +import com.intellij.openapi.vfs.VirtualFile; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import javax.swing.*; +import java.io.File; +import java.io.IOException; +import java.util.List; + +public class TestDataUtil { + private TestDataUtil() { + } + + @Nullable + public static TestDataGroupVirtualFile getTestDataGroup(@NotNull List fileNames) { + if (fileNames.size() != 2) { + return null; + } + return getTestDataGroup(fileNames.get(0), fileNames.get(1)); + } + + @Nullable + public static TestDataGroupVirtualFile getTestDataGroup(@NotNull String fileName1, @NotNull String fileName2) { + VirtualFile file1 = getFileByPath(fileName1); + VirtualFile file2 = getFileByPath(fileName2); + if (file1 == null || file2 == null) { + return null; + } + int commonPrefixLength = StringUtil.commonPrefixLength(file1.getName(), file2.getName()); + if (file1.getName().substring(commonPrefixLength).toLowerCase().contains("after")) { + return new TestDataGroupVirtualFile(file2, file1); + } + if (file2.getName().substring(commonPrefixLength).toLowerCase().contains("after")) { + return new TestDataGroupVirtualFile(file1, file2); + } + return null; + } + + public static VirtualFile createFileByName(final Project project, final String path) { + return ApplicationManager.getApplication().runWriteAction(new Computable() { + public VirtualFile compute() { + try { + File file = new File(path); + VirtualFile parent = VfsUtil.createDirectories(file.getParent()); + return parent.createChildData(this, file.getName()); + } + catch (IOException e) { + Messages.showErrorDialog(project, e.getMessage(), "Create Testdata File"); //TODO externalize + return null; + } + } + }); + } + + public static void openOrAskToCreateFile(@NotNull Project project, @NotNull String path) { + VirtualFile file = getFileByPath(path); + if (file != null) { + new OpenFileDescriptor(project, file).navigate(true); + } + else { + //TODO externalize + int rc = Messages.showYesNoDialog(project, "The referenced testdata file " + path + " does not exist. Would you like to create it?", + "Create Testdata File", Messages.getQuestionIcon()); + if (rc == Messages.YES) { + VirtualFile vFile = createFileByName(project, path); + new OpenFileDescriptor(project, vFile).navigate(true); + } + } + } + + @Nullable + public static Icon getIcon(@NotNull String path) { + VirtualFile file = getFileByPath(path); + if (file == null) { + return null; + } + FileType fileType = FileTypeManager.getInstance().getFileTypeByFile(file); + return fileType.getIcon(); + } + + @Nullable + public static VirtualFile getFileByPath(String path) { + return LocalFileSystem.getInstance().refreshAndFindFileByPath(path); + } + + @Nullable + public static Pair getModuleOrProjectRelativeParentPath(Project project, VirtualFile file) { + VirtualFile parent = file.getParent(); + if (parent == null) { + // shouldn't happen + return null; + } + + Module module = ModuleUtilCore.findModuleForFile(parent, project); + if (module != null) { + VirtualFile moduleFile = module.getModuleFile(); + if (moduleFile != null) { + VirtualFile moduleFileDir = moduleFile.getParent(); + if (moduleFileDir != null) { + String moduleRelativePath = VfsUtilCore.getRelativePath(parent, moduleFileDir); + if (moduleRelativePath != null) { + return new Pair<>(module.getName(), moduleRelativePath); + } + } + } + } + + VirtualFile projectDir = project.getBaseDir(); + if (projectDir != null) { + String projectRelativePath = VfsUtilCore.getRelativePath(parent, projectDir); + if (projectRelativePath != null) { + return new Pair<>(project.getName(), projectRelativePath); + } + } + + return null; + } +} From 51e89edd5bbb0baf956cb174e73e15658091db6b Mon Sep 17 00:00:00 2001 From: Yaroslav Pankratyev Date: Fri, 1 Sep 2017 17:32:53 +0700 Subject: [PATCH 006/217] IDEA-178261 Show create missing testdata files option if at least one file doesn't exist --- .../idea/devkit/DevKitBundle.properties | 6 ++++ .../TestDataNavigationElementFactory.java | 35 +++++++++++++------ .../TestDataNavigationHandler.java | 9 ++--- .../src/testAssistant/TestDataUtil.java | 8 ++--- 4 files changed, 37 insertions(+), 21 deletions(-) diff --git a/plugins/devkit/resources/org/jetbrains/idea/devkit/DevKitBundle.properties b/plugins/devkit/resources/org/jetbrains/idea/devkit/DevKitBundle.properties index 5ff700a51792..24505fe54e08 100644 --- a/plugins/devkit/resources/org/jetbrains/idea/devkit/DevKitBundle.properties +++ b/plugins/devkit/resources/org/jetbrains/idea/devkit/DevKitBundle.properties @@ -56,6 +56,12 @@ run.configuration.title=Plugin run.configuration.type.description=Plugin Sandbox Environment idea.log.tab.title=IDEA LOG +#Test Data +testdata.create.dialog.title=Create Testdata File +testdata.file.doesn.not.exist=The referenced testdata file {0} does not exist. Would you like to create it? +testdata.create.missing.files=Create Missing Files +testdata.confirm.create.missing.files.dialog.message=The following testdata files will be created:\n{0} + #Misc info.message=Info create.smth=Create {0} diff --git a/plugins/devkit/src/testAssistant/TestDataNavigationElementFactory.java b/plugins/devkit/src/testAssistant/TestDataNavigationElementFactory.java index c68d1a8ef404..ff48ab506328 100644 --- a/plugins/devkit/src/testAssistant/TestDataNavigationElementFactory.java +++ b/plugins/devkit/src/testAssistant/TestDataNavigationElementFactory.java @@ -19,6 +19,7 @@ import com.google.common.collect.ImmutableList; import com.intellij.icons.AllIcons; import com.intellij.openapi.fileEditor.OpenFileDescriptor; import com.intellij.openapi.project.Project; +import com.intellij.openapi.ui.Messages; import com.intellij.openapi.util.Pair; import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vfs.LocalFileSystem; @@ -27,13 +28,14 @@ import com.intellij.ui.SimpleTextAttributes; import com.intellij.util.PathUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import org.jetbrains.idea.devkit.DevKitBundle; import javax.swing.*; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; +import java.util.*; public class TestDataNavigationElementFactory { + private static final int CREATE_MISSING_FILES_WITHOUT_CONFIRMATION_LIMIT = 3; + private TestDataNavigationElementFactory() { } @@ -62,15 +64,26 @@ public class TestDataNavigationElementFactory { @Override public void performAction(@NotNull Project project) { - for (String name : myFilePaths) { - if (LocalFileSystem.getInstance().refreshAndFindFileByPath(name) == null) { - TestDataUtil.createFileByName(project, name); + Set filePathsToCreate = new HashSet<>(); + for (String path : myFilePaths) { + if (LocalFileSystem.getInstance().refreshAndFindFileByPath(path) == null) { + filePathsToCreate.add(path); } } - TestDataGroupVirtualFile testDataGroup = TestDataUtil.getTestDataGroup(myFilePaths); - if (testDataGroup != null) { - new OpenFileDescriptor(project, testDataGroup).navigate(true); + + if (filePathsToCreate.size() > CREATE_MISSING_FILES_WITHOUT_CONFIRMATION_LIMIT) { + int code = Messages.showOkCancelDialog( + project, DevKitBundle.message("testdata.confirm.create.missing.files.dialog.message", StringUtil.join(filePathsToCreate, "\n")), + DevKitBundle.message("testdata.create.missing.files"), Messages.getQuestionIcon()); + if (code != Messages.OK) { + return; + } } + + filePathsToCreate.forEach(path -> { + VirtualFile file = TestDataUtil.createFileByName(project, path); + new OpenFileDescriptor(project, file).navigate(true); + }); } @Override @@ -81,8 +94,8 @@ public class TestDataNavigationElementFactory { @NotNull @Override public List> getTitleFragments() { - //TODO externalize - return Collections.singletonList(new Pair<>("Create Missing Files", SimpleTextAttributes.REGULAR_ITALIC_ATTRIBUTES)); + return Collections.singletonList(new Pair<>( + DevKitBundle.message("testdata.create.missing.files"), SimpleTextAttributes.REGULAR_ITALIC_ATTRIBUTES)); } } diff --git a/plugins/devkit/src/testAssistant/TestDataNavigationHandler.java b/plugins/devkit/src/testAssistant/TestDataNavigationHandler.java index b765eafbdab3..4419b74ef02f 100644 --- a/plugins/devkit/src/testAssistant/TestDataNavigationHandler.java +++ b/plugins/devkit/src/testAssistant/TestDataNavigationHandler.java @@ -89,13 +89,10 @@ public class TestDataNavigationHandler implements GutterIconNavigationHandler PathUtil.getFileName(path1).compareToIgnoreCase(PathUtil.getFileName(path2))); List elementsToDisplay = getElementsToDisplay(project, filePaths); - - //TODO review - why "Create Missing Files" option is added only when there're two files? - if (filePaths.size() == 2) { - VirtualFile file1 = LocalFileSystem.getInstance().refreshAndFindFileByPath(filePaths.get(0)); - VirtualFile file2 = LocalFileSystem.getInstance().refreshAndFindFileByPath(filePaths.get(1)); - if (file1 == null || file2 == null) { + for (String path : filePaths) { + if (LocalFileSystem.getInstance().refreshAndFindFileByPath(path) == null) { elementsToDisplay.add(TestDataNavigationElementFactory.createForCreateMissingFilesOption(filePaths)); + break; } } diff --git a/plugins/devkit/src/testAssistant/TestDataUtil.java b/plugins/devkit/src/testAssistant/TestDataUtil.java index edefdc399da3..f38cd575ba7b 100644 --- a/plugins/devkit/src/testAssistant/TestDataUtil.java +++ b/plugins/devkit/src/testAssistant/TestDataUtil.java @@ -32,6 +32,7 @@ import com.intellij.openapi.vfs.VfsUtilCore; import com.intellij.openapi.vfs.VirtualFile; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import org.jetbrains.idea.devkit.DevKitBundle; import javax.swing.*; import java.io.File; @@ -76,7 +77,7 @@ public class TestDataUtil { return parent.createChildData(this, file.getName()); } catch (IOException e) { - Messages.showErrorDialog(project, e.getMessage(), "Create Testdata File"); //TODO externalize + Messages.showErrorDialog(project, e.getMessage(), DevKitBundle.message("testdata.create.dialog.title")); return null; } } @@ -89,9 +90,8 @@ public class TestDataUtil { new OpenFileDescriptor(project, file).navigate(true); } else { - //TODO externalize - int rc = Messages.showYesNoDialog(project, "The referenced testdata file " + path + " does not exist. Would you like to create it?", - "Create Testdata File", Messages.getQuestionIcon()); + int rc = Messages.showYesNoDialog(project, DevKitBundle.message("testdata.file.doesn.not.exist", path), + DevKitBundle.message("testdata.create.dialog.title"), Messages.getQuestionIcon()); if (rc == Messages.YES) { VirtualFile vFile = createFileByName(project, path); new OpenFileDescriptor(project, vFile).navigate(true); From 2b20c8145de87df392842aa1454b654912dbeede Mon Sep 17 00:00:00 2001 From: Yaroslav Pankratyev Date: Tue, 5 Sep 2017 12:10:14 +0300 Subject: [PATCH 007/217] IDEA-129745 Fixes and improvements in guessing testdata by existing files --- .../openapi/util/text/StringUtil.java | 10 +++ .../TestDataGuessByExistingFilesUtil.java | 73 ++++++++++++++----- 2 files changed, 64 insertions(+), 19 deletions(-) diff --git a/platform/util/src/com/intellij/openapi/util/text/StringUtil.java b/platform/util/src/com/intellij/openapi/util/text/StringUtil.java index 34c99226c241..0b2d8c72a464 100644 --- a/platform/util/src/com/intellij/openapi/util/text/StringUtil.java +++ b/platform/util/src/com/intellij/openapi/util/text/StringUtil.java @@ -3134,6 +3134,16 @@ public class StringUtil extends StringUtilRt { return false; } + /** + * Inserts a space before each word in a camelCased string. + * "myClass" -> "my Class", "MyClass" -> "My Class", "MyXML" -> "My XML". + */ + @NotNull + @Contract(pure = true) + public static String[] splitCamelCase(@NotNull String s) { + return s.split("(?<=[A-Z])(?=[A-Z][a-z])|(?<=[^A-Z])(?=[A-Z])|(?<=[A-Za-z])(?=[^A-Za-z])"); + } + private static final Pattern UNICODE_CHAR = Pattern.compile("\\\\u[0-9a-eA-E]{4}"); diff --git a/plugins/devkit/src/testAssistant/TestDataGuessByExistingFilesUtil.java b/plugins/devkit/src/testAssistant/TestDataGuessByExistingFilesUtil.java index d1773dac4504..93ae4e26d151 100644 --- a/plugins/devkit/src/testAssistant/TestDataGuessByExistingFilesUtil.java +++ b/plugins/devkit/src/testAssistant/TestDataGuessByExistingFilesUtil.java @@ -36,7 +36,7 @@ import com.intellij.testIntegration.TestFramework; import com.intellij.util.CommonProcessors; import com.intellij.util.PathUtil; import com.intellij.util.containers.ContainerUtil; -import com.intellij.util.containers.HashSet; +import com.intellij.util.containers.HashMap; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.jps.model.java.JavaModuleSourceRootTypes; @@ -172,7 +172,7 @@ public class TestDataGuessByExistingFilesUtil { { ProjectFileIndex fileIndex = ProjectRootManager.getInstance(psiClass.getProject()).getFileIndex(); GotoFileModel gotoModel = new GotoFileModel(psiClass.getProject()); - Set descriptors = new HashSet<>(); + Map descriptorsByFileNames = new HashMap<>(); // PhpStorm has tests that use '$' symbol as a file path separator, e.g. 'test$while_stmt$declaration' test // stands for '/while_smt/declaration.php' file somewhere in a test data. final String possibleFileName = ContainerUtil.getLastItem(StringUtil.split(test, "$"), test); @@ -211,18 +211,18 @@ public class TestDataGuessByExistingFilesUtil { continue; } - if (descriptors.isEmpty() || (descriptors.iterator().next().dir.equals(current.dir) && !descriptors.contains(current))) { - descriptors.add(current); + TestLocationDescriptor previousDescriptor = descriptorsByFileNames.get(name); + if (previousDescriptor == null) { + descriptorsByFileNames.put(name, current); continue; } - if (moreRelevantPath(current, descriptors, psiClass)) { - descriptors.clear(); - descriptors.add(current); + if (moreRelevantPath(current, previousDescriptor, psiClass)) { + descriptorsByFileNames.put(name, current); } break; } } - return new TestDataDescriptor(descriptors, possibleFileName); + return new TestDataDescriptor(descriptorsByFileNames.values(), possibleFileName); } private static Collection getAllFileNames(final String testName, final GotoFileModel model) { @@ -252,7 +252,7 @@ public class TestDataGuessByExistingFilesUtil { } private static boolean moreRelevantPath(@NotNull TestLocationDescriptor candidate, - @NotNull Set currentDescriptors, + @NotNull TestLocationDescriptor current, @NotNull PsiClass psiClass) { final String className = psiClass.getQualifiedName(); @@ -260,16 +260,17 @@ public class TestDataGuessByExistingFilesUtil { return false; } - final TestLocationDescriptor current = currentDescriptors.iterator().next(); boolean candidateMatched; boolean currentMatched; // By package. - int i = className.lastIndexOf("."); - if (i >= 0) { - String packageAsPath = className.substring(0, i).replace('.', '/').toLowerCase(); - candidateMatched = candidate.dir.toLowerCase().contains(packageAsPath); - currentMatched = current.dir.toLowerCase().contains(packageAsPath); + int lastDotIndex = className.lastIndexOf("."); + String candidateLcDir = candidate.dir.toLowerCase(); + String currentLcDir = current.dir.toLowerCase(); + if (lastDotIndex >= 0) { + String packageAsPath = className.substring(0, lastDotIndex).replace('.', '/').toLowerCase(); + candidateMatched = candidateLcDir.contains(packageAsPath); + currentMatched = currentLcDir.contains(packageAsPath); if (candidateMatched ^ currentMatched) { return candidateMatched; } @@ -279,11 +280,45 @@ public class TestDataGuessByExistingFilesUtil { String simpleName = getSimpleClassName(psiClass); if (simpleName != null) { String pattern = simpleName.toLowerCase(); - candidateMatched = candidate.dir.toLowerCase().contains(pattern); - currentMatched = current.dir.toLowerCase().contains(pattern); + candidateMatched = candidateLcDir.contains(pattern); + currentMatched = currentLcDir.contains(pattern); if (candidateMatched ^ currentMatched) { return candidateMatched; } + + // By class name words and their position. More words + greater position = better. + String[] words = StringUtil.splitCamelCase(simpleName); + int candidateWordsMatched = 0; + int currentWordsMatched = 0; + int candidateMatchPosition = -1; + int currentMatchPosition = -1; + + StringBuilder currentNameSubstringSb = new StringBuilder(); + for (int i = 0; i < words.length; i++) { + currentNameSubstringSb.append(words[i]); + String currentNameLcSubstring = currentNameSubstringSb.toString().toLowerCase(); + + int candidateWordsIndex = candidateLcDir.lastIndexOf(currentNameLcSubstring); + if (candidateWordsIndex > 0) { + candidateWordsMatched = i + 1; + candidateMatchPosition = candidateWordsIndex; + } + + int currentWordsIndex = currentLcDir.lastIndexOf(currentNameLcSubstring); + if (currentWordsIndex > 0) { + currentWordsMatched = i + 1; + candidateMatchPosition = currentWordsIndex; + } + + if (candidateWordsMatched != currentWordsMatched) { + break; // no need to continue + } + } + + if (candidateWordsMatched != currentWordsMatched) { + return candidateWordsMatched > currentWordsMatched; + } + return candidateMatchPosition > currentMatchPosition; } return false; @@ -403,10 +438,10 @@ public class TestDataGuessByExistingFilesUtil { for (TestLocationDescriptor descriptor : myDescriptors) { if (root != null && !root.equals(descriptor.dir)) continue; result.add(String.format( - "%s/%s%c%s%s.%s", + "%s/%s%c%s%s%s", descriptor.dir, descriptor.filePrefix, descriptor.startWithLowerCase ? Character.toLowerCase(testName.charAt(0)) : Character.toUpperCase(testName.charAt(0)), - testName.substring(1), descriptor.fileSuffix, descriptor.ext + testName.substring(1), descriptor.fileSuffix, StringUtil.isEmpty(descriptor.ext) ? "" : "." + descriptor.ext )); } return result; From 87b124fd030ba56659c9080f85432bc7328a45eb Mon Sep 17 00:00:00 2001 From: Yaroslav Pankratyev Date: Tue, 5 Sep 2017 12:29:13 +0300 Subject: [PATCH 008/217] IDEA-178261 Fix detecting wrong testdata pairs --- .../TestDataNavigationHandler.java | 20 ++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/plugins/devkit/src/testAssistant/TestDataNavigationHandler.java b/plugins/devkit/src/testAssistant/TestDataNavigationHandler.java index 4419b74ef02f..0d7ce4818810 100644 --- a/plugins/devkit/src/testAssistant/TestDataNavigationHandler.java +++ b/plugins/devkit/src/testAssistant/TestDataNavigationHandler.java @@ -19,8 +19,8 @@ import com.intellij.codeInsight.daemon.GutterIconNavigationHandler; import com.intellij.openapi.fileEditor.OpenFileDescriptor; import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.popup.PopupChooserBuilder; +import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vfs.LocalFileSystem; -import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.PsiMethod; import com.intellij.ui.ColoredListCellRenderer; import com.intellij.ui.awt.RelativePoint; @@ -85,10 +85,9 @@ public class TestDataNavigationHandler implements GutterIconNavigationHandler filePaths, RelativePoint point) { - ContainerUtil.removeDuplicates(filePaths); - filePaths.sort((path1, path2) -> PathUtil.getFileName(path1).compareToIgnoreCase(PathUtil.getFileName(path2))); - List elementsToDisplay = getElementsToDisplay(project, filePaths); + + // if at least one file doesn't exist add "Create missing files" element for (String path : filePaths) { if (LocalFileSystem.getInstance().refreshAndFindFileByPath(path) == null) { elementsToDisplay.add(TestDataNavigationElementFactory.createForCreateMissingFilesOption(filePaths)); @@ -117,11 +116,22 @@ public class TestDataNavigationHandler implements GutterIconNavigationHandler getElementsToDisplay(Project project, List filePaths) { + ContainerUtil.removeDuplicates(filePaths); + + filePaths.sort((path1, path2) -> { + String name1 = PathUtil.getFileName(path1); + String name2 = PathUtil.getFileName(path2); + name1 = StringUtil.trimStart(name1, "before"); + name2 = StringUtil.trimStart(name2, "before"); + name1 = StringUtil.trimStart(name1, "after"); + name2 = StringUtil.trimStart(name2, "after"); + return name1.compareToIgnoreCase(name2); + }); + List result = new ArrayList<>(); for (ListIterator iterator = filePaths.listIterator(); iterator.hasNext(); ) { String path = iterator.next(); - //FIXME it's not guaranteed that even in sorted list of file paths group files will be placed one after another // check if there's a testdata group if (iterator.hasNext()) { String nextPath = iterator.next(); From 14e09ff556dcdd16844201bd995053b9224d1195 Mon Sep 17 00:00:00 2001 From: Yaroslav Pankratyev Date: Tue, 5 Sep 2017 13:47:15 +0300 Subject: [PATCH 009/217] Javadoc fix --- .../util/src/com/intellij/openapi/util/text/StringUtil.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/platform/util/src/com/intellij/openapi/util/text/StringUtil.java b/platform/util/src/com/intellij/openapi/util/text/StringUtil.java index 0b2d8c72a464..cfde3e758355 100644 --- a/platform/util/src/com/intellij/openapi/util/text/StringUtil.java +++ b/platform/util/src/com/intellij/openapi/util/text/StringUtil.java @@ -3135,8 +3135,8 @@ public class StringUtil extends StringUtilRt { } /** - * Inserts a space before each word in a camelCased string. - * "myClass" -> "my Class", "MyClass" -> "My Class", "MyXML" -> "My XML". + * Splits a camelCased string by words. + * "myClass" -> [my,Class], "MyClass" -> [My,Class], "MyXML" -> [My,XML]. */ @NotNull @Contract(pure = true) From 28b565dbb47f759c74485fb971234104c948c606 Mon Sep 17 00:00:00 2001 From: Yaroslav Pankratyev Date: Tue, 5 Sep 2017 14:15:59 +0300 Subject: [PATCH 010/217] IDEA-129745 Fix skipping testdata candidates after first more relevant found + test --- .../TestDataGuessByExistingFilesUtil.java | 1 - .../guessByExistingFiles/Test/testdata_file.txt | 0 .../guessByExistingFiles/TestMore/testdata_file.txt | 0 .../guessByExistingFiles/TestMoreRelevant.java | 6 ++++++ .../TestMoreRelevant/testdata_file.txt | 0 .../TestDataGuessByExistingFilesUtilTest.java | 12 ++++++++++++ 6 files changed, 18 insertions(+), 1 deletion(-) create mode 100644 plugins/devkit/testData/guessByExistingFiles/Test/testdata_file.txt create mode 100644 plugins/devkit/testData/guessByExistingFiles/TestMore/testdata_file.txt create mode 100644 plugins/devkit/testData/guessByExistingFiles/TestMoreRelevant.java create mode 100644 plugins/devkit/testData/guessByExistingFiles/TestMoreRelevant/testdata_file.txt diff --git a/plugins/devkit/src/testAssistant/TestDataGuessByExistingFilesUtil.java b/plugins/devkit/src/testAssistant/TestDataGuessByExistingFilesUtil.java index 93ae4e26d151..0cf6892e5160 100644 --- a/plugins/devkit/src/testAssistant/TestDataGuessByExistingFilesUtil.java +++ b/plugins/devkit/src/testAssistant/TestDataGuessByExistingFilesUtil.java @@ -219,7 +219,6 @@ public class TestDataGuessByExistingFilesUtil { if (moreRelevantPath(current, previousDescriptor, psiClass)) { descriptorsByFileNames.put(name, current); } - break; } } return new TestDataDescriptor(descriptorsByFileNames.values(), possibleFileName); diff --git a/plugins/devkit/testData/guessByExistingFiles/Test/testdata_file.txt b/plugins/devkit/testData/guessByExistingFiles/Test/testdata_file.txt new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/plugins/devkit/testData/guessByExistingFiles/TestMore/testdata_file.txt b/plugins/devkit/testData/guessByExistingFiles/TestMore/testdata_file.txt new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/plugins/devkit/testData/guessByExistingFiles/TestMoreRelevant.java b/plugins/devkit/testData/guessByExistingFiles/TestMoreRelevant.java new file mode 100644 index 000000000000..8cb85cf6f5d1 --- /dev/null +++ b/plugins/devkit/testData/guessByExistingFiles/TestMoreRelevant.java @@ -0,0 +1,6 @@ +import junit.framework.TestCase; + +public class TestMoreRelevant extends TestCase { + public void testMoreRelevant() { + } +} \ No newline at end of file diff --git a/plugins/devkit/testData/guessByExistingFiles/TestMoreRelevant/testdata_file.txt b/plugins/devkit/testData/guessByExistingFiles/TestMoreRelevant/testdata_file.txt new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/plugins/devkit/testSources/testAssistant/TestDataGuessByExistingFilesUtilTest.java b/plugins/devkit/testSources/testAssistant/TestDataGuessByExistingFilesUtilTest.java index 60a62c3f0ab1..ffc164d66d29 100644 --- a/plugins/devkit/testSources/testAssistant/TestDataGuessByExistingFilesUtilTest.java +++ b/plugins/devkit/testSources/testAssistant/TestDataGuessByExistingFilesUtilTest.java @@ -20,6 +20,7 @@ import com.intellij.openapi.application.PluginPathManager; import com.intellij.openapi.projectRoots.ex.JavaSdkUtil; import com.intellij.openapi.util.ThrowableComputable; import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.psi.PsiClass; import com.intellij.psi.PsiMethod; import com.intellij.psi.util.PsiTreeUtil; import com.intellij.testFramework.PsiTestUtil; @@ -63,6 +64,17 @@ public class TestDataGuessByExistingFilesUtilTest extends TestDataPathTestCase { assertEquals("TestName", result); } + public void testMoreRelevantFiles() { + PsiMethod testMethod = getTestMethod("TestMoreRelevant.java", + "Test/testdata_file.txt", "TestMore/testdata_file.txt", "TestMoreRelevant/testdata_file.txt"); + PsiClass testClass = (PsiClass)testMethod.getParent(); + + List result = TestDataGuessByExistingFilesUtil.suggestTestDataFiles("testdata_file", null, testClass); + assertEquals(result.toString(), 1, result.size()); + String resultPath = result.get(0); + assertTrue(resultPath, resultPath.endsWith("TestMoreRelevant/testdata_file.txt")); + } + public void testCollectTestDataByExistingFilesBeforeAndAfter() { PsiMethod testMethod = getTestMethodWithBeforeAndAfterTestData(); List result = TestDataGuessByExistingFilesUtil.collectTestDataByExistingFiles(testMethod); From cf5a2800d2cd199d53af662e2d8e36b6e497d94e Mon Sep 17 00:00:00 2001 From: Yaroslav Pankratyev Date: Tue, 5 Sep 2017 14:44:43 +0300 Subject: [PATCH 011/217] IDEA-129745 Fix false positives in detecting testdata groups --- plugins/devkit/src/testAssistant/TestDataUtil.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/plugins/devkit/src/testAssistant/TestDataUtil.java b/plugins/devkit/src/testAssistant/TestDataUtil.java index f38cd575ba7b..230a76eb58a3 100644 --- a/plugins/devkit/src/testAssistant/TestDataUtil.java +++ b/plugins/devkit/src/testAssistant/TestDataUtil.java @@ -59,6 +59,9 @@ public class TestDataUtil { return null; } int commonPrefixLength = StringUtil.commonPrefixLength(file1.getName(), file2.getName()); + if (commonPrefixLength == 0) { + return null; + } if (file1.getName().substring(commonPrefixLength).toLowerCase().contains("after")) { return new TestDataGroupVirtualFile(file2, file1); } From 6899fa897f338a655405afba202e5fb906555f74 Mon Sep 17 00:00:00 2001 From: Yaroslav Pankratyev Date: Tue, 12 Sep 2017 14:26:51 +0300 Subject: [PATCH 012/217] Minor fixes and refactoring after review --- .../openapi/util/text/StringUtil.java | 11 --- .../TestDataGroupEditorProvider.java | 1 + .../TestDataGroupFileEditor.java | 1 + .../TestDataGuessByExistingFilesUtil.java | 72 +++++++++---------- .../TestDataNavigationElementFactory.java | 1 + .../TestDataNavigationHandler.java | 1 + .../src/testAssistant/TestDataUtil.java | 3 +- .../vfs/TestDataGroupFileSystem.java | 1 - .../{ => vfs}/TestDataGroupVirtualFile.java | 5 +- .../TestDataGuessByExistingFilesUtilTest.java | 2 +- 10 files changed, 45 insertions(+), 53 deletions(-) rename plugins/devkit/src/testAssistant/{ => vfs}/TestDataGroupVirtualFile.java (95%) diff --git a/platform/util/src/com/intellij/openapi/util/text/StringUtil.java b/platform/util/src/com/intellij/openapi/util/text/StringUtil.java index cfde3e758355..831fa9db5aa1 100644 --- a/platform/util/src/com/intellij/openapi/util/text/StringUtil.java +++ b/platform/util/src/com/intellij/openapi/util/text/StringUtil.java @@ -3134,17 +3134,6 @@ public class StringUtil extends StringUtilRt { return false; } - /** - * Splits a camelCased string by words. - * "myClass" -> [my,Class], "MyClass" -> [My,Class], "MyXML" -> [My,XML]. - */ - @NotNull - @Contract(pure = true) - public static String[] splitCamelCase(@NotNull String s) { - return s.split("(?<=[A-Z])(?=[A-Z][a-z])|(?<=[^A-Z])(?=[A-Z])|(?<=[A-Za-z])(?=[^A-Za-z])"); - } - - private static final Pattern UNICODE_CHAR = Pattern.compile("\\\\u[0-9a-eA-E]{4}"); public static String replaceUnicodeEscapeSequences(String text) { diff --git a/plugins/devkit/src/testAssistant/TestDataGroupEditorProvider.java b/plugins/devkit/src/testAssistant/TestDataGroupEditorProvider.java index 5f9bb41f3f4e..a08d63c366c7 100644 --- a/plugins/devkit/src/testAssistant/TestDataGroupEditorProvider.java +++ b/plugins/devkit/src/testAssistant/TestDataGroupEditorProvider.java @@ -22,6 +22,7 @@ import com.intellij.openapi.project.DumbAware; import com.intellij.openapi.project.Project; import com.intellij.openapi.vfs.VirtualFile; import org.jetbrains.annotations.NotNull; +import org.jetbrains.idea.devkit.testAssistant.vfs.TestDataGroupVirtualFile; /** * @author yole diff --git a/plugins/devkit/src/testAssistant/TestDataGroupFileEditor.java b/plugins/devkit/src/testAssistant/TestDataGroupFileEditor.java index 678f00e04bf1..47b891d63150 100644 --- a/plugins/devkit/src/testAssistant/TestDataGroupFileEditor.java +++ b/plugins/devkit/src/testAssistant/TestDataGroupFileEditor.java @@ -27,6 +27,7 @@ import com.intellij.openapi.util.UserDataHolderBase; import com.intellij.reference.SoftReference; import com.intellij.util.ui.JBUI; import org.jetbrains.annotations.NotNull; +import org.jetbrains.idea.devkit.testAssistant.vfs.TestDataGroupVirtualFile; import javax.swing.*; import java.awt.*; diff --git a/plugins/devkit/src/testAssistant/TestDataGuessByExistingFilesUtil.java b/plugins/devkit/src/testAssistant/TestDataGuessByExistingFilesUtil.java index 0cf6892e5160..b51c0d00190e 100644 --- a/plugins/devkit/src/testAssistant/TestDataGuessByExistingFilesUtil.java +++ b/plugins/devkit/src/testAssistant/TestDataGuessByExistingFilesUtil.java @@ -28,6 +28,7 @@ import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.PsiClass; import com.intellij.psi.PsiFile; import com.intellij.psi.PsiMethod; +import com.intellij.psi.codeStyle.NameUtil; import com.intellij.psi.util.CachedValueProvider; import com.intellij.psi.util.CachedValuesManager; import com.intellij.psi.util.PsiModificationTracker; @@ -277,50 +278,49 @@ public class TestDataGuessByExistingFilesUtil { // By class name. String simpleName = getSimpleClassName(psiClass); - if (simpleName != null) { - String pattern = simpleName.toLowerCase(); - candidateMatched = candidateLcDir.contains(pattern); - currentMatched = currentLcDir.contains(pattern); - if (candidateMatched ^ currentMatched) { - return candidateMatched; + if (simpleName == null) { + return false; + } + String pattern = simpleName.toLowerCase(); + candidateMatched = candidateLcDir.contains(pattern); + currentMatched = currentLcDir.contains(pattern); + if (candidateMatched ^ currentMatched) { + return candidateMatched; + } + + // By class name words and their position. More words + greater position = better. + String[] words = NameUtil.nameToWords(simpleName); + int candidateWordsMatched = 0; + int currentWordsMatched = 0; + int candidateMatchPosition = -1; + int currentMatchPosition = -1; + + StringBuilder currentNameSubstringSb = new StringBuilder(); + for (int i = 0; i < words.length; i++) { + currentNameSubstringSb.append(words[i]); + String currentNameLcSubstring = currentNameSubstringSb.toString().toLowerCase(); + + int candidateWordsIndex = candidateLcDir.lastIndexOf(currentNameLcSubstring); + if (candidateWordsIndex > 0) { + candidateWordsMatched = i + 1; + candidateMatchPosition = candidateWordsIndex; } - // By class name words and their position. More words + greater position = better. - String[] words = StringUtil.splitCamelCase(simpleName); - int candidateWordsMatched = 0; - int currentWordsMatched = 0; - int candidateMatchPosition = -1; - int currentMatchPosition = -1; - - StringBuilder currentNameSubstringSb = new StringBuilder(); - for (int i = 0; i < words.length; i++) { - currentNameSubstringSb.append(words[i]); - String currentNameLcSubstring = currentNameSubstringSb.toString().toLowerCase(); - - int candidateWordsIndex = candidateLcDir.lastIndexOf(currentNameLcSubstring); - if (candidateWordsIndex > 0) { - candidateWordsMatched = i + 1; - candidateMatchPosition = candidateWordsIndex; - } - - int currentWordsIndex = currentLcDir.lastIndexOf(currentNameLcSubstring); - if (currentWordsIndex > 0) { - currentWordsMatched = i + 1; - candidateMatchPosition = currentWordsIndex; - } - - if (candidateWordsMatched != currentWordsMatched) { - break; // no need to continue - } + int currentWordsIndex = currentLcDir.lastIndexOf(currentNameLcSubstring); + if (currentWordsIndex > 0) { + currentWordsMatched = i + 1; + candidateMatchPosition = currentWordsIndex; } if (candidateWordsMatched != currentWordsMatched) { - return candidateWordsMatched > currentWordsMatched; + break; // no need to continue } - return candidateMatchPosition > currentMatchPosition; } - return false; + if (candidateWordsMatched != currentWordsMatched) { + return candidateWordsMatched > currentWordsMatched; + } + return candidateMatchPosition > currentMatchPosition; } private static class TestLocationDescriptor { diff --git a/plugins/devkit/src/testAssistant/TestDataNavigationElementFactory.java b/plugins/devkit/src/testAssistant/TestDataNavigationElementFactory.java index ff48ab506328..35e8d940f7ff 100644 --- a/plugins/devkit/src/testAssistant/TestDataNavigationElementFactory.java +++ b/plugins/devkit/src/testAssistant/TestDataNavigationElementFactory.java @@ -29,6 +29,7 @@ import com.intellij.util.PathUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.idea.devkit.DevKitBundle; +import org.jetbrains.idea.devkit.testAssistant.vfs.TestDataGroupVirtualFile; import javax.swing.*; import java.util.*; diff --git a/plugins/devkit/src/testAssistant/TestDataNavigationHandler.java b/plugins/devkit/src/testAssistant/TestDataNavigationHandler.java index 0d7ce4818810..f49d06f72eea 100644 --- a/plugins/devkit/src/testAssistant/TestDataNavigationHandler.java +++ b/plugins/devkit/src/testAssistant/TestDataNavigationHandler.java @@ -29,6 +29,7 @@ import com.intellij.util.PathUtil; 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; diff --git a/plugins/devkit/src/testAssistant/TestDataUtil.java b/plugins/devkit/src/testAssistant/TestDataUtil.java index 230a76eb58a3..4557dac8327e 100644 --- a/plugins/devkit/src/testAssistant/TestDataUtil.java +++ b/plugins/devkit/src/testAssistant/TestDataUtil.java @@ -33,13 +33,14 @@ import com.intellij.openapi.vfs.VirtualFile; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.idea.devkit.DevKitBundle; +import org.jetbrains.idea.devkit.testAssistant.vfs.TestDataGroupVirtualFile; import javax.swing.*; import java.io.File; import java.io.IOException; import java.util.List; -public class TestDataUtil { +class TestDataUtil { private TestDataUtil() { } diff --git a/plugins/devkit/src/testAssistant/vfs/TestDataGroupFileSystem.java b/plugins/devkit/src/testAssistant/vfs/TestDataGroupFileSystem.java index 750b96efc560..f4235b033866 100644 --- a/plugins/devkit/src/testAssistant/vfs/TestDataGroupFileSystem.java +++ b/plugins/devkit/src/testAssistant/vfs/TestDataGroupFileSystem.java @@ -21,7 +21,6 @@ import com.intellij.openapi.vfs.VirtualFile; import com.intellij.openapi.vfs.VirtualFileManager; import com.intellij.openapi.vfs.ex.dummy.DummyCachingFileSystem; import org.jetbrains.annotations.NotNull; -import org.jetbrains.idea.devkit.testAssistant.TestDataGroupVirtualFile; public class TestDataGroupFileSystem extends DummyCachingFileSystem { /** diff --git a/plugins/devkit/src/testAssistant/TestDataGroupVirtualFile.java b/plugins/devkit/src/testAssistant/vfs/TestDataGroupVirtualFile.java similarity index 95% rename from plugins/devkit/src/testAssistant/TestDataGroupVirtualFile.java rename to plugins/devkit/src/testAssistant/vfs/TestDataGroupVirtualFile.java index 70784a5c5480..0af8ff91a7e6 100644 --- a/plugins/devkit/src/testAssistant/TestDataGroupVirtualFile.java +++ b/plugins/devkit/src/testAssistant/vfs/TestDataGroupVirtualFile.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. @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.jetbrains.idea.devkit.testAssistant; +package org.jetbrains.idea.devkit.testAssistant.vfs; import com.intellij.ide.presentation.Presentation; import com.intellij.openapi.fileTypes.FileType; @@ -21,7 +21,6 @@ import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vfs.*; import com.intellij.util.ArrayUtil; import org.jetbrains.annotations.NotNull; -import org.jetbrains.idea.devkit.testAssistant.vfs.TestDataGroupFileSystem; import java.io.IOException; import java.io.InputStream; diff --git a/plugins/devkit/testSources/testAssistant/TestDataGuessByExistingFilesUtilTest.java b/plugins/devkit/testSources/testAssistant/TestDataGuessByExistingFilesUtilTest.java index ffc164d66d29..b12786feda31 100644 --- a/plugins/devkit/testSources/testAssistant/TestDataGuessByExistingFilesUtilTest.java +++ b/plugins/devkit/testSources/testAssistant/TestDataGuessByExistingFilesUtilTest.java @@ -70,7 +70,7 @@ public class TestDataGuessByExistingFilesUtilTest extends TestDataPathTestCase { PsiClass testClass = (PsiClass)testMethod.getParent(); List result = TestDataGuessByExistingFilesUtil.suggestTestDataFiles("testdata_file", null, testClass); - assertEquals(result.toString(), 1, result.size()); + assertOneElement(result); String resultPath = result.get(0); assertTrue(resultPath, resultPath.endsWith("TestMoreRelevant/testdata_file.txt")); } From 090245360746a5d62c4adb8a568cdb5713311060 Mon Sep 17 00:00:00 2001 From: Yaroslav Pankratyev Date: Wed, 13 Sep 2017 11:06:57 +0300 Subject: [PATCH 013/217] IDEA-178261 Fix testdata paths sorting to avoid failed group detection in some cases --- .../openapi/util/text/StringUtil.java | 8 ++++ .../intellij/util/text/StringUtilTest.java | 9 +++++ .../TestDataNavigationHandler.java | 39 +++++++++++++++---- 3 files changed, 48 insertions(+), 8 deletions(-) diff --git a/platform/util/src/com/intellij/openapi/util/text/StringUtil.java b/platform/util/src/com/intellij/openapi/util/text/StringUtil.java index 831fa9db5aa1..86afe816e02b 100644 --- a/platform/util/src/com/intellij/openapi/util/text/StringUtil.java +++ b/platform/util/src/com/intellij/openapi/util/text/StringUtil.java @@ -1976,6 +1976,14 @@ public class StringUtil extends StringUtilRt { return text.substring(i + subString.length()); } + @NotNull + @Contract(pure = true) + public static String substringBeforeLast(@NotNull String text, @NotNull String subString) { + int i = text.lastIndexOf(subString); + if (i == -1) return text; + return text.substring(0, i); + } + /** * Allows to retrieve index of last occurrence of the given symbols at {@code [start; end)} sub-sequence of the given text. * diff --git a/platform/util/testSrc/com/intellij/util/text/StringUtilTest.java b/platform/util/testSrc/com/intellij/util/text/StringUtilTest.java index c49c78bf17a0..a912f2339871 100644 --- a/platform/util/testSrc/com/intellij/util/text/StringUtilTest.java +++ b/platform/util/testSrc/com/intellij/util/text/StringUtilTest.java @@ -600,4 +600,13 @@ public class StringUtilTest { assertEquals(3, StringUtil.countChars("abcddddefghd", 'd', 4, true)); assertEquals(2, StringUtil.countChars("abcddddefghd", 'd', 4, 6, false)); } + + @Test + public void testSubstringBeforeLast() { + assertEquals("a", StringUtil.substringBeforeLast("abc", "b")); + assertEquals("abab", StringUtil.substringBeforeLast("ababbccc", "b")); + assertEquals("abc", StringUtil.substringBeforeLast("abc", "")); + assertEquals("abc", StringUtil.substringBeforeLast("abc", "1")); + assertEquals("", StringUtil.substringBeforeLast("", "1")); + } } diff --git a/plugins/devkit/src/testAssistant/TestDataNavigationHandler.java b/plugins/devkit/src/testAssistant/TestDataNavigationHandler.java index f49d06f72eea..22ad5005496d 100644 --- a/plugins/devkit/src/testAssistant/TestDataNavigationHandler.java +++ b/plugins/devkit/src/testAssistant/TestDataNavigationHandler.java @@ -34,6 +34,7 @@ import org.jetbrains.idea.devkit.testAssistant.vfs.TestDataGroupVirtualFile; import javax.swing.*; import java.awt.event.MouseEvent; import java.util.ArrayList; +import java.util.Comparator; import java.util.List; import java.util.ListIterator; @@ -119,14 +120,36 @@ public class TestDataNavigationHandler implements GutterIconNavigationHandler getElementsToDisplay(Project project, List filePaths) { ContainerUtil.removeDuplicates(filePaths); - filePaths.sort((path1, path2) -> { - String name1 = PathUtil.getFileName(path1); - String name2 = PathUtil.getFileName(path2); - name1 = StringUtil.trimStart(name1, "before"); - name2 = StringUtil.trimStart(name2, "before"); - name1 = StringUtil.trimStart(name1, "after"); - name2 = StringUtil.trimStart(name2, "after"); - return name1.compareToIgnoreCase(name2); + filePaths.sort(new Comparator() { + @Override + public int compare(String path1, String path2) { + String name1 = prepareToCompare(path1); + String name2 = prepareToCompare(path2); + return name1.compareToIgnoreCase(name2); + } + + private String prepareToCompare(String path) { + String result = PathUtil.getFileName(path); + result = StringUtil.trimStart(result, "before"); + result = StringUtil.trimStart(result, "after"); + + String extension = PathUtil.getFileExtension(result); + if (extension != null) { + extension = "." + extension; + if (result.endsWith("_after" + extension)) { + result = StringUtil.substringBeforeLast(result, "_after" + extension) + extension; + } + else if (result.endsWith("_before" + extension)) { + result = StringUtil.substringBeforeLast(result, "_before" + extension) + extension; + } + } + else { + result = StringUtil.trimEnd(result, "_after"); + result = StringUtil.trimEnd(result, "_before"); + } + + return result; + } }); List result = new ArrayList<>(); From e3941346f7badc4629ce0a5a7a5917618baa7523 Mon Sep 17 00:00:00 2001 From: Yaroslav Pankratyev Date: Wed, 13 Sep 2017 12:50:26 +0300 Subject: [PATCH 014/217] IDEA-178261 Cancelable searching for testdata by existing files + handle "test" test name --- .../idea/devkit/DevKitBundle.properties | 1 + .../TestDataGuessByExistingFilesUtil.java | 107 +++++++++++------- 2 files changed, 66 insertions(+), 42 deletions(-) diff --git a/plugins/devkit/resources/org/jetbrains/idea/devkit/DevKitBundle.properties b/plugins/devkit/resources/org/jetbrains/idea/devkit/DevKitBundle.properties index 24505fe54e08..741bb37181d7 100644 --- a/plugins/devkit/resources/org/jetbrains/idea/devkit/DevKitBundle.properties +++ b/plugins/devkit/resources/org/jetbrains/idea/devkit/DevKitBundle.properties @@ -61,6 +61,7 @@ testdata.create.dialog.title=Create Testdata File testdata.file.doesn.not.exist=The referenced testdata file {0} does not exist. Would you like to create it? testdata.create.missing.files=Create Missing Files testdata.confirm.create.missing.files.dialog.message=The following testdata files will be created:\n{0} +testdata.searching=Searching for Testdata Files #Misc info.message=Info diff --git a/plugins/devkit/src/testAssistant/TestDataGuessByExistingFilesUtil.java b/plugins/devkit/src/testAssistant/TestDataGuessByExistingFilesUtil.java index b51c0d00190e..2230bb228883 100644 --- a/plugins/devkit/src/testAssistant/TestDataGuessByExistingFilesUtil.java +++ b/plugins/devkit/src/testAssistant/TestDataGuessByExistingFilesUtil.java @@ -18,7 +18,11 @@ package org.jetbrains.idea.devkit.testAssistant; import com.intellij.codeInsight.AnnotationUtil; import com.intellij.codeInsight.TestFrameworks; import com.intellij.ide.util.gotoByName.GotoFileModel; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.progress.ProcessCanceledException; +import com.intellij.openapi.progress.ProgressIndicator; import com.intellij.openapi.progress.ProgressManager; +import com.intellij.openapi.project.Project; import com.intellij.openapi.roots.ProjectFileIndex; import com.intellij.openapi.roots.ProjectRootManager; import com.intellij.openapi.util.io.FileUtil; @@ -40,6 +44,7 @@ import com.intellij.util.containers.ContainerUtil; import com.intellij.util.containers.HashMap; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import org.jetbrains.idea.devkit.DevKitBundle; import org.jetbrains.jps.model.java.JavaModuleSourceRootTypes; import java.io.File; @@ -171,56 +176,74 @@ public class TestDataGuessByExistingFilesUtil { private static TestDataDescriptor buildDescriptor(@NotNull String test, @NotNull PsiClass psiClass) { - ProjectFileIndex fileIndex = ProjectRootManager.getInstance(psiClass.getProject()).getFileIndex(); - GotoFileModel gotoModel = new GotoFileModel(psiClass.getProject()); - Map descriptorsByFileNames = new HashMap<>(); - // PhpStorm has tests that use '$' symbol as a file path separator, e.g. 'test$while_stmt$declaration' test + // PhpStorm has tests that use '$' symbol as a file path separator, e.g. 'test$while_stmt$declaration' test // stands for '/while_smt/declaration.php' file somewhere in a test data. final String possibleFileName = ContainerUtil.getLastItem(StringUtil.split(test, "$"), test); assert possibleFileName != null; + if (possibleFileName.isEmpty()) { + return TestDataDescriptor.NOTHING_FOUND; + } + + Project project = psiClass.getProject(); + ProjectFileIndex fileIndex = ProjectRootManager.getInstance(project).getFileIndex(); + GotoFileModel gotoModel = new GotoFileModel(project); final String possibleFilePath = test.replace('$', '/'); - final Collection fileNames = getAllFileNames(possibleFileName, gotoModel); - for (String name : fileNames) { - ProgressManager.checkCanceled(); - final Object[] elements = gotoModel.getElementsByName(name, false, name); - for (Object element : elements) { - if (!(element instanceof PsiFile)) { - continue; - } - final VirtualFile file = ((PsiFile)element).getVirtualFile(); - if (file == null || fileIndex.isInSource(file) && !fileIndex.isUnderSourceRootOfType(file, JavaModuleSourceRootTypes.RESOURCES)) { - continue; - } + Map descriptorsByFileNames = new HashMap<>(); + boolean completed = ProgressManager.getInstance().runProcessWithProgressSynchronously(() -> { + final Collection fileNames = getAllFileNames(possibleFileName, gotoModel); + ProgressIndicator indicator = ProgressManager.getInstance().getProgressIndicator(); + indicator.setIndeterminate(false); + ApplicationManager.getApplication().runReadAction(() -> { + int fileNamesCount = fileNames.size(); + double currentIndex = 0; + for (String name : fileNames) { + ProgressManager.checkCanceled(); + final Object[] elements = gotoModel.getElementsByName(name, false, name); + for (Object element : elements) { + if (!(element instanceof PsiFile)) { + continue; + } + final VirtualFile file = ((PsiFile)element).getVirtualFile(); + if (file == null || fileIndex.isInSource(file) && !fileIndex.isUnderSourceRootOfType(file, JavaModuleSourceRootTypes.RESOURCES)) { + continue; + } - final String filePath = file.getPath(); - if (!StringUtil.containsIgnoreCase(filePath, possibleFilePath) && !StringUtil.containsIgnoreCase(filePath, test)) { - continue; - } - final String fileName = PathUtil.getFileName(filePath).toLowerCase(); - int i = fileName.indexOf(possibleFileName.toLowerCase()); - // Skip files that doesn't contain target test name and files that contain digit after target test name fragment. - // Example: there are tests with names 'testEnter()' and 'testEnter2()' and we don't want test data file 'testEnter2' - // to be matched to the test 'testEnter()'. - if (i < 0 || (i + possibleFileName.length() < fileName.length()) - && Character.isDigit(fileName.charAt(i + possibleFileName.length()))) { - continue; - } + final String filePath = file.getPath(); + if (!StringUtil.containsIgnoreCase(filePath, possibleFilePath) && !StringUtil.containsIgnoreCase(filePath, test)) { + continue; + } + final String fileName = PathUtil.getFileName(filePath).toLowerCase(); + int i = fileName.indexOf(possibleFileName.toLowerCase()); + // Skip files that doesn't contain target test name and files that contain digit after target test name fragment. + // Example: there are tests with names 'testEnter()' and 'testEnter2()' and we don't want test data file 'testEnter2' + // to be matched to the test 'testEnter()'. + if (i < 0 || (i + possibleFileName.length() < fileName.length()) + && Character.isDigit(fileName.charAt(i + possibleFileName.length()))) { + continue; + } - TestLocationDescriptor current = new TestLocationDescriptor(); - current.populate(possibleFileName, file); - if (!current.isComplete()) { - continue; - } + TestLocationDescriptor current = new TestLocationDescriptor(); + current.populate(possibleFileName, file); + if (!current.isComplete()) { + continue; + } - TestLocationDescriptor previousDescriptor = descriptorsByFileNames.get(name); - if (previousDescriptor == null) { - descriptorsByFileNames.put(name, current); - continue; + TestLocationDescriptor previousDescriptor = descriptorsByFileNames.get(name); + if (previousDescriptor == null) { + descriptorsByFileNames.put(name, current); + continue; + } + if (moreRelevantPath(current, previousDescriptor, psiClass)) { + descriptorsByFileNames.put(name, current); + } + } + indicator.setFraction(++currentIndex / fileNamesCount); } - if (moreRelevantPath(current, previousDescriptor, psiClass)) { - descriptorsByFileNames.put(name, current); - } - } + }); + }, DevKitBundle.message("testdata.searching"), true, project); + + if (!completed) { + throw new ProcessCanceledException(); } return new TestDataDescriptor(descriptorsByFileNames.values(), possibleFileName); } From 243b82a8b3a2bf097a712a7fbc2feefa6072a7fe Mon Sep 17 00:00:00 2001 From: Yaroslav Pankratyev Date: Wed, 13 Sep 2017 15:46:15 +0300 Subject: [PATCH 015/217] Minor fixes --- .../intellij/openapi/util/text/StringUtil.java | 16 ++++++++-------- .../testAssistant/TestDataNavigationHandler.java | 9 ++++----- .../devkit/src/testAssistant/TestDataUtil.java | 14 +++++++------- .../TestDataGuessByExistingFilesUtilTest.java | 3 +-- 4 files changed, 20 insertions(+), 22 deletions(-) diff --git a/platform/util/src/com/intellij/openapi/util/text/StringUtil.java b/platform/util/src/com/intellij/openapi/util/text/StringUtil.java index 86afe816e02b..f3c8c5ff1027 100644 --- a/platform/util/src/com/intellij/openapi/util/text/StringUtil.java +++ b/platform/util/src/com/intellij/openapi/util/text/StringUtil.java @@ -1968,14 +1968,6 @@ public class StringUtil extends StringUtilRt { return text.substring(0, i); } - @Nullable - @Contract(pure = true) - public static String substringAfter(@NotNull String text, @NotNull String subString) { - int i = text.indexOf(subString); - if (i == -1) return null; - return text.substring(i + subString.length()); - } - @NotNull @Contract(pure = true) public static String substringBeforeLast(@NotNull String text, @NotNull String subString) { @@ -1984,6 +1976,14 @@ public class StringUtil extends StringUtilRt { return text.substring(0, i); } + @Nullable + @Contract(pure = true) + public static String substringAfter(@NotNull String text, @NotNull String subString) { + int i = text.indexOf(subString); + if (i == -1) return null; + return text.substring(i + subString.length()); + } + /** * Allows to retrieve index of last occurrence of the given symbols at {@code [start; end)} sub-sequence of the given text. * diff --git a/plugins/devkit/src/testAssistant/TestDataNavigationHandler.java b/plugins/devkit/src/testAssistant/TestDataNavigationHandler.java index 22ad5005496d..f4851dca0041 100644 --- a/plugins/devkit/src/testAssistant/TestDataNavigationHandler.java +++ b/plugins/devkit/src/testAssistant/TestDataNavigationHandler.java @@ -123,14 +123,13 @@ public class TestDataNavigationHandler implements GutterIconNavigationHandler() { @Override public int compare(String path1, String path2) { - String name1 = prepareToCompare(path1); - String name2 = prepareToCompare(path2); + String name1 = stripBeforeAfterFromFileName(PathUtil.getFileName(path1)); + String name2 = stripBeforeAfterFromFileName(PathUtil.getFileName(path2)); return name1.compareToIgnoreCase(name2); } - private String prepareToCompare(String path) { - String result = PathUtil.getFileName(path); - result = StringUtil.trimStart(result, "before"); + private String stripBeforeAfterFromFileName(String name) { + String result = StringUtil.trimStart(name, "before"); result = StringUtil.trimStart(result, "after"); String extension = PathUtil.getFileExtension(result); diff --git a/plugins/devkit/src/testAssistant/TestDataUtil.java b/plugins/devkit/src/testAssistant/TestDataUtil.java index 4557dac8327e..50fb9ee6fa22 100644 --- a/plugins/devkit/src/testAssistant/TestDataUtil.java +++ b/plugins/devkit/src/testAssistant/TestDataUtil.java @@ -45,7 +45,7 @@ class TestDataUtil { } @Nullable - public static TestDataGroupVirtualFile getTestDataGroup(@NotNull List fileNames) { + static TestDataGroupVirtualFile getTestDataGroup(@NotNull List fileNames) { if (fileNames.size() != 2) { return null; } @@ -53,7 +53,7 @@ class TestDataUtil { } @Nullable - public static TestDataGroupVirtualFile getTestDataGroup(@NotNull String fileName1, @NotNull String fileName2) { + static TestDataGroupVirtualFile getTestDataGroup(@NotNull String fileName1, @NotNull String fileName2) { VirtualFile file1 = getFileByPath(fileName1); VirtualFile file2 = getFileByPath(fileName2); if (file1 == null || file2 == null) { @@ -72,7 +72,7 @@ class TestDataUtil { return null; } - public static VirtualFile createFileByName(final Project project, final String path) { + static VirtualFile createFileByName(final Project project, final String path) { return ApplicationManager.getApplication().runWriteAction(new Computable() { public VirtualFile compute() { try { @@ -88,7 +88,7 @@ class TestDataUtil { }); } - public static void openOrAskToCreateFile(@NotNull Project project, @NotNull String path) { + static void openOrAskToCreateFile(@NotNull Project project, @NotNull String path) { VirtualFile file = getFileByPath(path); if (file != null) { new OpenFileDescriptor(project, file).navigate(true); @@ -104,7 +104,7 @@ class TestDataUtil { } @Nullable - public static Icon getIcon(@NotNull String path) { + static Icon getIcon(@NotNull String path) { VirtualFile file = getFileByPath(path); if (file == null) { return null; @@ -114,12 +114,12 @@ class TestDataUtil { } @Nullable - public static VirtualFile getFileByPath(String path) { + static VirtualFile getFileByPath(String path) { return LocalFileSystem.getInstance().refreshAndFindFileByPath(path); } @Nullable - public static Pair getModuleOrProjectRelativeParentPath(Project project, VirtualFile file) { + static Pair getModuleOrProjectRelativeParentPath(Project project, VirtualFile file) { VirtualFile parent = file.getParent(); if (parent == null) { // shouldn't happen diff --git a/plugins/devkit/testSources/testAssistant/TestDataGuessByExistingFilesUtilTest.java b/plugins/devkit/testSources/testAssistant/TestDataGuessByExistingFilesUtilTest.java index b12786feda31..ed1889600fd1 100644 --- a/plugins/devkit/testSources/testAssistant/TestDataGuessByExistingFilesUtilTest.java +++ b/plugins/devkit/testSources/testAssistant/TestDataGuessByExistingFilesUtilTest.java @@ -70,8 +70,7 @@ public class TestDataGuessByExistingFilesUtilTest extends TestDataPathTestCase { PsiClass testClass = (PsiClass)testMethod.getParent(); List result = TestDataGuessByExistingFilesUtil.suggestTestDataFiles("testdata_file", null, testClass); - assertOneElement(result); - String resultPath = result.get(0); + String resultPath = assertOneElement(result); assertTrue(resultPath, resultPath.endsWith("TestMoreRelevant/testdata_file.txt")); } From ea9dbba967ae42b167560255903113bc4f5effe4 Mon Sep 17 00:00:00 2001 From: Yaroslav Pankratyev Date: Wed, 13 Sep 2017 16:43:12 +0300 Subject: [PATCH 016/217] IDEA-178261 Support for before/after testdata file prefix --- .../TestDataNavigationHandler.java | 16 ++++---- .../src/testAssistant/TestDataUtil.java | 37 +++++++++++++++++-- 2 files changed, 41 insertions(+), 12 deletions(-) diff --git a/plugins/devkit/src/testAssistant/TestDataNavigationHandler.java b/plugins/devkit/src/testAssistant/TestDataNavigationHandler.java index f4851dca0041..50eb17334622 100644 --- a/plugins/devkit/src/testAssistant/TestDataNavigationHandler.java +++ b/plugins/devkit/src/testAssistant/TestDataNavigationHandler.java @@ -129,22 +129,22 @@ public class TestDataNavigationHandler implements GutterIconNavigationHandler() { public VirtualFile compute() { From be332473b7b6e9ec4f36650b6c2bbbd8d72ffe34 Mon Sep 17 00:00:00 2001 From: Yaroslav Pankratyev Date: Wed, 13 Sep 2017 19:22:00 +0300 Subject: [PATCH 017/217] IDEA-178261 Better testdata group title for editor tab and Recent Files --- plugins/devkit/resources/META-INF/plugin.xml | 1 + .../TestDataGroupEditorTabTitleProvider.java | 35 +++++++++++++++++++ .../vfs/TestDataGroupVirtualFile.java | 10 ++---- 3 files changed, 39 insertions(+), 7 deletions(-) create mode 100644 plugins/devkit/src/testAssistant/TestDataGroupEditorTabTitleProvider.java diff --git a/plugins/devkit/resources/META-INF/plugin.xml b/plugins/devkit/resources/META-INF/plugin.xml index c3e3e57a4fd8..d33c1c4156b6 100644 --- a/plugins/devkit/resources/META-INF/plugin.xml +++ b/plugins/devkit/resources/META-INF/plugin.xml @@ -17,6 +17,7 @@ + diff --git a/plugins/devkit/src/testAssistant/TestDataGroupEditorTabTitleProvider.java b/plugins/devkit/src/testAssistant/TestDataGroupEditorTabTitleProvider.java new file mode 100644 index 000000000000..5d4e1af0e823 --- /dev/null +++ b/plugins/devkit/src/testAssistant/TestDataGroupEditorTabTitleProvider.java @@ -0,0 +1,35 @@ +/* + * 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. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jetbrains.idea.devkit.testAssistant; + +import com.intellij.openapi.fileEditor.impl.EditorTabTitleProvider; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.vfs.VirtualFile; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.idea.devkit.testAssistant.vfs.TestDataGroupVirtualFile; + +public class TestDataGroupEditorTabTitleProvider implements EditorTabTitleProvider { + @Nullable + @Override + public String getEditorTabTitle(Project project, VirtualFile file) { + if (!(file instanceof TestDataGroupVirtualFile)) { + return null; + } + + // TestDataGroupVirtualFile.getName() implementation is fine + return file.getName(); + } +} diff --git a/plugins/devkit/src/testAssistant/vfs/TestDataGroupVirtualFile.java b/plugins/devkit/src/testAssistant/vfs/TestDataGroupVirtualFile.java index 0af8ff91a7e6..183219fc0b0d 100644 --- a/plugins/devkit/src/testAssistant/vfs/TestDataGroupVirtualFile.java +++ b/plugins/devkit/src/testAssistant/vfs/TestDataGroupVirtualFile.java @@ -17,8 +17,8 @@ package org.jetbrains.idea.devkit.testAssistant.vfs; import com.intellij.ide.presentation.Presentation; import com.intellij.openapi.fileTypes.FileType; -import com.intellij.openapi.util.text.StringUtil; -import com.intellij.openapi.vfs.*; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.openapi.vfs.VirtualFileSystem; import com.intellij.util.ArrayUtil; import org.jetbrains.annotations.NotNull; @@ -42,11 +42,7 @@ public class TestDataGroupVirtualFile extends VirtualFile { @NotNull @Override public String getName() { - final String prefix = StringUtil.commonPrefix(myBeforeFile.getName(), myAfterFile.getName()); - if (prefix.isEmpty()) { - return StringUtil.commonSuffix(myBeforeFile.getName(), myAfterFile.getName()); - } - return prefix + "." + myBeforeFile.getExtension(); + return myAfterFile.getName() + " | " + myAfterFile.getName(); } @NotNull From 3ddd199ab5f6461509ff14233d939b7fb4d6882d Mon Sep 17 00:00:00 2001 From: Yaroslav Pankratyev Date: Thu, 14 Sep 2017 14:45:46 +0300 Subject: [PATCH 018/217] Add @TestDataFile to additionalFiles parameters in CodeInsightTestFixture --- .../testFramework/fixtures/CodeInsightTestFixture.java | 8 ++++---- .../fixtures/impl/CodeInsightTestFixtureImpl.java | 10 ++++++---- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/platform/testFramework/src/com/intellij/testFramework/fixtures/CodeInsightTestFixture.java b/platform/testFramework/src/com/intellij/testFramework/fixtures/CodeInsightTestFixture.java index a4e4cb754a1f..7f93bcc34042 100644 --- a/platform/testFramework/src/com/intellij/testFramework/fixtures/CodeInsightTestFixture.java +++ b/platform/testFramework/src/com/intellij/testFramework/fixtures/CodeInsightTestFixture.java @@ -389,12 +389,12 @@ public interface CodeInsightTestFixture extends IdeaProjectTestFixture { */ void testCompletion(@TestDataFile @NotNull String fileBefore, @NotNull @TestDataFile String fileAfter, - @NotNull String... additionalFiles); + @TestDataFile @NotNull String... additionalFiles); void testCompletionTyping(@NotNull @TestDataFile String fileBefore, @NotNull String toType, @NotNull @TestDataFile String fileAfter, - @NotNull String... additionalFiles); + @TestDataFile @NotNull String... additionalFiles); /** * Runs basic completion in caret position in fileBefore. @@ -415,7 +415,7 @@ public interface CodeInsightTestFixture extends IdeaProjectTestFixture { void testRename(@NotNull @TestDataFile String fileBefore, @NotNull @TestDataFile String fileAfter, @NotNull String newName, - @NotNull String... additionalFiles); + @TestDataFile @NotNull String... additionalFiles); void testRename(@NotNull @TestDataFile String fileAfter, @NotNull String newName); @@ -428,7 +428,7 @@ public interface CodeInsightTestFixture extends IdeaProjectTestFixture { @NotNull RangeHighlighter[] testHighlightUsages(@NotNull @TestDataFile String... files); - void moveFile(@NotNull @TestDataFile String filePath, @NotNull String to, @NotNull String... additionalFiles); + void moveFile(@NotNull @TestDataFile String filePath, @NotNull String to, @TestDataFile @NotNull String... additionalFiles); /** * Returns gutter renderer at the caret position. diff --git a/platform/testFramework/src/com/intellij/testFramework/fixtures/impl/CodeInsightTestFixtureImpl.java b/platform/testFramework/src/com/intellij/testFramework/fixtures/impl/CodeInsightTestFixtureImpl.java index 20255042c82d..f90853d731f7 100644 --- a/platform/testFramework/src/com/intellij/testFramework/fixtures/impl/CodeInsightTestFixtureImpl.java +++ b/platform/testFramework/src/com/intellij/testFramework/fixtures/impl/CodeInsightTestFixtureImpl.java @@ -639,7 +639,9 @@ public class CodeInsightTestFixtureImpl extends BaseFixture implements CodeInsig } @Override - public void testCompletion(@NotNull String fileBefore, @NotNull String fileAfter, @NotNull final String... additionalFiles) { + public void testCompletion(@NotNull String fileBefore, + @NotNull String fileAfter, + @TestDataFile @NotNull String... additionalFiles) { testCompletionTyping(fileBefore, "", fileAfter, additionalFiles); } @@ -647,7 +649,7 @@ public class CodeInsightTestFixtureImpl extends BaseFixture implements CodeInsig public void testCompletionTyping(@NotNull @TestDataFile String fileBefore, @NotNull String toType, @NotNull @TestDataFile String fileAfter, - @NotNull String... additionalFiles) { + @TestDataFile @NotNull String... additionalFiles) { testCompletionTyping(ArrayUtil.reverseArray(ArrayUtil.append(additionalFiles, fileBefore)), toType, fileAfter); } @@ -692,7 +694,7 @@ public class CodeInsightTestFixtureImpl extends BaseFixture implements CodeInsig public void testRename(@NotNull final String fileBefore, @NotNull String fileAfter, @NotNull String newName, - @NotNull String... additionalFiles) { + @TestDataFile @NotNull String... additionalFiles) { assertInitialized(); configureByFiles(ArrayUtil.reverseArray(ArrayUtil.append(additionalFiles, fileBefore))); testRename(fileAfter, newName); @@ -915,7 +917,7 @@ public class CodeInsightTestFixtureImpl extends BaseFixture implements CodeInsig } @Override - public void moveFile(@NotNull final String filePath, @NotNull final String to, @NotNull final String... additionalFiles) { + public void moveFile(@NotNull final String filePath, @NotNull final String to, @TestDataFile @NotNull final String... additionalFiles) { assertInitialized(); final Project project = getProject(); configureByFiles(ArrayUtil.reverseArray(ArrayUtil.append(additionalFiles, filePath))); From 34a8b3fe998705906076e3f1fceab6826e0dead0 Mon Sep 17 00:00:00 2001 From: Yaroslav Pankratyev Date: Thu, 14 Sep 2017 15:38:27 +0300 Subject: [PATCH 019/217] IDEA-178261 Fix wrong testdata group display name --- .../devkit/src/testAssistant/vfs/TestDataGroupVirtualFile.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/devkit/src/testAssistant/vfs/TestDataGroupVirtualFile.java b/plugins/devkit/src/testAssistant/vfs/TestDataGroupVirtualFile.java index 183219fc0b0d..b1aa9d9f0def 100644 --- a/plugins/devkit/src/testAssistant/vfs/TestDataGroupVirtualFile.java +++ b/plugins/devkit/src/testAssistant/vfs/TestDataGroupVirtualFile.java @@ -42,7 +42,7 @@ public class TestDataGroupVirtualFile extends VirtualFile { @NotNull @Override public String getName() { - return myAfterFile.getName() + " | " + myAfterFile.getName(); + return myBeforeFile.getName() + " | " + myAfterFile.getName(); } @NotNull From a0ab9375ecee0b9b9347c629f33dcdd18612280f Mon Sep 17 00:00:00 2001 From: Sergey Malenkov Date: Thu, 14 Sep 2017 16:05:33 +0300 Subject: [PATCH 020/217] increase time for performance test and add some logging --- .../intellij/ui/tree/AsyncTreeModelTest.java | 28 +++++++++++++++---- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/platform/platform-tests/testSrc/com/intellij/ui/tree/AsyncTreeModelTest.java b/platform/platform-tests/testSrc/com/intellij/ui/tree/AsyncTreeModelTest.java index a9d6a4b1a95e..4783ebaa7914 100644 --- a/platform/platform-tests/testSrc/com/intellij/ui/tree/AsyncTreeModelTest.java +++ b/platform/platform-tests/testSrc/com/intellij/ui/tree/AsyncTreeModelTest.java @@ -27,6 +27,7 @@ import org.junit.Test; import javax.swing.*; import javax.swing.tree.*; import java.util.Objects; +import java.util.concurrent.TimeoutException; import java.util.function.Consumer; import java.util.function.Supplier; @@ -34,9 +35,10 @@ import static com.intellij.util.ArrayUtil.EMPTY_OBJECT_ARRAY; import static java.util.concurrent.TimeUnit.SECONDS; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; +import static org.junit.Assert.fail; public final class AsyncTreeModelTest { - private final static boolean PRINT = false; + private static final boolean PRINT = false; @Test public void testAggressiveUpdating() { @@ -250,7 +252,7 @@ public final class AsyncTreeModelTest { } private static void testEventDispatchThread(Supplier root, Consumer consumer, boolean showLoadingNode, int delay) { - new AsyncTest(showLoadingNode, new EventDispatchThreadModel(delay, root)).start(consumer, delay + 10); + new AsyncTest(showLoadingNode, new EventDispatchThreadModel(delay, root)).start(consumer, getSecondsToWait(delay)); } private static void testBackgroundThread(Supplier root, Consumer consumer, boolean showLoadingNode) { @@ -260,7 +262,7 @@ public final class AsyncTreeModelTest { } private static void testBackgroundThread(Supplier root, Consumer consumer, boolean showLoadingNode, int delay) { - if (consumer != null) new AsyncTest(showLoadingNode, new BackgroundThreadModel(delay, root)).start(consumer, delay + 10); + if (consumer != null) new AsyncTest(showLoadingNode, new BackgroundThreadModel(delay, root)).start(consumer, getSecondsToWait(delay)); } private static void testBackgroundPool(Supplier root, Consumer consumer, boolean showLoadingNode) { @@ -270,7 +272,7 @@ public final class AsyncTreeModelTest { } private static void testBackgroundPool(Supplier root, Consumer consumer, boolean showLoadingNode, int delay) { - if (consumer != null) new AsyncTest(showLoadingNode, new BackgroundPoolModel(delay, root)).start(consumer, delay + 10); + if (consumer != null) new AsyncTest(showLoadingNode, new BackgroundPoolModel(delay, root)).start(consumer, getSecondsToWait(delay)); } private static void printTime(long time, String postfix) { @@ -294,6 +296,14 @@ public final class AsyncTreeModelTest { }); } + /** + * @param delay a delay used to create a slow tree model + * @return a maximal time in seconds allowed for the test + */ + private static int getSecondsToWait(int delay) { + return delay + 20; + } + private static class ModelTest { private final AsyncPromise promise = new AsyncPromise<>(); private final DefaultTreeModel model; @@ -323,10 +333,18 @@ public final class AsyncTreeModelTest { try { promise.blockingGet(seconds, SECONDS); } + catch (Exception exception) { + //noinspection InstanceofCatchParameter because of Kotlin + if (exception instanceof TimeoutException) { + fail(seconds + " seconds is not enough for " + toString()); + } + throw exception; + } finally { TreeModel model = tree.getModel(); if (model instanceof Disposable) Disposer.dispose((Disposable)model); printTime(time, "ms to done"); + if (PRINT) System.out.println(); } } @@ -445,7 +463,7 @@ public final class AsyncTreeModelTest { } private void pause() { - if (this instanceof InvokerSupplier && .9 < Math.random()) { + if (this instanceof InvokerSupplier && .95 < Math.random()) { // sometimes throw an exception to cancel current operation if (PRINT) System.out.println("interrupt access to model:" + toString()); throw new ProcessCanceledException(); From 4b57c2b8546857fd579695cd114a8dfc385c3068 Mon Sep 17 00:00:00 2001 From: Dmitry Batkovich Date: Thu, 14 Sep 2017 15:54:03 +0300 Subject: [PATCH 021/217] remove accidentally added sout --- .../jetbrains/jps/javac/ast/api/JavacNameTable.java | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/jps/jps-builders-6/src/org/jetbrains/jps/javac/ast/api/JavacNameTable.java b/jps/jps-builders-6/src/org/jetbrains/jps/javac/ast/api/JavacNameTable.java index 9fda9fd6402e..0e07167e19bf 100644 --- a/jps/jps-builders-6/src/org/jetbrains/jps/javac/ast/api/JavacNameTable.java +++ b/jps/jps-builders-6/src/org/jetbrains/jps/javac/ast/api/JavacNameTable.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. @@ -51,13 +51,7 @@ public class JavacNameTable { @NotNull public String parseBinaryName(Element element) { - try { - return parseName(myElements.getBinaryName((TypeElement)element)); - } - catch (ClassCastException e) { - System.out.println(123); - throw e; - } + return parseName(myElements.getBinaryName((TypeElement)element)); } public boolean isAsterisk(Name name) { From 4b1594bb590468fe1c163c2f85a3a4b90a8110b8 Mon Sep 17 00:00:00 2001 From: Sergey Simonchik Date: Thu, 14 Sep 2017 16:17:19 +0300 Subject: [PATCH 022/217] recognize *.opts as plain text files E.g. there is spec.opts (rspec), mocha.opts (node.js), and misc linux configurations in .opts files --- .../openapi/fileTypes/impl/PlatformFileTypeFactory.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/platform/platform-impl/src/com/intellij/openapi/fileTypes/impl/PlatformFileTypeFactory.java b/platform/platform-impl/src/com/intellij/openapi/fileTypes/impl/PlatformFileTypeFactory.java index a84a0d452b84..5fc1ec7ce173 100644 --- a/platform/platform-impl/src/com/intellij/openapi/fileTypes/impl/PlatformFileTypeFactory.java +++ b/platform/platform-impl/src/com/intellij/openapi/fileTypes/impl/PlatformFileTypeFactory.java @@ -25,7 +25,7 @@ import org.jetbrains.annotations.NotNull; public class PlatformFileTypeFactory extends FileTypeFactory { public void createFileTypes(@NotNull final FileTypeConsumer consumer) { consumer.consume(ArchiveFileType.INSTANCE, "zip;jar;war;ear;swc;ane;egg;apk"); - consumer.consume(PlainTextFileType.INSTANCE, "txt;sh;bat;cmd;policy;log;cgi;MF;jad;jam;htaccess;rb"); + consumer.consume(PlainTextFileType.INSTANCE, "txt;sh;bat;cmd;policy;log;cgi;MF;jad;jam;htaccess;rb;opts"); consumer.consume(NativeFileType.INSTANCE, "doc;docx;xls;xlsx;ppt;pptx;mdb;vsd;pdf;hlp;chm;odt"); consumer.consume(UnknownFileType.INSTANCE, "lib;dll;a;so;dylib"); } From 440c09c2f488894cffc72dda139ee7d57f4db880 Mon Sep 17 00:00:00 2001 From: Anna Kozlova Date: Wed, 13 Sep 2017 21:57:20 +0300 Subject: [PATCH 023/217] EA-107305 - NPE: RedundantThrowsDeclarationInspection.checkElement --- .../unneededThrows/RedundantThrowsDeclarationInspection.java | 1 + 1 file changed, 1 insertion(+) diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/unneededThrows/RedundantThrowsDeclarationInspection.java b/java/java-analysis-impl/src/com/intellij/codeInspection/unneededThrows/RedundantThrowsDeclarationInspection.java index e2669f837062..f9216e6a3f14 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/unneededThrows/RedundantThrowsDeclarationInspection.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/unneededThrows/RedundantThrowsDeclarationInspection.java @@ -81,6 +81,7 @@ public class RedundantThrowsDeclarationInspection extends GlobalJavaBatchInspect if (unThrown == null) return null; PsiMethod psiMethod = (PsiMethod)refMethod.getElement(); + if (psiMethod == null) return null; PsiClassType[] throwsList = psiMethod.getThrowsList().getReferencedTypes(); PsiJavaCodeReferenceElement[] throwsRefs = psiMethod.getThrowsList().getReferenceElements(); List problems = null; From 3f35f1c61486a8ae1cb3c7cb5458a86b91fff4ee Mon Sep 17 00:00:00 2001 From: Anna Kozlova Date: Thu, 14 Sep 2017 15:31:47 +0300 Subject: [PATCH 024/217] include api guardian + commons into IDEA generated junit 5 library --- .../junit/JUnitExternalLibraryDescriptor.java | 27 ++++++++++++------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/plugins/junit/src/com/intellij/execution/junit/JUnitExternalLibraryDescriptor.java b/plugins/junit/src/com/intellij/execution/junit/JUnitExternalLibraryDescriptor.java index 25724c6aa7d4..efb8e13b1100 100644 --- a/plugins/junit/src/com/intellij/execution/junit/JUnitExternalLibraryDescriptor.java +++ b/plugins/junit/src/com/intellij/execution/junit/JUnitExternalLibraryDescriptor.java @@ -15,20 +15,23 @@ */ package com.intellij.execution.junit; +import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.projectRoots.ex.JavaSdkUtil; import com.intellij.openapi.roots.ExternalLibraryDescriptor; import com.intellij.util.PathUtil; -import com.intellij.util.ReflectionUtil; import org.jetbrains.annotations.NotNull; -import java.util.Arrays; import java.util.Collections; import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; +import java.util.stream.Stream; /** * @author nik */ public abstract class JUnitExternalLibraryDescriptor extends ExternalLibraryDescriptor { + private static final Logger LOG = Logger.getInstance(JUnitExternalLibraryDescriptor.class); public static final ExternalLibraryDescriptor JUNIT3 = new JUnitExternalLibraryDescriptor("3") { @NotNull @Override @@ -47,13 +50,19 @@ public abstract class JUnitExternalLibraryDescriptor extends ExternalLibraryDesc @NotNull @Override public List getLibraryClassesRoots() { - try { - return Arrays.asList(PathUtil.getJarPathForClass(Class.forName("org.junit.jupiter.api.Test")), - PathUtil.getJarPathForClass(Class.forName("org.opentest4j.AssertionFailedError"))); - } - catch (ClassNotFoundException e) { - throw new RuntimeException(e); - } + return Stream.of("org.junit.jupiter.api.Test", "org.opentest4j.AssertionFailedError", + "org.apiguardian.api.API", "org.junit.platform.commons.JUnitException") + .map(className -> { + try { + return PathUtil.getJarPathForClass(Class.forName(className)); + } + catch (ClassNotFoundException e) { + LOG.info(e); + } + return null; + }) + .filter(Objects::nonNull) + .collect(Collectors.toList()); } }; private final String myVersion; From 4a7c8c0952401b05d5cfd75c10cf767a49848823 Mon Sep 17 00:00:00 2001 From: Anna Kozlova Date: Thu, 14 Sep 2017 16:36:00 +0300 Subject: [PATCH 025/217] EA-107346 - SOE: RefactoringUtil.collectTypeParametersInDependencies --- .../refactoring/util/RefactoringUtil.java | 16 ++++++++-------- .../TypeParamsListWithRecursiveDependencies.java | 6 ++++++ ...aramsListWithRecursiveDependencies_after.java | 10 ++++++++++ .../java/refactoring/ExtractMethodTest.java | 4 ++++ 4 files changed, 28 insertions(+), 8 deletions(-) create mode 100644 java/java-tests/testData/refactoring/extractMethod/TypeParamsListWithRecursiveDependencies.java create mode 100644 java/java-tests/testData/refactoring/extractMethod/TypeParamsListWithRecursiveDependencies_after.java diff --git a/java/java-impl/src/com/intellij/refactoring/util/RefactoringUtil.java b/java/java-impl/src/com/intellij/refactoring/util/RefactoringUtil.java index 155686ea8938..7c1dc1fd0004 100644 --- a/java/java-impl/src/com/intellij/refactoring/util/RefactoringUtil.java +++ b/java/java-impl/src/com/intellij/refactoring/util/RefactoringUtil.java @@ -1401,14 +1401,14 @@ public class RefactoringUtil { } private static void collectTypeParametersInDependencies(Condition filter, Set used) { - HashSet typeParametersInDependencies = new HashSet<>(); - for (PsiTypeParameter parameter : used) { - collectTypeParameters(typeParametersInDependencies, parameter, filter); - } - typeParametersInDependencies.removeAll(used); - if (!typeParametersInDependencies.isEmpty()) { - collectTypeParametersInDependencies(filter, typeParametersInDependencies); - used.addAll(typeParametersInDependencies); + Stack toProcess = new Stack<>(); + toProcess.addAll(used); + while (!toProcess.isEmpty()) { + PsiTypeParameter parameter = toProcess.pop(); + HashSet dependencies = new HashSet<>(); + collectTypeParameters(dependencies, parameter, param -> filter.value(param) && !used.contains(param)); + used.addAll(dependencies); + toProcess.addAll(dependencies); } } diff --git a/java/java-tests/testData/refactoring/extractMethod/TypeParamsListWithRecursiveDependencies.java b/java/java-tests/testData/refactoring/extractMethod/TypeParamsListWithRecursiveDependencies.java new file mode 100644 index 000000000000..34c63819d3cd --- /dev/null +++ b/java/java-tests/testData/refactoring/extractMethod/TypeParamsListWithRecursiveDependencies.java @@ -0,0 +1,6 @@ +class Test { + + void f(C c) { + System.out.println(c); + } +} \ No newline at end of file diff --git a/java/java-tests/testData/refactoring/extractMethod/TypeParamsListWithRecursiveDependencies_after.java b/java/java-tests/testData/refactoring/extractMethod/TypeParamsListWithRecursiveDependencies_after.java new file mode 100644 index 000000000000..a7aaae5d3ed8 --- /dev/null +++ b/java/java-tests/testData/refactoring/extractMethod/TypeParamsListWithRecursiveDependencies_after.java @@ -0,0 +1,10 @@ +class Test { + + void f(C c) { + newMethod(c); + } + + private void newMethod(C c) { + System.out.println(c); + } +} \ No newline at end of file diff --git a/java/java-tests/testSrc/com/intellij/java/refactoring/ExtractMethodTest.java b/java/java-tests/testSrc/com/intellij/java/refactoring/ExtractMethodTest.java index ab3261bfbedc..a33e72e7f3a3 100644 --- a/java/java-tests/testSrc/com/intellij/java/refactoring/ExtractMethodTest.java +++ b/java/java-tests/testSrc/com/intellij/java/refactoring/ExtractMethodTest.java @@ -679,6 +679,10 @@ public class ExtractMethodTest extends LightCodeInsightTestCase { doTest(); } + public void testTypeParamsListWithRecursiveDependencies() throws Exception { + doTest(); + } + public void testFromLambdaBody() throws Exception { doTest(); } From de00b84f5230aec857d64a70f9de89cf116e4486 Mon Sep 17 00:00:00 2001 From: Yaroslav Pankratyev Date: Thu, 14 Sep 2017 16:41:36 +0300 Subject: [PATCH 026/217] IDEA-178261 Filter out testdata files from other modules --- .../TestDataGuessByExistingFilesUtil.java | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/plugins/devkit/src/testAssistant/TestDataGuessByExistingFilesUtil.java b/plugins/devkit/src/testAssistant/TestDataGuessByExistingFilesUtil.java index 2230bb228883..cc37b7d960d4 100644 --- a/plugins/devkit/src/testAssistant/TestDataGuessByExistingFilesUtil.java +++ b/plugins/devkit/src/testAssistant/TestDataGuessByExistingFilesUtil.java @@ -19,6 +19,8 @@ import com.intellij.codeInsight.AnnotationUtil; import com.intellij.codeInsight.TestFrameworks; import com.intellij.ide.util.gotoByName.GotoFileModel; import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.module.Module; +import com.intellij.openapi.module.ModuleUtilCore; import com.intellij.openapi.progress.ProcessCanceledException; import com.intellij.openapi.progress.ProgressIndicator; import com.intellij.openapi.progress.ProgressManager; @@ -245,6 +247,8 @@ public class TestDataGuessByExistingFilesUtil { if (!completed) { throw new ProcessCanceledException(); } + + filterDirsFromOtherModules(descriptorsByFileNames, project, psiClass); return new TestDataDescriptor(descriptorsByFileNames.values(), possibleFileName); } @@ -260,6 +264,38 @@ public class TestDataGuessByExistingFilesUtil { return processor.getResults(); } + private static void filterDirsFromOtherModules(Map descriptorsByFileNames, + Project project, PsiClass psiClass) { + if (descriptorsByFileNames.size() > 1) { + Module module = ModuleUtilCore.findModuleForFile(psiClass.getContainingFile().getVirtualFile(), project); + if (module == null) { + return; + } + VirtualFile moduleFile = module.getModuleFile(); + if (moduleFile == null) { + return; + } + VirtualFile moduleFileDir = moduleFile.getParent(); + if (moduleFileDir == null) { + return; + } + + String moduleFileDirPath = moduleFileDir.getPath(); + boolean foundInModuleFileDir = false; + for (TestLocationDescriptor descriptor : descriptorsByFileNames.values()) { + if (descriptor.dir.startsWith(moduleFileDirPath)) { + foundInModuleFileDir = true; + break; + } + } + if (!foundInModuleFileDir) { + return; + } + + descriptorsByFileNames.entrySet().removeIf(e -> !e.getValue().dir.startsWith(moduleFileDirPath)); + } + } + @Nullable private static String getSimpleClassName(@NotNull PsiClass psiClass) { String result = psiClass.getQualifiedName(); From 8fe474ed6bfd3dcb478ddcbe6d78194746a6e174 Mon Sep 17 00:00:00 2001 From: Roman Shevchenko Date: Thu, 14 Sep 2017 17:17:15 +0300 Subject: [PATCH 027/217] [tests] ignores connection problems in live update parsing test --- .../ide/updates/UpdateInfoParsingTest.kt | 24 ++++++++++++++----- 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/platform/platform-tests/testSrc/com/intellij/ide/updates/UpdateInfoParsingTest.kt b/platform/platform-tests/testSrc/com/intellij/ide/updates/UpdateInfoParsingTest.kt index 88b105a492a7..400f3756b18b 100644 --- a/platform/platform-tests/testSrc/com/intellij/ide/updates/UpdateInfoParsingTest.kt +++ b/platform/platform-tests/testSrc/com/intellij/ide/updates/UpdateInfoParsingTest.kt @@ -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. @@ -20,7 +20,9 @@ import com.intellij.openapi.updateSettings.impl.UpdateChannel import com.intellij.openapi.updateSettings.impl.UpdatesInfo import com.intellij.openapi.util.BuildNumber import com.intellij.util.loadElement +import org.junit.Assume.assumeTrue import org.junit.Test +import java.io.IOException import java.net.URL import java.text.SimpleDateFormat import kotlin.test.assertEquals @@ -28,13 +30,23 @@ import kotlin.test.assertNotNull class UpdateInfoParsingTest { @Test fun liveJetbrainsUpdateFile() { - val info = load(URL("http://www.jetbrains.com/updates/updates.xml").readText()) - assertNotNull(info["IC"]) + try { + val info = load(URL("https://www.jetbrains.com/updates/updates.xml").readText()) + assertNotNull(info["IC"]) + } + catch (e: IOException) { + assumeTrue(e.toString(), false) + } } @Test fun liveAndroidUpdateFile() { - val info = load(URL("https://dl.google.com/android/studio/patches/updates.xml").readText()) - assertNotNull(info["AI"]) + try { + val info = load(URL("https://dl.google.com/android/studio/patches/updates.xml").readText()) + assertNotNull(info["AI"]) + } + catch (e: IOException) { + assumeTrue(e.toString(), false) + } } @Test fun emptyChannels() { @@ -66,7 +78,7 @@ class UpdateInfoParsingTest { - IntelliJ IDEA X RC is available. Please visit hhttp://confluence.jetbrains.net/display/IDEADEV/IDEA+X+EAP to learn more. + IntelliJ IDEA X RC is available. Please visit http://confluence.jetbrains.net/display/IDEADEV/IDEA+X+EAP to learn more.