Generators for files and methods

GitOrigin-RevId: 93b45a72817ce4571d2302b4cfd0726730644d9e
This commit is contained in:
Alexey Kryuchkov
2022-07-01 00:31:04 +00:00
committed by intellij-monorepo-bot
parent 1b22bba054
commit 68a5740171
7 changed files with 348 additions and 0 deletions
@@ -2270,6 +2270,7 @@
<notificationGroup id="Remove redundant exports/opens" displayType="BALLOON" bundle="messages.JavaBundle" key="notification.group.redundant.exports"/>
<notificationGroup id="Setup SDK" displayType="BALLOON" bundle="messages.JavaBundle" key="notification.group.setup.sdk"/>
<notificationGroup id="External annotations" displayType="BALLOON" bundle="messages.JavaBundle" key="notification.group.setup.external.annotations"/>
<notificationGroup id="Test integration" displayType="BALLOON" bundle="messages.JavaBundle" key="notification.group.testintegration"/>
<compiler.task execute="AFTER" implementation="com.intellij.execution.scratch.JavaScratchCompilationSupport"/>
<registryKey key="load.maven.dependencies.timeout" defaultValue="120" description="How long (in minutes) idea will wait for results of synchronized maven dependencies resolution"/>
@@ -0,0 +1,109 @@
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.testIntegration;
import com.intellij.codeInsight.AnnotationUtil;
import com.intellij.codeInsight.CodeInsightActionHandler;
import com.intellij.codeInsight.generation.actions.BaseGenerateAction;
import com.intellij.find.FindBundle;
import com.intellij.java.JavaBundle;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.roots.ProjectRootManager;
import com.intellij.openapi.ui.DialogWrapper;
import com.intellij.openapi.ui.ValidationInfo;
import com.intellij.openapi.util.NlsSafe;
import com.intellij.psi.*;
import com.intellij.ui.components.JBPanel;
import com.intellij.ui.components.JBTextField;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.uast.UAnnotation;
import org.jetbrains.uast.UastContextKt;
import javax.swing.*;
import java.awt.*;
import java.util.Collections;
import java.util.List;
class GenerateTestDataPathCommon extends BaseGenerateAction {
@NonNls
private static final String CONTENT_ROOT_VARIABLE = "$CONTENT_ROOT";
@NonNls
private static final String PROJECT_ROOT_VARIABLE = "$PROJECT_ROOT";
@NonNls
protected static final String ANNOTATION_FQN = "com.intellij.testFramework.TestDataPath";
protected static final Logger LOG = Logger.getInstance(GenerateTestDataPathCommon.class);
GenerateTestDataPathCommon(CodeInsightActionHandler handler) {
super(handler);
}
@Nullable
protected static String annotationValue(@NotNull PsiModifierListOwner owner, String annotationFqName) {
var annotationNames = Collections.singleton(annotationFqName);
var nestedClass = owner instanceof PsiClass && ((PsiClass)owner).getContainingClass() != null;
var element = nestedClass
? AnnotationUtil.findAnnotation(owner, annotationNames)
: AnnotationUtil.findAnnotationInHierarchy(owner, annotationNames);
final var annotation = UastContextKt.toUElement(element, UAnnotation.class);
if (annotation != null) {
var value = annotation.findAttributeValue(PsiAnnotation.DEFAULT_REFERENCED_METHOD_NAME);
if (value != null) {
final var project = owner.getProject();
final var constantValue = value.evaluate();
if (constantValue instanceof String) {
var path = (String)constantValue;
if (path.contains(CONTENT_ROOT_VARIABLE)) {
final var fileIndex = ProjectRootManager.getInstance(project).getFileIndex();
final var file = owner.getContainingFile().getVirtualFile();
if (file == null) return null;
final var contentRoot = fileIndex.getContentRootForFile(file);
if (contentRoot == null) return null;
path = path.replace(CONTENT_ROOT_VARIABLE, contentRoot.getPath());
}
if (path.contains(PROJECT_ROOT_VARIABLE)) {
var baseDir = project.getBasePath();
if (baseDir == null) {
return null;
}
path = path.replace(PROJECT_ROOT_VARIABLE, baseDir);
}
return path;
}
}
}
return null;
}
@Override
protected boolean isValidForClass(PsiClass targetClass) {
return AnnotationUtil.findAnnotation(targetClass, "com.intellij.testFramework.TestDataPath") != null;
}
protected static abstract class TestDataPathDialog extends DialogWrapper {
protected TestDataPathDialog(@Nullable Project project, @NlsSafe @NotNull String titleText, boolean canBeParent) {
super(project, canBeParent);
setTitle(titleText);
setOKButtonText(JavaBundle.message("generate.button.title"));
init();
}
@Override
protected @Nullable JComponent createCenterPanel() {
var mainPanel = createMainPanel();
getPanelContent().forEach(mainPanel::add);
return mainPanel;
}
abstract protected List<Component> getPanelContent();
protected JBPanel createMainPanel() {
var mainPanel = new JBPanel<>().withPreferredWidth(250);
var layout = new BoxLayout(mainPanel, BoxLayout.Y_AXIS);
mainPanel.setLayout(layout);
return mainPanel;
}
}
}
@@ -0,0 +1,119 @@
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.testIntegration;
import com.intellij.java.JavaBundle;
import com.intellij.notification.NotificationGroupManager;
import com.intellij.notification.NotificationType;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.command.WriteCommandAction;
import com.intellij.openapi.ui.ValidationInfo;
import com.intellij.openapi.vfs.LocalFileSystem;
import com.intellij.openapi.vfs.VirtualFileManager;
import com.intellij.psi.PsiClass;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.ui.components.*;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.awt.*;
import java.io.IOException;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class GenerateTestDataPathFileAction extends GenerateTestDataPathCommon {
public GenerateTestDataPathFileAction() {
super(((project, editor, file) -> {
var currentClass = PsiTreeUtil.findChildOfType(file, PsiClass.class);
if (currentClass == null) return;
var annotationValue = annotationValue(currentClass, ANNOTATION_FQN);
if (annotationValue == null) return;
var directoryPath = LocalFileSystem.getInstance().findFileByPath(annotationValue);
if (directoryPath == null || !directoryPath.isDirectory() || directoryPath.getChildren() == null) {
NotificationGroupManager.getInstance().getNotificationGroup("Test integration")
.createNotification(JavaBundle.message("generate.method.nosuites.warn", annotationValue), NotificationType.WARNING)
.notify(project);
return;
}
var suggestedFileNames = Arrays
.stream(currentClass.getAllMethods())
.filter(method -> method.getName().startsWith("test"))
.map(method -> method.getName().substring(4))
.collect(Collectors.toSet());
if (suggestedFileNames.isEmpty()) {
NotificationGroupManager.getInstance().getNotificationGroup("Test integration")
.createNotification(JavaBundle.message("generate.method.nofiles.warn", currentClass.getName(), annotationValue),
NotificationType.WARNING)
.notify(project);
return;
}
var dialog = new TestDataPathDialog(project, JavaBundle.message("dialog.title.testdatapath.file.generate"), false) {
private JBList<String> suggestedFileList;
private JCheckBox qfCheckbox;
private JBTextField extensionField;
@Override
protected List<Component> getPanelContent() {
suggestedFileList = new JBList<String>(suggestedFileNames);
suggestedFileList.setSelectionInterval(0, suggestedFileList.getItemsCount());
var scrollPane = new JBScrollPane(suggestedFileList);
scrollPane.setAlignmentX(Component.LEFT_ALIGNMENT);
qfCheckbox = new JCheckBox();
qfCheckbox.setText(JavaBundle.message("generate.quickfix.files"));
qfCheckbox.setAlignmentX(Component.LEFT_ALIGNMENT);
qfCheckbox.setSelected(false);
extensionField = new JBTextField();
extensionField.getEmptyText().setText(JavaBundle.message("generate.file.extension.text"));
extensionField.setAlignmentX(Component.LEFT_ALIGNMENT);
return List.of(scrollPane, qfCheckbox, extensionField);
}
@Override
protected void doOKAction() {
getSelectedMethod().forEach(fileName -> {
var requester = new Object();
var folder = VirtualFileManager.getInstance().findFileByNioPath(Path.of(annotationValue));
WriteCommandAction.runWriteCommandAction(project, (Runnable)() -> {
try {
folder.createChildData(requester, fileName + extensionField.getText());
if (isQFFileNeeded()) folder.createChildData(requester, fileName + ".after" + extensionField.getText());
}
catch (IOException | NullPointerException e) {
LOG.warn(e);
}
});
});
super.doOKAction();
}
@Override
protected @Nullable ValidationInfo doValidate() {
var in = extensionField.getText().trim();
if (in.isBlank() || !in.startsWith(".") || in.contains(" ")) {
return new ValidationInfo(JavaBundle.message("generate.file.extension.validation.error", in));
}
return null;
}
List<String> getSelectedMethod() { return suggestedFileList.getSelectedValuesList(); }
boolean isQFFileNeeded() { return qfCheckbox.isSelected(); }
};
ApplicationManager.getApplication().invokeLater(() -> dialog.show());
})
);
}
}
@@ -0,0 +1,106 @@
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.testIntegration;
import com.intellij.java.JavaBundle;
import com.intellij.notification.NotificationGroupManager;
import com.intellij.notification.NotificationType;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.command.WriteCommandAction;
import com.intellij.openapi.ui.ComboBox;
import com.intellij.openapi.vfs.LocalFileSystem;
import com.intellij.psi.PsiClass;
import com.intellij.psi.PsiElementFactory;
import com.intellij.psi.PsiMethod;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.ui.components.JBList;
import com.intellij.ui.components.JBScrollPane;
import org.jetbrains.annotations.NotNull;
import javax.lang.model.element.Modifier;
import java.awt.*;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.stream.Collectors;
public class GenerateTestDataPathMethodAction extends GenerateTestDataPathCommon {
public GenerateTestDataPathMethodAction() {
super(((project, editor, file) -> {
var currentClass = PsiTreeUtil.findChildOfType(file, PsiClass.class);
if (currentClass == null) return;
var existingMethodNames = Arrays
.stream(currentClass.getAllMethods())
.map(PsiMethod::getName)
.filter(name -> name.startsWith("test"))
.collect(Collectors.toCollection(HashSet<String>::new));
var annotationValue = annotationValue(currentClass, ANNOTATION_FQN);
if (annotationValue == null) {
return;
}
var directoryPath = LocalFileSystem.getInstance().findFileByPath(annotationValue);
if (directoryPath == null || !directoryPath.isDirectory() || directoryPath.getChildren() == null) {
NotificationGroupManager.getInstance().getNotificationGroup("Test integration")
.createNotification(JavaBundle.message("generate.method.nosuites.warn", annotationValue), NotificationType.WARNING)
.notify(project);
return;
}
var preparedMethodNames = Arrays.stream(directoryPath.getChildren())
.filter(vFile -> vFile.getExtension() != null && (!vFile.getExtension().equals("java") || !vFile.getExtension().equals("kt")))
.map(vFile -> "test" + capitalizeFirstChar(normalizeMethodName(vFile.getName())))
.filter(methodName -> !existingMethodNames.contains(methodName))
.collect(Collectors.toSet());
var dialog = new TestDataPathDialog(project, JavaBundle.message("dialog.title.testdatapath.method.generate"), false) {
private JBList<String> suggestedFileList;
private ComboBox<String> selectModifierBox;
@Override
protected List<Component> getPanelContent() {
suggestedFileList = new JBList<>(preparedMethodNames);
suggestedFileList.setSelectionInterval(0, suggestedFileList.getItemsCount());
var scrollPane = new JBScrollPane(suggestedFileList);
scrollPane.setAlignmentX(Component.LEFT_ALIGNMENT);
final var choices = new String[]{JavaBundle.message("generate.select.default.modifier.text"), Modifier.PUBLIC.toString(), Modifier.PROTECTED.toString(), Modifier.PRIVATE.toString()};
selectModifierBox = new ComboBox<String>(choices);
selectModifierBox.setAlignmentX(Component.LEFT_ALIGNMENT);
return List.of(scrollPane, selectModifierBox);
}
@Override
protected void doOKAction() {
suggestedFileList.getSelectedValuesList().forEach(methodName -> {
WriteCommandAction.runWriteCommandAction(project, (Runnable)() -> currentClass.add(
PsiElementFactory.getInstance(project).createMethodFromText(getSelectedModifier() + "void " + methodName + "(){ }", currentClass))
);
});
super.doOKAction();
}
String getSelectedModifier() {
return selectModifierBox.getSelectedIndex() == 0 ? "" : selectModifierBox.getSelectedItem().toString() + " ";
}
};
ApplicationManager.getApplication().invokeLater(() -> dialog.show());
})
);
}
private static String capitalizeFirstChar(@NotNull String inString) {
if (Character.isUpperCase(inString.charAt(0))) return inString;
if (inString.length() == 1) return inString.toUpperCase();
return inString.charAt(0) + inString.substring(1);
}
private static String normalizeMethodName(@NotNull String inString) {
return inString.substring(0, inString.indexOf("."));
}
}
@@ -223,6 +223,8 @@ dialog.title.infer.nullity.results=Infer Nullity Results
dialog.title.no.test.roots.found=No Test Roots Found
dialog.title.process.duplicates=Process Duplicates
dialog.title.super.method.found=Super Method Found
dialog.title.testdatapath.method.generate=Generate methods from @TestDataPath
dialog.title.testdatapath.file.generate=Generate files from class to @TestDataPath
do.not.import.inner.classes.for=Exclude inner classes by short name:
do.not.import.inner.classes.no.classes=No inner classes defined
editbox.blanklines.around.initializer=Around initializer:
@@ -325,6 +327,12 @@ generate.tostring.sort.super=Super class members first
generate.tostring.tab.title=toString() Generation Settings
generate.tostring.template.label=&Template:
generate.tostring.title=Generate toString()
generate.quickfix.files=Create QF scenarios(with '.after' suffix)
generate.select.default.modifier.text=Select modifier
generate.file.extension.text=Enter file extension: .php, .java, .kt, .cpp ....
generate.file.extension.validation.error=File extension ''{0}'' isn't valid. It must start from ''.'', can't contain space symbols or be blank
generate.method.nosuites.warn=No test suites were found in @TestDataPath("{0}")
generate.method.nofiles.warn=No test methods were found in class {0}
goto.super.class.chooser.title=Choose super class or interface
group.javadoc.alignment=Alignment
group.javadoc.blank.lines=Blank lines
@@ -1738,6 +1746,7 @@ notification.group.preview.features=Preview Java language level may be discontin
notification.group.redundant.exports=Redundant exports/opens can be removed
notification.group.setup.sdk=JDK configured
notification.group.setup.external.annotations=Failed to load external annotations
notification.group.testintegration=Failed to generate tests for @TestDataPath
popup.title.effective.visibility=Effective Visibility
inspection.io.stream.constructor.description='InputStream' and 'OutputStream' can be constructed using 'Files' methods
inspection.input.stream.constructor.message='InputStream' can be constructed using 'Files.newInputStream'
@@ -227,6 +227,8 @@ action.GenerateBeforeClassMethod.text=BeforeClass Method
action.GenerateTearDownMethod.text=TearDown Method
action.GenerateAfterClassMethod.text=AfterClass Method
action.GenerateDataMethod.text=Parameters Method
action.GenerateTestDataPathMethods.text=@TestDataPath Methods
action.GenerateTestDataPathFiles.text=@TestDataPath Files
action.GenerateConstructor.text=Constructor
action.GenerateConstructor.description=Generate constructor
action.GenerateCreateUI.text=createUI(...)
+2
View File
@@ -280,6 +280,8 @@
<action id="GenerateBeforeClassMethod" class="com.intellij.testIntegration.GenerateBeforeClassMethodAction"/>
<action id="GenerateAfterClassMethod" class="com.intellij.testIntegration.GenerateAfterClassMethodAction"/>
<action id="GenerateDataMethod" class="com.intellij.testIntegration.GenerateDataMethodAction"/>
<action id="GenerateTestDataPathMethods" class="com.intellij.testIntegration.GenerateTestDataPathMethodAction"/>
<action id="GenerateTestDataPathFiles" class="com.intellij.testIntegration.GenerateTestDataPathFileAction"/>
<separator/>
<action id="GenerateConstructor" class="com.intellij.codeInsight.generation.actions.GenerateConstructorAction"/>
<action id="GenerateGetter" class="com.intellij.codeInsight.generation.actions.GenerateGetterAction"/>