From 88c9372448ec9aacc9b74a255b8589ecef0fbe4c Mon Sep 17 00:00:00 2001 From: RoiSoleil Date: Tue, 12 Dec 2023 23:45:42 +0100 Subject: [PATCH] Introduce MoreUnit Code Mining - Fix #94 --- org.moreunit.plugin/META-INF/MANIFEST.MF | 3 +- org.moreunit.plugin/plugin.xml | 25 +++ .../moreunit/codemining/JumpCodeMining.java | 152 ++++++++++++++++++ .../MoreUnitCodeMiningProvider.java | 113 +++++++++++++ .../preferences/PreferenceConstants.java | 2 + .../org/moreunit/preferences/Preferences.java | 14 ++ .../OtherMoreunitPropertiesBlock.java | 11 ++ 7 files changed, 319 insertions(+), 1 deletion(-) create mode 100644 org.moreunit.plugin/src/org/moreunit/codemining/JumpCodeMining.java create mode 100644 org.moreunit.plugin/src/org/moreunit/codemining/MoreUnitCodeMiningProvider.java diff --git a/org.moreunit.plugin/META-INF/MANIFEST.MF b/org.moreunit.plugin/META-INF/MANIFEST.MF index 0a8f872f..564e01dd 100644 --- a/org.moreunit.plugin/META-INF/MANIFEST.MF +++ b/org.moreunit.plugin/META-INF/MANIFEST.MF @@ -17,7 +17,8 @@ Require-Bundle: org.eclipse.core.runtime, org.eclipse.ui, org.eclipse.ui.workbench.texteditor, org.moreunit.core, - org.eclipse.ui.ide + org.eclipse.ui.ide, + org.eclipse.ui.editors xxEclipse-AutoStart: true Bundle-ActivationPolicy: lazy Bundle-ClassPath: . diff --git a/org.moreunit.plugin/plugin.xml b/org.moreunit.plugin/plugin.xml index 7fc6dd42..ca2a00e8 100644 --- a/org.moreunit.plugin/plugin.xml +++ b/org.moreunit.plugin/plugin.xml @@ -560,4 +560,29 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/org.moreunit.plugin/src/org/moreunit/codemining/JumpCodeMining.java b/org.moreunit.plugin/src/org/moreunit/codemining/JumpCodeMining.java new file mode 100644 index 00000000..05bd300a --- /dev/null +++ b/org.moreunit.plugin/src/org/moreunit/codemining/JumpCodeMining.java @@ -0,0 +1,152 @@ +package org.moreunit.codemining; + +import static org.moreunit.elements.CorrespondingMemberRequest.newCorrespondingMemberRequest; + +import java.util.concurrent.CompletableFuture; +import java.util.function.Consumer; + +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.jdt.core.IClasspathEntry; +import org.eclipse.jdt.core.IJavaElement; +import org.eclipse.jdt.core.IMember; +import org.eclipse.jdt.core.IMethod; +import org.eclipse.jdt.core.IPackageFragmentRoot; +import org.eclipse.jdt.core.ISourceRange; +import org.eclipse.jdt.core.ISourceReference; +import org.eclipse.jdt.core.IType; +import org.eclipse.jdt.core.JavaModelException; +import org.eclipse.jface.text.BadLocationException; +import org.eclipse.jface.text.IDocument; +import org.eclipse.jface.text.ITextViewer; +import org.eclipse.jface.text.codemining.ICodeMining; +import org.eclipse.jface.text.codemining.ICodeMiningProvider; +import org.eclipse.jface.text.codemining.LineEndCodeMining; +import org.eclipse.swt.events.MouseEvent; +import org.eclipse.ui.IEditorPart; +import org.moreunit.elements.CorrespondingMemberRequest; +import org.moreunit.elements.CorrespondingMemberRequest.MemberType; +import org.moreunit.elements.TypeFacade; +import org.moreunit.preferences.Preferences; +import org.moreunit.preferences.Preferences.MethodSearchMode; +import org.moreunit.ui.EditorUI; + +/** + * {@link ICodeMining} to "jump" from tested class/method to test class/method + * and from test class/method to tested class/method + */ +public class JumpCodeMining extends LineEndCodeMining +{ + + private final IJavaElement element; + + public JumpCodeMining(IJavaElement element, IDocument document, ICodeMiningProvider provider) throws JavaModelException, BadLocationException + { + super(document, getLineNumber(element, document), provider); + this.element = element; + } + + private static int getLineNumber(IJavaElement element, IDocument document) throws JavaModelException, BadLocationException + { + ISourceRange r = ((ISourceReference) element).getNameRange(); + int offset = r.getOffset(); + return document.getLineOfOffset(offset); + } + + @Override + protected CompletableFuture doResolve(ITextViewer viewer, IProgressMonitor monitor) + { + String testOrTested = isTest() ? "tested" : "test"; + return CompletableFuture.runAsync(() -> { + if(element instanceof IType) + { + MethodSearchMode searchMode = Preferences.getInstance().getMethodSearchMode(element.getJavaProject()); + + TypeFacade typeFacade = TypeFacade.createFacade(((IMember) element).getCompilationUnit()); + + CorrespondingMemberRequest request = newCorrespondingMemberRequest() // + .withExpectedResultType(MemberType.TYPE_OR_METHOD) // + .withCurrentMethod(element instanceof IMethod method ? method : null) // + .methodSearchMode(searchMode) // + .build(); + + IMember memberToJump = typeFacade.getOneCorrespondingMember(request); + if(memberToJump != null) + { + setLabel(" Jump to " + testOrTested + " class"); + } + } + else if(element instanceof IMethod) + { + MethodSearchMode searchMode = Preferences.getInstance().getMethodSearchMode(element.getJavaProject()); + + TypeFacade typeFacade = TypeFacade.createFacade(((IMember) element).getCompilationUnit()); + + CorrespondingMemberRequest request = newCorrespondingMemberRequest() // + .withExpectedResultType(MemberType.TYPE_OR_METHOD) // + .withCurrentMethod(element instanceof IMethod method ? method : null) // + .methodSearchMode(searchMode) // + .build(); + + IMember memberToJump = typeFacade.getOneCorrespondingMember(request); + if(memberToJump instanceof IMethod) + { + setLabel(" Jump to " + testOrTested + " method"); + } + } + }); + } + + private boolean isTest() + { + IPackageFragmentRoot packageFragmentRoot = (IPackageFragmentRoot) element.getAncestor(IJavaElement.PACKAGE_FRAGMENT_ROOT); + if(packageFragmentRoot != null) + { + try + { + IClasspathEntry resolvedClasspathEntry = packageFragmentRoot.getResolvedClasspathEntry(); + return resolvedClasspathEntry != null && resolvedClasspathEntry.isTest(); + } + catch (JavaModelException e) + { + } + } + return false; + } + + @Override + public Consumer getAction() + { + return e -> { + MethodSearchMode searchMode = Preferences.getInstance().getMethodSearchMode(element.getJavaProject()); + + TypeFacade typeFacade = TypeFacade.createFacade(((IMember) element).getCompilationUnit()); + + CorrespondingMemberRequest request = newCorrespondingMemberRequest() // + .withExpectedResultType(MemberType.TYPE_OR_METHOD) // + .withCurrentMethod(element instanceof IMethod method ? method : null) // + .methodSearchMode(searchMode) // + .build(); + + IMember memberToJump = typeFacade.getOneCorrespondingMember(request); + if(memberToJump != null) + { + jumpToMember(memberToJump); + } + }; + } + + private void jumpToMember(IMember memberToJump) + { + EditorUI editorUI = new EditorUI(); + if(memberToJump instanceof IMethod) + { + IMethod methodToJump = (IMethod) memberToJump; + IEditorPart openedEditor = editorUI.open(methodToJump.getDeclaringType().getParent()); + editorUI.reveal(openedEditor, methodToJump); + } + else + { + editorUI.open(memberToJump.getParent()); + } + } +} diff --git a/org.moreunit.plugin/src/org/moreunit/codemining/MoreUnitCodeMiningProvider.java b/org.moreunit.plugin/src/org/moreunit/codemining/MoreUnitCodeMiningProvider.java new file mode 100644 index 00000000..99a5fb66 --- /dev/null +++ b/org.moreunit.plugin/src/org/moreunit/codemining/MoreUnitCodeMiningProvider.java @@ -0,0 +1,113 @@ +package org.moreunit.codemining; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.CompletableFuture; + +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.jdt.core.IJavaElement; +import org.eclipse.jdt.core.IMethod; +import org.eclipse.jdt.core.IType; +import org.eclipse.jdt.core.ITypeRoot; +import org.eclipse.jdt.core.JavaModelException; +import org.eclipse.jdt.internal.ui.javaeditor.EditorUtility; +import org.eclipse.jdt.internal.ui.javaeditor.JavaEditor; +import org.eclipse.jface.text.BadLocationException; +import org.eclipse.jface.text.ITextViewer; +import org.eclipse.jface.text.codemining.AbstractCodeMiningProvider; +import org.eclipse.jface.text.codemining.ICodeMining; +import org.eclipse.jface.text.codemining.ICodeMiningProvider; +import org.eclipse.ui.texteditor.ITextEditor; +import org.moreunit.preferences.Preferences; + +/** + * {@link ICodeMiningProvider} for MoreUnit. + */ +public class MoreUnitCodeMiningProvider extends AbstractCodeMiningProvider +{ + + private Preferences preferences; + + public MoreUnitCodeMiningProvider() + { + this.preferences = Preferences.getInstance(); + } + + @Override + public CompletableFuture> provideCodeMinings(ITextViewer viewer, IProgressMonitor monitor) + { + ITextEditor textEditor = super.getAdapter(ITextEditor.class); + ITypeRoot unit = EditorUtility.getEditorInputJavaElement(textEditor, true); + if(unit == null || ! preferences.shouldEnableMoreUnitCodeMining(unit.getJavaProject())) + { + return CompletableFuture.completedFuture(Collections.emptyList()); + } + return CompletableFuture.supplyAsync(() -> { + monitor.isCanceled(); + try + { + IJavaElement[] elements = unit.getChildren(); + List minings = new ArrayList<>(elements.length); + collectMinings(unit, textEditor, unit.getChildren(), minings, viewer, monitor); + return minings; + } + catch (JavaModelException e) + { + // Should never occur + } + return Collections.emptyList(); + }); + } + + private void collectMinings(ITypeRoot unit, ITextEditor textEditor, IJavaElement[] elements, List minings, ITextViewer viewer, IProgressMonitor monitor) throws JavaModelException + { + + if(! (textEditor instanceof JavaEditor)) + { + return; + } + + for (IJavaElement element : elements) + { + if(monitor.isCanceled()) + { + return; + } + if(element.getElementType() == IJavaElement.TYPE) + { + collectMinings(unit, textEditor, ((IType) element).getChildren(), minings, viewer, monitor); + } + else if((element.getElementType() != IJavaElement.METHOD)) + { + continue; + } + // support methods, classes + boolean addMining = false; + if(element instanceof IType) + { + IType type = (IType) element; + if(type.isClass()) + { + addMining = true; + } + } + else if(element instanceof IMethod) + { + addMining = true; + } + if(addMining) + { + try + { + minings.add(new JumpCodeMining(element, viewer.getDocument(), this)); + } + catch (BadLocationException e) + { + // Should never occur + } + } + } + } + +} diff --git a/org.moreunit.plugin/src/org/moreunit/preferences/PreferenceConstants.java b/org.moreunit.plugin/src/org/moreunit/preferences/PreferenceConstants.java index 6a410060..588d6220 100644 --- a/org.moreunit.plugin/src/org/moreunit/preferences/PreferenceConstants.java +++ b/org.moreunit.plugin/src/org/moreunit/preferences/PreferenceConstants.java @@ -25,6 +25,7 @@ public interface PreferenceConstants String ENABLE_TEST_METHOD_SEARCH_BY_NAME = "org.moreunit.enableTestMethodSearchByName"; String TEST_CLASS_NAME_TEMPLATE = "org.moreunit.testClassNameTemplate"; String GENERATE_COMMENTS_FOR_TEST_METHOD = "org.moreunit.generateCommentsForTestMethod"; + String ENABLE_MOREUNIT_CODE_MINING = "org.moreunit.enableMoreUnitCodeMining"; String TEST_TYPE = "org.moreunit.test_type"; String TEST_TYPE_VALUE_JUNIT_3 = "junit3"; @@ -62,6 +63,7 @@ public interface PreferenceConstants String TEXT_EXTENDED_TEST_METHOD_SEARCH = "Enable test method search \"by call\""; String TEXT_ENABLE_TEST_METHOD_SEARCH_BY_NAME = "Enable test method search \"by name\""; String TEXT_GENERATE_COMMENTS_FOR_TEST_METHOD = "Generate comments for test methods"; + String TEXT_ENABLE_MOREUNIT_CODEMINING = "Enable MoreUnit Code Mining (Beta)"; String TEXT_ANNOTATION_MODE = "Annotate tested methods"; String TEST_ANNOTATION_MODE_DISABLED = "Disabled"; String TEST_ANNOTATION_MODE_BY_NAME = "Search by method name"; diff --git a/org.moreunit.plugin/src/org/moreunit/preferences/Preferences.java b/org.moreunit.plugin/src/org/moreunit/preferences/Preferences.java index 5f4c7319..fa1c03b8 100644 --- a/org.moreunit.plugin/src/org/moreunit/preferences/Preferences.java +++ b/org.moreunit.plugin/src/org/moreunit/preferences/Preferences.java @@ -478,6 +478,20 @@ public void setGenerateCommentsForTestMethod(IJavaProject javaProject, boolean a getProjectStore(javaProject).setValue(PreferenceConstants.GENERATE_COMMENTS_FOR_TEST_METHOD, addComments); } + public boolean shouldEnableMoreUnitCodeMining(IJavaProject javaProject) + { + if(storeToRead(javaProject).contains(PreferenceConstants.ENABLE_MOREUNIT_CODE_MINING)) + { + return storeToRead(javaProject).getBoolean(PreferenceConstants.ENABLE_MOREUNIT_CODE_MINING); + } + return storeToRead(javaProject).getDefaultBoolean(PreferenceConstants.ENABLE_MOREUNIT_CODE_MINING); + } + + public void setEnableMoreUnitCodeMining(IJavaProject javaProject, boolean enableMoreUnitCodeMining) + { + getProjectStore(javaProject).setValue(PreferenceConstants.ENABLE_MOREUNIT_CODE_MINING, enableMoreUnitCodeMining); + } + public MethodSearchMode getMethodSearchMode(IJavaProject javaProject) { boolean searchByCall = getBooleanValue(PreferenceConstants.EXTENDED_TEST_METHOD_SEARCH, javaProject); diff --git a/org.moreunit.plugin/src/org/moreunit/properties/OtherMoreunitPropertiesBlock.java b/org.moreunit.plugin/src/org/moreunit/properties/OtherMoreunitPropertiesBlock.java index d5856f5e..e67c6db5 100644 --- a/org.moreunit.plugin/src/org/moreunit/properties/OtherMoreunitPropertiesBlock.java +++ b/org.moreunit.plugin/src/org/moreunit/properties/OtherMoreunitPropertiesBlock.java @@ -42,6 +42,7 @@ public class OtherMoreunitPropertiesBlock implements SelectionListener private Text packageSuffixTextField; private Text superClassTextField; private Button addCommentsToTestMethodCheckbox; + private Button enableMoreUnitCodeMining; private Button extendedSearchCheckbox; private Button enableSearchByNameCheckbox; @@ -108,6 +109,7 @@ private void createCompositeWith2ColsParent(final Composite parentWith2Cols) createExtendedSearchCheckboxes(parentWith2Cols); createTestAnnotationModeRadioButtons(parentWith2Cols); createAddCommentsToTestMethodsCheckbox(parentWith2Cols); + createEnableMoreUnitCodeMiningCheckbox(parentWith2Cols); checkStateOfMethodPrefixButton(); } @@ -323,6 +325,14 @@ private void createAddCommentsToTestMethodsCheckbox(Composite parent) addCommentsToTestMethodCheckbox.setSelection(preferences.shouldGenerateCommentsForTestMethod(javaProject)); } + private void createEnableMoreUnitCodeMiningCheckbox(Composite parent) + { + enableMoreUnitCodeMining = new Button(parent, SWT.CHECK); + enableMoreUnitCodeMining.setText(PreferenceConstants.TEXT_ENABLE_MOREUNIT_CODEMINING); + enableMoreUnitCodeMining.setLayoutData(layoutForOneLineControls); + enableMoreUnitCodeMining.setSelection(preferences.shouldGenerateCommentsForTestMethod(javaProject)); + } + public void saveProperties() { preferences.setTestType(javaProject, getSelectedTestType()); @@ -348,6 +358,7 @@ public void saveProperties() preferences.setShouldUseTestMethodExtendedSearch(javaProject, extendedSearchCheckbox.getSelection()); preferences.setShouldUseTestMethodSearchByName(javaProject, enableSearchByNameCheckbox.getSelection()); preferences.setGenerateCommentsForTestMethod(javaProject, addCommentsToTestMethodCheckbox.getSelection()); + preferences.setEnableMoreUnitCodeMining(javaProject, enableMoreUnitCodeMining.getSelection()); if(testAnnotationsByNameAndByCallButton.getSelection()) {