Skip to content

Commit

Permalink
Create new tycho-cleancode plugin
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
laeubi committed Jan 21, 2025
1 parent ffbcbae commit 23ed37f
Show file tree
Hide file tree
Showing 12 changed files with 635 additions and 2 deletions.
54 changes: 52 additions & 2 deletions RELEASE_NOTES.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,62 @@ All of the following variants to specify a version are now possible:
</locations>
</target>
```
## new `quickfix` and `cleanup` mojo

Keeping code up-todate is a daunting task and Eclipse IDE can be a great help due to its offering for automatic quick-fixes and cleanup actions.
Still this has usually be applied manually (or automatic on save) and requires some user interaction.

There is now a new `tycho-cleancode:cleanup` and `tycho-cleancode:quickfix` mojo that help with these things to be called via automation or part of the build,
both mojos run by default in the process-sources phase so any later compilation can verify the outcome easily. Due to the way they work internally,
they require an eclipse project to be present and configured currently.

The `tycho-cleancode:cleanup` mojo allows to configure the desired cleanup profile in the pom (maybe inside a profile), the values are those that you will find
when exporting a profile from the IDE. If no profile is given the default from the project are used:

```xml
<plugin>
<groupId>org.eclipse.tycho</groupId>
<artifactId>tycho-cleancode-plugin</artifactId>
<version>${tycho-version}</version>
<executions>
<execution>
<id>cleanup</id>
<goals>
<goal>cleanup</goal>
</goals>
<configuration>
<cleanUpProfile>
<cleanup.static_inner_class>true</cleanup.static_inner_class>
</cleanUpProfile>
</configuration>
</execution>
</executions>
</plugin>
```

The `tycho-cleancode:quickfix` mojo simply apply the quick fix with the highest relevance for resolution and can be enabled like this (maybe inside a profile):

```xml
<plugin>
<groupId>org.eclipse.tycho</groupId>
<artifactId>tycho-cleancode-plugin</artifactId>
<version>${tycho-version}</version>
<executions>
<execution>
<id>quickfix</id>
<goals>
<goal>quickfix</goal>
</goals>
</execution>
</executions>
</plugin>
```

## new `check-dependencies` mojo

When using version ranges there is a certain risk that one actually uses some methods from never release and it goes unnoticed.

There is now a new `tycho-baseline:dependencies mojo` that analyze the compiled class files for used method references and compares them to
There is now a new `tycho-baseline:dependencies` mojo that analyze the compiled class files for used method references and compares them to
the individual artifacts that match the version range. To find these versions it uses the maven metadata stored in P2 as well as
the eclipse-repository index to find possible candidates.

Expand All @@ -47,7 +97,7 @@ according to the discovered problems, a configuration for this might look like t
<plugin>
<groupId>org.eclipse.tycho</groupId>
<artifactId>tycho-baseline-plugin</artifactId>
<version>${tycho.version}</version>
<version>${tycho-version}</version>
<executions>
<execution>
<id>checkit</id>
Expand Down
1 change: 1 addition & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -596,6 +596,7 @@
<module>tycho-repository-plugin</module>
<module>tycho-eclipse-plugin</module>
<module>tycho-wrap-plugin</module>
<module>tycho-cleancode-plugin</module>
</modules>
<profiles>
<profile>
Expand Down
8 changes: 8 additions & 0 deletions tycho-cleancode-plugin/.settings/org.eclipse.jdt.core.prefs
Original file line number Diff line number Diff line change
@@ -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
64 changes: 64 additions & 0 deletions tycho-cleancode-plugin/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.eclipse.tycho</groupId>
<artifactId>tycho</artifactId>
<version>5.0.0-SNAPSHOT</version>
</parent>
<artifactId>tycho-cleancode-plugin</artifactId>
<name>Tycho Eclipse Plugin</name>
<packaging>maven-plugin</packaging>
<prerequisites>
<maven>${minimal-maven-version}</maven>
</prerequisites>
<description>Maven Plugins for performing automatic code clean options</description>
<dependencies>
<dependency>
<groupId>org.apache.maven</groupId>
<artifactId>maven-core</artifactId>
</dependency>
<dependency>
<groupId>org.apache.maven</groupId>
<artifactId>maven-plugin-api</artifactId>
</dependency>
<dependency>
<groupId>org.apache.maven.plugin-tools</groupId>
<artifactId>maven-plugin-annotations</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.tycho</groupId>
<artifactId>tycho-eclipse-plugin</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.eclipse.jdt</groupId>
<artifactId>org.eclipse.jdt.ui</artifactId>
<version>3.33.200</version>
</dependency>
<dependency>
<groupId>org.eclipse.jdt</groupId>
<artifactId>org.eclipse.jdt.core.manipulation</artifactId>
<version>1.21.300</version>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.codehaus.plexus</groupId>
<artifactId>plexus-component-metadata</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-plugin-plugin</artifactId>
<!-- workaround for
https://issues.apache.org/jira/browse/MPLUGIN-450 -->
<configuration>
<goalPrefix>tycho-cleancode</goalPrefix>
</configuration>
</plugin>
</plugins>
</build>
</project>
Original file line number Diff line number Diff line change
@@ -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<CleanupResult> {

private Map<String, String> customProfile;

CleanUp(Path projectDir, boolean debug, Map<String, String> 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<ICompilationUnit> 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<ICompilationUnit> getCompilationUnits(IProject project) throws JavaModelException {
IJavaProject javaProject = JavaCore.create(project);
List<ICompilationUnit> units = new ArrayList<ICompilationUnit>();
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<String, String> 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);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
/*******************************************************************************
* 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.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

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.Parameter;
import org.apache.maven.plugins.annotations.ResolutionScope;
import org.eclipse.tycho.eclipsebuild.AbstractEclipseBuildMojo;
import org.eclipse.tycho.model.project.EclipseProject;

/**
* 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<CleanupResult> {

@Parameter(defaultValue = "${project.build.directory}/cleanups.md", property = "tycho.cleanup.report")
private File reportFileName;

/**
* Defines key value pairs of a cleanup profile, if not defined will use the
* project defaults
*/
@Parameter
private Map<String, String> 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 {
List<String> results = new ArrayList<>();
results.add("The following cleanups where applied:");
result.cleanups().forEach(cleanup -> {
results.add("- " + cleanup);
});
try {
Files.writeString(reportFileName.toPath(),
results.stream().collect(Collectors.joining(System.lineSeparator())));
} catch (IOException e) {
throw new MojoFailureException(e);
}
}

@Override
protected boolean isValid(EclipseProject eclipseProject) {
// Cleanups can only be applied to java projects
return eclipseProject.hasNature("org.eclipse.jdt.core.javanature");
}

}
Loading

0 comments on commit 23ed37f

Please sign in to comment.