From 60d33e005b979bd459afb24c81314750a995f215 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christoph=20L=C3=A4ubrich?= Date: Sun, 19 Jan 2025 19:57:14 +0100 Subject: [PATCH] Create new tycho-cleancode plugin Currently there are two categories in the IDE that allows automatic code changes: 1) QuickFix that allows to resolve an error/warning automatically 2) CleanUp that allows to modernize or fix a more generic form of problem Tycho now has support to apply these automatically to a given project codebase to automate this process especially when the code evolves or new warnings occur due to changed dependencies. --- pom.xml | 1 + .../.settings/org.eclipse.jdt.core.prefs | 8 ++ tycho-cleancode-plugin/pom.xml | 64 +++++++++ .../org/eclipse/tycho/cleancode/CleanUp.java | 128 ++++++++++++++++++ .../eclipse/tycho/cleancode/CleanUpMojo.java | 62 +++++++++ .../tycho/cleancode/CleanupResult.java | 33 +++++ .../org/eclipse/tycho/cleancode/QuickFix.java | 109 +++++++++++++++ .../eclipse/tycho/cleancode/QuickFixMojo.java | 44 ++++++ .../tycho/cleancode/QuickFixResult.java | 19 +++ .../eclipsebuild/AbstractEclipseBuild.java | 10 ++ 10 files changed, 478 insertions(+) create mode 100644 tycho-cleancode-plugin/.settings/org.eclipse.jdt.core.prefs create mode 100644 tycho-cleancode-plugin/pom.xml create mode 100644 tycho-cleancode-plugin/src/main/java/org/eclipse/tycho/cleancode/CleanUp.java create mode 100644 tycho-cleancode-plugin/src/main/java/org/eclipse/tycho/cleancode/CleanUpMojo.java create mode 100644 tycho-cleancode-plugin/src/main/java/org/eclipse/tycho/cleancode/CleanupResult.java create mode 100644 tycho-cleancode-plugin/src/main/java/org/eclipse/tycho/cleancode/QuickFix.java create mode 100644 tycho-cleancode-plugin/src/main/java/org/eclipse/tycho/cleancode/QuickFixMojo.java create mode 100644 tycho-cleancode-plugin/src/main/java/org/eclipse/tycho/cleancode/QuickFixResult.java diff --git a/pom.xml b/pom.xml index f31487437f..e5101ee219 100644 --- a/pom.xml +++ b/pom.xml @@ -596,6 +596,7 @@ tycho-repository-plugin tycho-eclipse-plugin tycho-wrap-plugin + tycho-cleancode-plugin diff --git a/tycho-cleancode-plugin/.settings/org.eclipse.jdt.core.prefs b/tycho-cleancode-plugin/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 0000000000..eeac0e762f --- /dev/null +++ b/tycho-cleancode-plugin/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,8 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.targetPlatform=17 +org.eclipse.jdt.core.compiler.compliance=17 +org.eclipse.jdt.core.compiler.problem.enablePreviewFeatures=disabled +org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning +org.eclipse.jdt.core.compiler.problem.reportPreviewFeatures=ignore +org.eclipse.jdt.core.compiler.release=enabled +org.eclipse.jdt.core.compiler.source=17 diff --git a/tycho-cleancode-plugin/pom.xml b/tycho-cleancode-plugin/pom.xml new file mode 100644 index 0000000000..74848cd3c8 --- /dev/null +++ b/tycho-cleancode-plugin/pom.xml @@ -0,0 +1,64 @@ + + 4.0.0 + + org.eclipse.tycho + tycho + 5.0.0-SNAPSHOT + + tycho-cleancode-plugin + Tycho Eclipse Plugin + maven-plugin + + ${minimal-maven-version} + + Maven Plugins for performing automatic code clean options + + + org.apache.maven + maven-core + + + org.apache.maven + maven-plugin-api + + + org.apache.maven.plugin-tools + maven-plugin-annotations + + + org.eclipse.tycho + tycho-eclipse-plugin + ${project.version} + + + org.eclipse.jdt + org.eclipse.jdt.ui + 3.33.200 + + + org.eclipse.jdt + org.eclipse.jdt.core.manipulation + 1.21.300 + + + + + + + org.codehaus.plexus + plexus-component-metadata + + + org.apache.maven.plugins + maven-plugin-plugin + + + tycho-cleancode + + + + + \ No newline at end of file diff --git a/tycho-cleancode-plugin/src/main/java/org/eclipse/tycho/cleancode/CleanUp.java b/tycho-cleancode-plugin/src/main/java/org/eclipse/tycho/cleancode/CleanUp.java new file mode 100644 index 0000000000..6c85f26204 --- /dev/null +++ b/tycho-cleancode-plugin/src/main/java/org/eclipse/tycho/cleancode/CleanUp.java @@ -0,0 +1,128 @@ +/******************************************************************************* + * Copyright (c) 2025 Christoph Läubrich and others. + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Christoph Läubrich - initial API and implementation + *******************************************************************************/ +package org.eclipse.tycho.cleancode; + +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +import org.eclipse.core.resources.IProject; +import org.eclipse.core.resources.ProjectScope; +import org.eclipse.jdt.core.ICompilationUnit; +import org.eclipse.jdt.core.IJavaElement; +import org.eclipse.jdt.core.IJavaProject; +import org.eclipse.jdt.core.IPackageFragment; +import org.eclipse.jdt.core.IPackageFragmentRoot; +import org.eclipse.jdt.core.JavaCore; +import org.eclipse.jdt.core.JavaModelException; +import org.eclipse.jdt.internal.corext.fix.CleanUpPreferenceUtil; +import org.eclipse.jdt.internal.corext.fix.CleanUpRefactoring; +import org.eclipse.jdt.internal.ui.JavaPlugin; +import org.eclipse.jdt.internal.ui.fix.MapCleanUpOptions; +import org.eclipse.jdt.ui.cleanup.CleanUpOptions; +import org.eclipse.jdt.ui.cleanup.ICleanUp; +import org.eclipse.ltk.core.refactoring.Change; +import org.eclipse.ltk.core.refactoring.PerformChangeOperation; +import org.eclipse.ltk.core.refactoring.RefactoringStatus; +import org.eclipse.tycho.eclipsebuild.AbstractEclipseBuild; + +public class CleanUp extends AbstractEclipseBuild { + + private Map customProfile; + + CleanUp(Path projectDir, boolean debug, Map customProfile) { + super(projectDir, debug); + this.customProfile = customProfile; + } + + @Override + protected CleanupResult createResult(IProject project) throws Exception { + CleanupResult result = new CleanupResult(); + CleanUpOptions options = getOptions(project); + ICleanUp[] cleanups = getCleanups(result, options); + if (cleanups.length > 0) { + List units = getCompilationUnits(project); + final CleanUpRefactoring refactoring = new CleanUpRefactoring(project.getName()); + for (ICompilationUnit cu : units) { + refactoring.addCompilationUnit(cu); + } + refactoring.setUseOptionsFromProfile(false); + for (ICleanUp cleanUp : cleanups) { + refactoring.addCleanUp(cleanUp); + } + final RefactoringStatus status = refactoring.checkAllConditions(this); + if (status.isOK()) { + Change change = refactoring.createChange(this); + change.initializeValidationData(this); + PerformChangeOperation performChangeOperation = new PerformChangeOperation(change); + performChangeOperation.run(this); + } else if (status.hasError()) { + throw new RuntimeException("Refactoring failed: " + status); + } + } + return result; + } + + private List getCompilationUnits(IProject project) throws JavaModelException { + IJavaProject javaProject = JavaCore.create(project); + List units = new ArrayList(); + IPackageFragmentRoot[] packages = javaProject.getPackageFragmentRoots(); + for (IPackageFragmentRoot root : packages) { + if (root.getKind() == IPackageFragmentRoot.K_SOURCE) { + for (IJavaElement javaElement : root.getChildren()) { + if (javaElement.getElementType() == IJavaElement.PACKAGE_FRAGMENT) { + IPackageFragment pf = (IPackageFragment) javaElement; + ICompilationUnit[] compilationUnits = pf.getCompilationUnits(); + for (ICompilationUnit compilationUnit : compilationUnits) { + units.add(compilationUnit); + } + } + } + } + } + return units; + } + + private ICleanUp[] getCleanups(CleanupResult result, CleanUpOptions options) { + ICleanUp[] cleanUps = JavaPlugin.getDefault().getCleanUpRegistry().createCleanUps(); + for (ICleanUp cleanUp : cleanUps) { + try { + cleanUp.setOptions(options); + String[] descriptions = cleanUp.getStepDescriptions(); + if (descriptions != null) { + for (String description : descriptions) { + result.addCleanup(description); + } + } + } catch (Exception e) { + debug("Ignore cleanup '" + cleanUp + "' because of initialization error.", e); + } + } + return cleanUps; + } + + private CleanUpOptions getOptions(IProject project) { + Map options; + if (customProfile == null || customProfile.isEmpty()) { + options = CleanUpPreferenceUtil.loadOptions(new ProjectScope(project)); + } else { + options = customProfile; + } + debug("Cleanup Profile: " + options.entrySet().stream().map(e -> e.getKey() + " = " + e.getValue()) + .collect(Collectors.joining(System.lineSeparator()))); + return new MapCleanUpOptions(options); + } + +} diff --git a/tycho-cleancode-plugin/src/main/java/org/eclipse/tycho/cleancode/CleanUpMojo.java b/tycho-cleancode-plugin/src/main/java/org/eclipse/tycho/cleancode/CleanUpMojo.java new file mode 100644 index 0000000000..6b26d59ca0 --- /dev/null +++ b/tycho-cleancode-plugin/src/main/java/org/eclipse/tycho/cleancode/CleanUpMojo.java @@ -0,0 +1,62 @@ +/******************************************************************************* + * Copyright (c) 2023 Christoph Läubrich and others. + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Christoph Läubrich - initial API and implementation + *******************************************************************************/ +package org.eclipse.tycho.cleancode; + +import java.util.Map; + +import org.apache.maven.plugin.MojoFailureException; +import org.apache.maven.plugin.logging.Log; +import org.apache.maven.plugins.annotations.LifecyclePhase; +import org.apache.maven.plugins.annotations.Mojo; +import org.apache.maven.plugins.annotations.Parameter; +import org.apache.maven.plugins.annotations.ResolutionScope; +import org.eclipse.tycho.eclipsebuild.AbstractEclipseBuildMojo; + +/** + * This mojo allows to perform eclipse cleanup action + */ +@Mojo(name = "cleanup", defaultPhase = LifecyclePhase.PROCESS_SOURCES, threadSafe = true, requiresDependencyCollection = ResolutionScope.COMPILE_PLUS_RUNTIME) +public class CleanUpMojo extends AbstractEclipseBuildMojo { + + /** + * Defines key value pairs of a cleanup profile, if not defined will use the + * project defaults + */ + @Parameter + private Map cleanUpProfile; + + @Override + protected String[] getRequireBundles() { + return new String[] { "org.eclipse.jdt.ui" }; + } + + @Override + protected String getName() { + return "Perform Cleanup"; + } + + @Override + protected CleanUp createExecutable() { + return new CleanUp(project.getBasedir().toPath(), debug, cleanUpProfile); + } + + @Override + protected void handleResult(CleanupResult result) + throws MojoFailureException { + Log log = getLog(); + result.cleanups().forEach(cleanup -> { + log.info("Performed cleanup " + cleanup); + }); + } + +} diff --git a/tycho-cleancode-plugin/src/main/java/org/eclipse/tycho/cleancode/CleanupResult.java b/tycho-cleancode-plugin/src/main/java/org/eclipse/tycho/cleancode/CleanupResult.java new file mode 100644 index 0000000000..2982ca4edd --- /dev/null +++ b/tycho-cleancode-plugin/src/main/java/org/eclipse/tycho/cleancode/CleanupResult.java @@ -0,0 +1,33 @@ +/******************************************************************************* + * Copyright (c) 2025 Christoph Läubrich and others. + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Christoph Läubrich - initial API and implementation + *******************************************************************************/ +package org.eclipse.tycho.cleancode; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Stream; + +import org.eclipse.tycho.eclipsebuild.EclipseBuildResult; + +public class CleanupResult extends EclipseBuildResult { + + private List cleanups = new ArrayList<>(); + + public void addCleanup(String cleanup) { + cleanups.add(cleanup); + } + + public Stream cleanups() { + return this.cleanups.stream(); + } + +} diff --git a/tycho-cleancode-plugin/src/main/java/org/eclipse/tycho/cleancode/QuickFix.java b/tycho-cleancode-plugin/src/main/java/org/eclipse/tycho/cleancode/QuickFix.java new file mode 100644 index 0000000000..a021885561 --- /dev/null +++ b/tycho-cleancode-plugin/src/main/java/org/eclipse/tycho/cleancode/QuickFix.java @@ -0,0 +1,109 @@ +/******************************************************************************* + * Copyright (c) 2025 Christoph Läubrich and others. + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Christoph Läubrich - initial API and implementation + *******************************************************************************/ +package org.eclipse.tycho.cleancode; + +import java.nio.file.Path; +import java.util.Arrays; +import java.util.Comparator; + +import org.eclipse.core.resources.IMarker; +import org.eclipse.core.resources.IProject; +import org.eclipse.core.resources.IResource; +import org.eclipse.core.runtime.NullProgressMonitor; +import org.eclipse.tycho.eclipsebuild.AbstractEclipseBuild; +import org.eclipse.ui.IMarkerResolution; +import org.eclipse.ui.IMarkerResolution2; +import org.eclipse.ui.IMarkerResolutionRelevance; +import org.eclipse.ui.ide.IDE; +import org.eclipse.ui.views.markers.WorkbenchMarkerResolution; + +public class QuickFix extends AbstractEclipseBuild { + + QuickFix(Path projectDir, boolean debug) { + super(projectDir, debug); + } + + @Override + protected QuickFixResult createResult(IProject project) throws Exception { + QuickFixResult result = new QuickFixResult(); + for (IMarker marker : project.findMarkers(IMarker.PROBLEM, true, IResource.DEPTH_INFINITE)) { + debug("Marker: " + marker); + try { + IMarkerResolution[] resolutions = IDE.getMarkerHelpRegistry().getResolutions(marker); + debug("Marker has " + resolutions.length + " resolutions"); + IMarkerResolution resolution = Arrays.stream(resolutions) + .max(Comparator.comparingInt(r -> getRelevance(r))).orElse(null); + if (resolution != null) { + debug("Apply best resolution to marker: " + getInfo(resolution)); + // must use an own thread to make sure it is not called as a job + Thread thread = new Thread(new Runnable() { + + @Override + public void run() { + try { + if (resolution instanceof WorkbenchMarkerResolution wbr) { + // the multi marker is often more aware of solving things without UI... + wbr.run(new IMarker[] { marker, marker }, new NullProgressMonitor()); + } else { + resolution.run(marker); + } + } catch (Throwable t) { + debug("Marker could not be applied!", t); + } + } + }); + thread.start(); + thread.join(); + } + } catch (Throwable t) { + debug("Marker resolutions could not be computed!", t); + } + } + return result; + } + + private String getInfo(IMarkerResolution resolution) { + if (resolution instanceof IMarkerResolution2 res2) { + return resolution.getClass().getName() + ": " + getLabel(resolution) + " // " + getDescription(res2); + } else { + return resolution.getClass().getName() + ": " + getLabel(resolution); + } + } + + private int getRelevance(IMarkerResolution resolution) { + try { + if (resolution instanceof IMarkerResolutionRelevance relevance) { + return relevance.getRelevanceForResolution(); + } + } catch (RuntimeException e) { + } + return -1; + } + + private String getDescription(IMarkerResolution2 res2) { + try { + return res2.getDescription(); + } catch (RuntimeException e) { + return null; + } + } + + private String getLabel(IMarkerResolution resolution) { + try { + return resolution.getLabel(); + } catch (RuntimeException e) { + return resolution.getClass().getName(); + } + } + +} diff --git a/tycho-cleancode-plugin/src/main/java/org/eclipse/tycho/cleancode/QuickFixMojo.java b/tycho-cleancode-plugin/src/main/java/org/eclipse/tycho/cleancode/QuickFixMojo.java new file mode 100644 index 0000000000..3aafcee2fc --- /dev/null +++ b/tycho-cleancode-plugin/src/main/java/org/eclipse/tycho/cleancode/QuickFixMojo.java @@ -0,0 +1,44 @@ +/******************************************************************************* + * Copyright (c) 2025 Christoph Läubrich and others. + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Christoph Läubrich - initial API and implementation + *******************************************************************************/ +package org.eclipse.tycho.cleancode; + +import org.apache.maven.plugin.MojoFailureException; +import org.apache.maven.plugins.annotations.LifecyclePhase; +import org.apache.maven.plugins.annotations.Mojo; +import org.apache.maven.plugins.annotations.ResolutionScope; +import org.eclipse.tycho.eclipsebuild.AbstractEclipseBuildMojo; + +@Mojo(name = "quickfix", defaultPhase = LifecyclePhase.PROCESS_SOURCES, threadSafe = true, requiresDependencyCollection = ResolutionScope.COMPILE_PLUS_RUNTIME) +public class QuickFixMojo extends AbstractEclipseBuildMojo { + + @Override + protected void handleResult(QuickFixResult result) throws MojoFailureException { + // TODO create a report of resolved markers! + } + + @Override + protected String[] getRequireBundles() { + return new String[] { "org.eclipse.ui.ide", "org.eclipse.jdt.ui" }; + } + + @Override + protected QuickFix createExecutable() { + return new QuickFix(project.getBasedir().toPath(), debug); + } + + @Override + protected String getName() { + return "Quick Fix"; + } + +} diff --git a/tycho-cleancode-plugin/src/main/java/org/eclipse/tycho/cleancode/QuickFixResult.java b/tycho-cleancode-plugin/src/main/java/org/eclipse/tycho/cleancode/QuickFixResult.java new file mode 100644 index 0000000000..afcce85807 --- /dev/null +++ b/tycho-cleancode-plugin/src/main/java/org/eclipse/tycho/cleancode/QuickFixResult.java @@ -0,0 +1,19 @@ +/******************************************************************************* + * Copyright (c) 2025 Christoph Läubrich and others. + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Christoph Läubrich - initial API and implementation + *******************************************************************************/ +package org.eclipse.tycho.cleancode; + +import org.eclipse.tycho.eclipsebuild.EclipseBuildResult; + +public class QuickFixResult extends EclipseBuildResult { + +} diff --git a/tycho-eclipse-plugin/src/main/java/org/eclipse/tycho/eclipsebuild/AbstractEclipseBuild.java b/tycho-eclipse-plugin/src/main/java/org/eclipse/tycho/eclipsebuild/AbstractEclipseBuild.java index 199607b938..46c449db14 100644 --- a/tycho-eclipse-plugin/src/main/java/org/eclipse/tycho/eclipsebuild/AbstractEclipseBuild.java +++ b/tycho-eclipse-plugin/src/main/java/org/eclipse/tycho/eclipsebuild/AbstractEclipseBuild.java @@ -1,7 +1,9 @@ package org.eclipse.tycho.eclipsebuild; import java.io.IOException; +import java.io.PrintWriter; import java.io.Serializable; +import java.io.StringWriter; import java.nio.file.Path; import java.util.concurrent.Callable; @@ -83,6 +85,14 @@ protected void debug(String string) { } } + protected void debug(String string, Throwable t) { + if (debug) { + StringWriter writer = new StringWriter(); + t.printStackTrace(new PrintWriter(writer)); + debug(string + System.lineSeparator() + writer); + } + } + static String pathAsString(Path path) { if (path != null) { return path.toAbsolutePath().toString();