Spotless is a general-purpose formatting plugin. It is completely à la carte, but also includes powerful "batteries-included" if you opt-in.
To people who use your build, it looks like this:
cmd> gradlew build
...
:spotlessJavaCheck FAILED
> The following files had format violations:
src\main\java\com\diffplug\gradle\spotless\FormatExtension.java
@@ -109,7 +109,7 @@
...
-\t\t····if·(targets.length·==·0)·{
+\t\tif·(targets.length·==·0)·{
...
Run 'gradlew spotlessApply' to fix these violations.
cmd> gradlew spotlessApply
:spotlessApply
BUILD SUCCESSFUL
cmd> gradlew build
BUILD SUCCESSFUL
To use it in your buildscript, just add the Spotless dependency, and configure it like so:
spotless {
format 'misc', {
target '**/*.gradle', '**/*.md', '**/.gitignore'
trimTrailingWhitespace()
indentWithTabs() // or spaces. Takes an integer argument if you don't like 4
endWithNewline()
}
format 'cpp', {
target '**/*.hpp', '**/*.cpp'
replace 'Not enough space after if', 'if(', 'if ('
replaceRegex 'Too much space after if', 'if +\\(', 'if ('
// Everything before the first #include or #pragma will
// be replaced with whatever is in `spotless.license.cpp`
licenseHeaderFile 'spotless.license.cpp', '#'
}
}Spotless can check and apply formatting to any plain-text file, using simple rules (javadoc) like those above. It also supports more powerful formatters:
- Eclipse's java code formatter (including style and import ordering)
- Google's google-java-format
- Groovy Eclipse's groovy code formatter
- FreshMark (markdown with variables)
- ktlint
- scalafmt
- DBeaver sql format
- Any user-defined function which takes an unformatted string and outputs a formatted version.
Contributions are welcome, see the contributing guide for development info.
Spotless requires Gradle to be running on JRE 8+.See issue #7 for details.
By default, all Java source sets will be formatted. To change this,
set the target parameter as described in the Custom rules section.
apply plugin: 'java'
...
spotless {
java {
licenseHeader '/* Licensed under Apache-2.0 */' // License header
licenseHeaderFile 'spotless.license.java' // License header file
// Obviously, you can't specify both licenseHeader and licenseHeaderFile at the same time
importOrder 'java', 'javax', 'org', 'com', 'com.diffplug', '' // A sequence of package names
importOrderFile 'spotless.importorder' // An import ordering file, exported from Eclipse
// As before, you can't specify both importOrder and importOrderFile at the same time
// You probably want an empty string at the end - all of the imports you didn't specify
// explicitly will go there.
removeUnusedImports() // removes any unused imports
eclipse().configFile 'spotless.eclipseformat.xml' // XML file dumped out by the Eclipse formatter
// If you have Eclipse preference or property files, you can use them too.
// eclipse('4.7.1') to specify a specific version of eclipse,
// available versions are: https://bintray.com/diffplug/opensource/spotless-ext-eclipse-jdt
}
}See ECLIPSE_SCREENSHOTS for screenshots that demonstrate how to get and install the eclipseFormatFile and importOrderFile mentioned above.
Be sure to add target '**/*.java' otherwise spotless will not detect Java code inside Android modules.
spotless {
java {
// ...
target '**/*.java'
// ...
}
}Applying to Java source (google-java-format)
spotless {
java {
googleJavaFormat()
// optional: you can specify a specific version and/or switch to AOSP style
googleJavaFormat('1.1').aosp()
// you can then layer other format steps, such as
licenseHeaderFile 'spotless.license.java'
}
}Configuration for Groovy is similar to Java. Most java steps, like licenseHeader and importOrder, support Groovy as well as Java.
The groovy formatter's default behavior is to format all .groovy and .java files found in the Groovy source directories. If you would like to exclude the .java files, set the parameter excludeJava, or you can set the target parameter as described in the Custom rules section.
Due to cyclic ambiguities of groovy formatter results, e.g. for nested closures, the use of paddedCell() and/or Custom rules is recommended to bandaid over this third-party formatter problem.
apply plugin: 'groovy'
...
spotless {
java {
licenseHeaderFile 'spotless.license.java'
googleJavaFormat() // use a specific formatter for Java files
}
groovy {
licenseHeaderFile 'spotless.license.java'
excludeJava() // excludes all Java sources within the Groovy source dirs from formatting
paddedCell() // Avoid cyclic ambiguities
// the Groovy Eclipse formatter extends the Java Eclipse formatter,
// so it formats Java files by default (unless `excludeJava` is used).
greclipse().configFile('greclipse.properties')
}
groovyGradle {
// same as groovy, but for .gradle (defaults to '*.gradle')
target '*.gradle', 'additionalScripts/*.gradle'
greclipse().configFile('greclipse.properties')
}
}The Groovy-Eclipse formatter is based on the Eclipse Java formatter as used by eclipseFormatFile. It uses the same configuration parameters plus a few additional ones. These parameters can be configured within a single file, like the Java properties file greclipse.properties in the previous example. The formatter step can also load the exported Eclipse properties and augment it with the org.codehaus.groovy.eclipse.ui.prefs from the Eclipse workspace as shown below.
spotless {
groovy {
// Use the default version and Groovy-Eclipse default configuration
greclipse()
// optional: you can specify a specific version or config file(s)
// available versions: https://bintray.com/diffplug/opensource/spotless-ext-greclipse
greclipse('2.3.0').configFile('spotless.eclipseformat.xml', 'org.codehaus.groovy.eclipse.ui.prefs')
}
}Applying FreshMark to markdown files
Freshmark lets you generate markdown in the comments of your markdown. This helps to keep badges and links up-to-date (see the source for this file), and can also be helpful for generating complex tables (see the source for the parent readme).
To apply freshmark to all of the .md files in your project, with all of your project's properties available for templating, use this snippet:
spotless {
freshmark {
target 'README.md', 'CONTRIBUTING.md' // defaults to '**/*.md'
propertiesFile('gradle.properties') // loads all the properties in the given file
properties {
it.put('key', 'value') // specify other properties manually
}
}
}Applying scalafmt to Scala files
spotless {
scala {
scalafmt()
// optional: you can specify a specific version or config file
scalafmt('0.5.1').configFile('scalafmt.conf')
}
}Applying ktlint to Kotlin files
spotless {
kotlin {
// optionally takes a version
ktlint()
// Optional user arguments can be set as such:
ktlint().userData(['indent_size': '2', 'continuation_indent_size' : '2'])
// also supports license headers
licenseHeader '/* Licensed under Apache-2.0 */' // License header
licenseHeaderFile 'path-to-license-file' // License header file
}
kotlinGradle {
// same as kotlin, but for .gradle.kts files (defaults to '*.gradle.kts')
target '*.gradle.kts', 'additionalScripts/*.gradle.kts'
ktlint()
// Optional user arguments can be set as such:
ktlint().userData(['indent_size': '2', 'continuation_indent_size' : '2'])
// doesn't support licenseHeader, because scripts don't have a package statement
// to clearly mark where the license should go
}
}Applying DBeaver to SQL scripts
spotless {
sql {
// default value for target files
target '**/*.sql'
// configFile is optional, arguments available here: https://github.com/diffplug/spotless/blob/master/lib/src/main/java/com/diffplug/spotless/sql/DBeaverSQLFormatterStep.java
dbeaver().configFile('dbeaver.props')
}
}If the string contents of a licenseHeader step or the file contents of a licenseHeaderFile step contains a $YEAR token, then in the end-result generated license headers which use this license header as a template, $YEAR will be replaced with the current year.
For example:
/* Licensed under Apache-2.0 $YEAR. */
will produce
/* Licensed under Apache-2.0 2017. */
if Spotless is launched in 2017
The licenseHeader and licenseHeaderFile steps will generate license headers with automatic years from the base license header according to the following rules:
- A generated license header will be updated with the current year when
- the generated license header is missing
- the generated license header is not formatted correctly
- A generated license header will not be updated when
- a single year is already present, e.g.
/* Licensed under Apache-2.0 1990. */ - a year range is already present, e.g.
/* Licensed under Apache-2.0 1990-2003. */ - the
$YEARtoken is otherwise missing
- a single year is already present, e.g.
The separator for the year range defaults to the hyphen character, e.g 1990-2003, but can be customized with the yearSeparator property.
For instance, the following configuration treats 1990, 2003 as a valid year range.
spotless {
java {
licenseHeader('Licensed under Apache-2.0 $YEAR').yearSeparator(', ')
}
}Spotless is a generic system for specifying a sequence of steps which are applied to a set of files.
spotless {
// this will create two tasks: spotlessMiscCheck and spotlessMiscApply
format 'misc', {
// target determines which files this format will apply to
// - if you pass a string or a list of strings, they will be treated
// as 'include' parameters to a fileTree in the root directory
// - if you pass a FileCollection, it will pass through untouched
// e.g. project.files('build.gradle', 'settings.gradle')
// - if you pass anything else, it will be sent to project.files(yourArg)
target '**/*.gradle', '**/*.md', '**/.gitignore'
// spotless has built-in rules for the most basic formatting tasks
trimTrailingWhitespace()
indentWithTabs() // or spaces. Takes an integer argument if you don't like 4
endWithNewline()
// you can also call out to your own function
custom 'superFormatter', {
// when writing a custom step, it will be helpful to know
// how the formatting process works, which is as follows:
// 1) Load each target file, and convert it to unix-style line endings ('\n')
// 2) Pass its content through a series of steps, feeding the output of each step to the next
// 3) Put the correct line endings back on, then either check or apply
// each step receives a string as input, and should output
// a formatted string as output. Each step can trust that its
// input will have unix newlines, and it must promise to output
// only unix newlines. Other than that, anything is fair game!
}
}
}If you use custom or customLazy, you might want to take a look at this javadoc for a big performance win.
See JavaExtension.java if you'd like to see how a language-specific set of custom rules is implemented. We'd love PR's which add support for other languages.
Spotless uses UTF-8 by default, but you can use any encoding which Java supports. You can set it globally, and you can also set it per-format.
spotless {
java {
...
encoding 'Cp1252' // java will have Cp1252
}
encoding 'US-ASCII' // but all other formats will be interpreted as US-ASCII
}Line endings can also be set globally or per-format using the lineEndings property. Spotless supports four line ending modes: UNIX, WINDOWS, PLATFORM_NATIVE, and GIT_ATTRIBUTES. The default value is GIT_ATTRIBUTES, and we highly recommend that you do not change this value. Git has opinions about line endings, and if Spotless and git disagree, then you're going to have a bad time.
You can easily set the line endings of different files using a .gitattributes file. Here's an example .gitattributes which sets all files to unix newlines: * text eol=lf.
The check task is Gradle's built-in task for grouping all verification tasks - unit tests, static analysis, etc. By default, spotlessCheck is added as a dependency to check.
You might want to disable this behavior. We recommend against this, but it's easy to do if you'd like:
spotless {
enforceCheck false
}If a formatter throws an exception, possibly because of malformed code or a bug in a formatter step, Spotless will report a build failure. You can suppress these specific failures as such:
spotless {
java {
googleJavaFormat()
custom 'my-glitchy-step', { }
ignoreErrorForStep('my-glitchy-step') // ignore errors on all files thrown by a specific step
ignoreErrorForPath('path/to/file.java') // ignore errors by all steps on this specific file
}
}Note that enforceCheck is a global property which affects all formats (outside the java block), while ignoreErrorForStep/Path are local to a single format (inside the java block).
- Save your working tree with
git add -A, thengit commit -m "Checkpoint before spotless." - Run
gradlew spotlessApply - View the changes with
git diff - If you don't like what spotless did,
git reset --hard - If you'd like to remove the "checkpoint" commit,
git reset --soft head~1will make the checkpoint commit "disappear" from history, but keeps the changes in your working directory.
Spotless is hosted on jcenter and at plugins.gradle.org. Go here if you're not sure how to import the plugin.
- JUnit 5 (aka JUnit Lambda)
- opentest4j
- Durian (direct link to spotless section in its build.gradle)
- DurianRx (direct link to spotless section in its build.gradle)
- DurianSwt (direct link to spotless section in its build.gradle)
- MatConsoleCtl (direct link to spotless section in its build.gradle)
- MatFileRW (direct link to spotless section in its build.gradle)
- Goomph (direct link to spotless section in its build.gradle)
- FreshMark (direct link to spotless section in its build.gradle)
- JScriptBox (direct link to spotless section in its build.gradle)
- (Your project here)
