Skip to content

Commit

Permalink
Introduce MoreUnit Code Mining - Fix #94
Browse files Browse the repository at this point in the history
  • Loading branch information
RoiSoleil committed Dec 12, 2023
1 parent b60a372 commit 88c9372
Show file tree
Hide file tree
Showing 7 changed files with 319 additions and 1 deletion.
3 changes: 2 additions & 1 deletion org.moreunit.plugin/META-INF/MANIFEST.MF
Original file line number Diff line number Diff line change
Expand Up @@ -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: .
Expand Down
25 changes: 25 additions & 0 deletions org.moreunit.plugin/plugin.xml
Original file line number Diff line number Diff line change
Expand Up @@ -560,4 +560,29 @@
</viewShortcut>
</perspectiveExtension>
</extension>
<extension
point="org.eclipse.ui.workbench.texteditor.codeMiningProviders">
<codeMiningProvider
class="org.moreunit.codemining.MoreUnitCodeMiningProvider"
id="org.moreunit.MoreUnitCodeMiningProvider"
label="MoreUnit Code Mining">
<enabledWhen>
<and>
<with variable="editor">
<instanceof value="org.eclipse.jdt.internal.ui.javaeditor.JavaEditor" />
</with>
<with variable="editorInput">
<or>
<adapt type="org.eclipse.core.resources.IFile">
<test property="org.eclipse.core.resources.contentTypeId" value="org.eclipse.jdt.core.javaSource" />
</adapt>
<adapt type="org.eclipse.jdt.core.IClassFile">
<instanceof value="org.eclipse.jdt.core.IClassFile" />
</adapt>
</or>
</with>
</and>
</enabledWhen>
</codeMiningProvider>
</extension>
</plugin>
152 changes: 152 additions & 0 deletions org.moreunit.plugin/src/org/moreunit/codemining/JumpCodeMining.java
Original file line number Diff line number Diff line change
@@ -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<Void> 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<MouseEvent> 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());
}
}
}
Original file line number Diff line number Diff line change
@@ -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<List< ? extends ICodeMining>> 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<ICodeMining> 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<ICodeMining> 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
}
}
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -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";
Expand Down
14 changes: 14 additions & 0 deletions org.moreunit.plugin/src/org/moreunit/preferences/Preferences.java
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -108,6 +109,7 @@ private void createCompositeWith2ColsParent(final Composite parentWith2Cols)
createExtendedSearchCheckboxes(parentWith2Cols);
createTestAnnotationModeRadioButtons(parentWith2Cols);
createAddCommentsToTestMethodsCheckbox(parentWith2Cols);
createEnableMoreUnitCodeMiningCheckbox(parentWith2Cols);

checkStateOfMethodPrefixButton();
}
Expand Down Expand Up @@ -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());
Expand All @@ -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())
{
Expand Down

0 comments on commit 88c9372

Please sign in to comment.