Skip to content

Commit e5fd573

Browse files
authored
Added support for Spotless formatter (#5064)
This adds support for using [Spotless](https://github.com/diffplug/spotless/). A custom `mill.scalalib.spotless.Format` type is defined to configure a Spotless formatter. Multiple configurations, typically one per programming language, are used to create a `mill.scalalib.spotless.SpotlessWorker` that can check/fix formatting in files. This functionality is exposed via the `mill.scalalib.spotless.SpotlessModule.spotless` command. The implementation supports all [features](https://github.com/diffplug/spotless/tree/main?tab=readme-ov-file#current-feature-matrix) provided by other build tool plugins except - Fast format on fresh checkout using buildcache. - Retroactively [slurp year from git](https://github.com/diffplug/spotless/tree/main/plugin-gradle#retroactively-slurp-years-from-git-history). The [ratchet](https://github.com/diffplug/spotless/tree/main/plugin-gradle#ratchet) feature is implemented using [JGit](https://github.com/eclipse-jgit/jgit). All generic/Java/Kotlin/Scala formatter steps are supported. Fix #3888 Pull request: #5064
1 parent c1f8424 commit e5fd573

File tree

85 files changed

+2917
-2
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

85 files changed

+2917
-2
lines changed

.scalafmt.conf

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,12 +26,14 @@ project {
2626
"glob:**/mill/out/**",
2727
# don't try to format files that are purposefully malformed / misformatted
2828
"glob:**/example/scalalib/linting/1-scalafmt/src/Foo.scala",
29+
"glob:**/example/**/*-spotless*/**",
2930
"glob:**/scalalib/test/resources/checkstyle/**",
3031
"glob:**/scalalib/test/resources/giter8/hello.g8/src/main/g8/build.mill",
3132
"glob:**/scalalib/test/resources/javalib/palantirformat/**",
3233
"glob:**/integration/failure/parse-error/**",
3334
"glob:**/testkit/test/resources/example-test-example-project/build.mill",
34-
"glob:**/scalalib/test/resources/scalafmt/**"
35+
"glob:**/scalalib/test/resources/scalafmt/**",
36+
"glob:**/scalalib/test/resources/spotless/**"
3537
]
3638
}
3739

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
[
2+
{
3+
"includes": [
4+
"glob:build.mill"
5+
],
6+
"steps": [
7+
{
8+
"$type": "ScalaFmt"
9+
}
10+
]
11+
},
12+
{
13+
"includes": [
14+
"glob:resources/app.properties"
15+
],
16+
"steps": [
17+
{
18+
"$type": "TrimTrailingWhitespace"
19+
}
20+
]
21+
},
22+
{
23+
"includes": [
24+
"glob:**.java"
25+
],
26+
"steps": [
27+
{
28+
"$type": "PalantirJavaFormat"
29+
},
30+
{
31+
"$type": "LicenseHeader",
32+
"delimiter": "(package|import|public|class|module) "
33+
}
34+
]
35+
}
36+
]
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
// MIT
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
// :api-spotless: {site-url}/api/latest/mill/scalalib/spotless
2+
3+
// If your project has file groups each requiring different formatting,
4+
// you may want to give Mill's https://github.com/diffplug/spotless[Spotless] plugin a try.
5+
// It supports formatting all files with a single command
6+
// as opposed to using a different plugin/command for each group.
7+
8+
// We define a module that extends link:{api-spotless}/SpotlessModule.html[SpotlessModule] and
9+
// provide a JSON configuration file with the format specifications.
10+
package build
11+
12+
import mill.javalib.JavaModule
13+
import mill.scalalib.spotless.*
14+
15+
object `package` extends JavaModule with SpotlessModule
16+
17+
/** See Also: src/A.java */
18+
/** See Also: resources/app.properties */
19+
/** See Also: LICENSE */
20+
/** See Also: .spotless-formats.json */
21+
22+
// As per the specifications:
23+
24+
// * The `build.mill` is to be formatted using `ScalaFmt` step.
25+
// * The `resources/app.properties` file is to be formatted using `TrimTrailingWhitespace` step.
26+
// * All Java files are to be formatted using `PalantirFormatJava` and `LicenseHeader` steps.
27+
28+
// NOTE: Most fields have default values and can be omitted in the JSON file.
29+
// An example is the `LicenseHeader.header` field that references the `LICENSE` file.
30+
31+
// Next, we run the inherited `spotless` command to check/apply the format specifications.
32+
33+
/** Usage
34+
> ./mill spotless --check # check fails initially
35+
checking format in 1 mill files
36+
format errors in build.mill
37+
checking format in 1 properties files
38+
format errors in resources/app.properties
39+
checking format in 1 java files
40+
format errors in src/A.java
41+
format errors in 3 files
42+
error: ...format check failed for 3 files
43+
44+
> ./mill spotless # auto-fix format
45+
formatting build.mill
46+
formatting resources/app.properties
47+
formatting src/A.java
48+
formatted 3 files
49+
format completed
50+
51+
> ./mill spotless # fast incremental format
52+
1 mill files are already formatted
53+
1 properties files are already formatted
54+
1 java files are already formatted
55+
*/
56+
57+
// This demonstrates how different file groups can be formatted with a single command.
58+
59+
// For the full list of format steps and configuration options, please refer to the
60+
// link:{api-spotless}/Format.html[API documentation].
61+
62+
// TIP: For a multi-module project, it is sufficient to extend `SpotlessModule` in your
63+
// build root module and define a format specification for each use-case.
64+
65+
// You can also run `spotless` xref:fundamentals/modules.adoc#_external_modules[globally]
66+
// if you prefer not to have to extend `SpotlessModule`.
67+
68+
/** Usage
69+
> ./mill mill.scalalib.spotless.SpotlessModule/ --check
70+
checking format in 1 mill files
71+
checking format in 1 properties files
72+
checking format in 1 java files
73+
format check completed
74+
*/
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
foo=bar
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import mylib.Unused;
2+
import mylib.UsedB;
3+
import mylib.UsedA;
4+
5+
public class A {
6+
/**
7+
* Some javadoc.
8+
*/
9+
public static void main(String[] args) {
10+
System.out.println("A very very very very very very very very very very very very very very very very very very very very very long string that goes beyond the 100-character line length.");
11+
UsedB.someMethod();
12+
UsedA.someMethod();
13+
}
14+
}
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
mill
2+
out/
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
// Similar to the Spotless Gradle and Maven plugins, Mill provides the ability to
2+
// enforce formatting gradually aka https://github.com/diffplug/spotless/tree/main/plugin-gradle#ratchet[ratchet].
3+
4+
// We define a module with an incorrectly formatted file.
5+
package build
6+
7+
import mill.javalib.JavaModule
8+
import mill.scalalib.spotless.SpotlessModule
9+
10+
object `package` extends JavaModule with SpotlessModule
11+
12+
/** See Also: src/A.java */
13+
14+
// A Git repository is initialized and a base commit is created with the incorrectly formatted file.
15+
16+
// NOTE: Since no `.spotless-formats.json` file is present, a default list of format specifications is used.
17+
// The default is meant for use cases where some basic formatting is sufficient.
18+
19+
/** Usage
20+
> git init . -b main
21+
> git add .gitignore build.mill src/A.java
22+
> git commit -a -m "1"
23+
*/
24+
25+
// Next, we create a new file with format errors.
26+
27+
/** Usage
28+
> echo " module hello {}" > src/module-info.java # content has leading space at start
29+
30+
> ./mill spotless --check
31+
format errors in build.mill
32+
format errors in src/A.java
33+
format errors in src/module-info.java
34+
error: ...format check failed for 3 files
35+
*/
36+
37+
// The `spotless` command finds format errors in all files.
38+
// But we do not want to fix the formatting in files that were committed previously.
39+
40+
// Instead, we use the `ratchet` command to identify and format files that differ between Git trees.
41+
42+
/** Usage
43+
> ./mill ratchet --check # format changes in working tree since HEAD commit
44+
ratchet found changes in 1 files
45+
format errors in src/module-info.java
46+
error: ...format check failed for 1 files
47+
48+
> ./mill ratchet # auto-fix formatting in changeset
49+
ratchet found changes in 1 files
50+
formatting src/module-info.java
51+
formatted 1 files
52+
*/
53+
54+
// This demonstrates how to introduce formatting incrementally into your project.
55+
56+
// You can also set up actions on CI systems that compare and check/fix formatting between 2
57+
// https://javadoc.io/doc/org.eclipse.jgit/org.eclipse.jgit/latest/org.eclipse.jgit/org/eclipse/jgit/lib/Repository.html#resolve(java.lang.String)[revisions].
58+
59+
/** Usage
60+
> git add src/module-info.java # stage and
61+
> git commit -a -m "2" # commit changes
62+
63+
> ./mill ratchet --check HEAD^ HEAD # format changes between last 2 commits
64+
ratchet found changes in 1 files
65+
1 java files are already formatted
66+
*/
67+
68+
// CAUTION: CI actions may require additional
69+
// https://github.com/diffplug/spotless/tree/main/plugin-gradle#using-ratchetfrom-on-ci-systems[setup].
70+
71+
// The `ratchet` command is also available globally.
72+
73+
/** Usage
74+
> ./mill mill.scalalib.spotless.SpotlessModule/ratchet --check HEAD^ HEAD
75+
ratchet found changes in 1 files
76+
*/
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
2+
import mylib.Unused;
3+
import mylib.UsedB;
4+
import mylib.UsedA;
5+
6+
public class A {
7+
/**
8+
* Some javadoc.
9+
*/
10+
public static void main(String[] args) {
11+
System.out.println("A very very very very very very very very very very very very very very very very very very very very very long string that goes beyond the 100-character line length.");
12+
UsedB.someMethod();
13+
UsedA.someMethod();
14+
}
15+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
[
2+
{
3+
"includes": [
4+
"glob:build.mill"
5+
],
6+
"steps": [
7+
{
8+
"$type": "ScalaFmt"
9+
}
10+
]
11+
},
12+
{
13+
"includes": [
14+
"glob:**.java"
15+
],
16+
"steps": [
17+
{
18+
"$type": "PalantirJavaFormat"
19+
},
20+
{
21+
"$type": "LicenseHeader",
22+
"delimiter": "(package|import|public|class|module) "
23+
}
24+
]
25+
},
26+
{
27+
"includes": [
28+
"glob:**.{kt,kts}"
29+
],
30+
"steps": [
31+
{
32+
"$type": "Ktfmt"
33+
},
34+
{
35+
"$type": "LicenseHeader",
36+
"delimiter": "(package |@file|import )"
37+
}
38+
]
39+
}
40+
]

0 commit comments

Comments
 (0)