When Robots Can Do All The Tedious Work
In a previous article we talked about how Atomist can inform you about common issues in your code. However, sometimes you can go a step further. What if a pattern you detected could be fixed automatically?
A nice example for this is TSLint. While you could create a code inspection that runs TSLint and shows you any violations, taking it a step further would be to run tslint-fix
on your projects automatically. Atomist provides this functionality out of the box in sdm-pack-node
.
Registering such an autofix is done in a similar manner as a code inspection: by registering autofixes on a specific goal, in this case Autofix
.
const autofix = new Autofix().with(tslintFix);
By adding this goal to your goalset, Atomist will check each commit and run TSLint’s fixing capabilities, assuming your commit passes the tests defined in the autofix (being it’s a Typescript and NodeJS project and has a tslint.json
file).
Writing your own autofixes
So now you’re starting to think about what patterns you can automatically apply to certain projects in your portfolio. Let me give you an example that might also demonstrate the power of the interactions between different automations within Atomist.
In the JVM world, there’s something called SpotBugs. SpotBugs is able to statically analyze your code and spot violations according to certain patterns. It can be activated by adding a Maven or Gradle plugin to your project.
Assume that you want your teams to use SpotBugs and you want to make sure every project is using the same configuration and thus ensuring the same level of quality of the code. An autofix can make sure everyone is always using the same configuration.
Prerequisite: Have an SDM ready
To follow these steps, you need to have an SDM on your machine. The developer quick start guide explains how to create a new SDM, but we’ll repeat the instructions here.
Step 1: Make sure you have NodeJS and npm
installed on your system
Step 2: Install the Atomist CLI
npm install -g @atomist/cli
Step 3: Create a new SDM by issuing the following command
atomist create sdm
and choose to create a spring
SDM. Fill in the data Atomist prompts you for and it will create a brand new SDM for you. The Spring SDM has the capabilities to build Spring Boot applications, but let’s extend its functionality by adding a code inspection.
Transforming your code
At the heart of an autofix is the mechanism to change code in your codebase. This mechanism is called a code transform in Atomist. For this article, we’ll use a brand new transform, written especially to support this article. The transform adds a Maven plugin to your POM.
/** Add the given plugin to projects. It's not an error
* if the project doesn't have a POM. The transform will do nothing
* in this case.
* @param {Plugin} plugin
* @return {CodeTransform}
*/
export function addPluginTransform(plugin: Plugin): CodeTransform {
return async p => {
if (await p.hasFile("pom.xml")) {
const plg = await findDeclaredPlugins(p);
if (plg.plugins.length === 0) {
throw new Error("No plugins in POM: Cannot add plugin");
}
if (plg.plugins.some(pl => plugin.artifact === pl.artifact && plugin.group === pl.group)) {
logger.info("Plugin [%s] already present. Nothing to do", plugin.artifact);
} else {
logger.info("Adding plugin [%s]", plugin.artifact);
// Add after last dependency
const lastPlugin = _.last(plg.plugins);
await astUtils.doWithAllMatches(
p,
new XmldocFileParser(),
"pom.xml",
`//project/build/plugins/plugin[/artifactId[@innerValue='${lastPlugin.artifact}']]`,
m => {
const pluginContent = indent(pluginStanza(plugin), " ", 4);
m.append("\n" + pluginContent);
}
);
}
}
};
}
But what can we do with this? Well, very interesting things if you combine this afterwards with code inspections. Let us first create a code transform that adds the SpotBugs plugin to a Maven POM and enables the findsecsbugs plugin, which does static analysis security testing, while we’re at it.
const AddSpotBugs = addPluginTransform({
group: "com.github.spotbugs",
artifact: "spotbugs-maven-plugin",
version: "3.1.3",
configuration: {
effort: "Max",
threshold: "Low",
failOnError: "true",
plugins: {
plugin: {
groupId: "com.h3xstream.findsecbugs",
artifactId: "findsecbugs-plugin",
version: "LATEST",
},
},
},
}
);
Executing the transform
There are two ways to execute a code transform, which we’ll both explain:
- Manually by issuing a command
- Automatically by registering an autofix
The first way is by registering a command handler, just like you can do with a code inspection in the definition of your SDM.
const AddSpotBugsCommand: CodeTransformRegistration = {
intent: "add spotbugs plugin",
transform: AddSpotBugs,
projectTest: allSatisfied(IsMaven),
}
sdm.addCodeTransformCommand(AddSpotBugsCommand);
The second way is when you want to automatically apply a plugin to every project that’s being handled by the SDM. That way you can be certain that every project will use the plugin.
Autofixes are triggered by defining an Autofix
goal and adding it to the goal set of your project. The Spring SDM will have an autofix
goal defined already and registered in the SDM. To add an autofix to the goal, add the following code:
const AddSpotBugsAutofix: AutofixRegistration = {
transform: AddSpotBugs,
pushTest: allSatisfied(IsMaven),
}
autofix.with(AddSpotBugsAutofix);
As the AddSpotBugs
transformation already checks whether the plugin is present, we don’t need to write an additional push test. Now on each commit, Atomist will trigger this autofix when a Maven POM is present and will add the SpotBugs plugin if it’s not present in the POM.
Now the kicker
Now that we have the capability of adding plugins to a Maven POM, what could we do next? Well, there’s a reason why we picked the SpotBugs Maven plugin and enabled the FindSecBugs plugin for it.
Gitlab has an interesting feature in their CI tool that does static analysis security testing, available in their Gold subscription. For Java projects, this executes the SpotBugs maven plugin with the FindSecBugs plugin enabled. So, now that we have a way to enable that plugin automatically and we know how to write code inspections, how hard would it be to mimic that behavior?
Well, we could write a code inspection that executes mvn compile spotbugs:spotbugs
, retrieves the generated target/spotbugsXml.xml
, parses the XML and generates review comments based on the content of the report.
const SpotbugsReview: CodeInspection<ProjectReview> = async (p: Project): Promise<ProjectReview> => {
await spawnAndWatch({command: await determineMavenCommand(p), args: ["compile", "spotbugs:spotbugs"]}, {
cwd: (p as GitProject).baseDir,
}, new LoggingProgressLog("spotbugs"));
const comments = await extractCommentsFromSpotbugsReport(p);
return {repoId: p.id, comments: _.flatten(comments)};
};
async function extractCommentsFromSpotbugsReport(p: Project): Promise<ReviewComment[]> {
const report = await p.getFile("target/spotbugsXml.xml");
const parser = new xml2js.Parser();
const comments: ReviewComment[] = [];
parser.parseString(await report.getContent(), (err: any, result: any) => {
if (result.BugCollection.BugInstance !== undefined) {
const bugInstances: any[] = result.BugCollection.BugInstance;
for (const bugInstance of bugInstances) {
comments.push({
severity: "error",
category: "spotbugs",
detail: bugInstance.ShortMessage[0],
sourceLocation: {
path: "src/main/java/" +
bugInstance.SourceLine[0].$.sourcepath,
lineFrom1: bugInstance.SourceLine[0].$.start,
offset: 0,
},
});
}
}
});
return comments;
}
extractComments
opens up the XML file, parses the content using xml2js
and looks for elements under the /BugCollection/BugInstance
path expression and parses them into review comments.
If you’ve connected Atomist to a Slack channel, the output from running this code inspection could look like this:

You can get as creative as you want to regarding the visual representation of the review comments.
Conclusion
Autofixes help you to apply company policies throughout your codebase and get consistent application for those policies. Autofixes provide a powerful way to automatically alter code to meet your team’s standards. In most cases, your process will probably look like this:
- Write a code inspection that checks for violations
- Run that code inspection automatically
- Provide a code transform if those violations can be fixed in an automated manner
- Run that code transform automatically in an autofix
So next time you do repetitive work that you may have already written a script for, consider writing an autofix with Atomist.