Skip to content

Commit

Permalink
Added plugin for formatting Java sources using Palantir (#3531)
Browse files Browse the repository at this point in the history
Added `contrib.palantir.JavafmtModule` for formatting Java sources using
[Palantir](https://github.com/palantir/palantir-java-format/).
The plugin also supports command line arguments for
- checking for formatting errors with `--check` flag
- formatting specific files or folders

Resolves #3448.
  • Loading branch information
ajaychandran authored Sep 22, 2024
1 parent b50c600 commit 09d7784
Show file tree
Hide file tree
Showing 13 changed files with 608 additions and 0 deletions.
26 changes: 26 additions & 0 deletions example/javalib/linting/3-palantirformat/build.mill
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package build

import mill._
import mill.javalib.palantirformat._

object `package` extends RootModule with PalantirFormatModule {
}

/** See Also: src/A.java */

/** Usage

> ./mill palantirformat --check # check should fail initially
...checking format in java sources ...
...src/A.java
error: ...palantirformat aborted due to format error(s) (or invalid plugin settings/palantirformat options)

> ./mill palantirformat # format all Java source files
...formatting java sources ...

> ./mill palantirformat --check # check should succeed now
...checking format in java sources ...

> ./mill mill.javalib.palantirformat.PalantirFormatModule/ __.sources # alternatively, use external module to check/format
...formatting java sources ...
*/
6 changes: 6 additions & 0 deletions example/javalib/linting/3-palantirformat/src/A.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
public class A {

public static void main(String[] args) {
System.out.println("hello"); // indentation should be fixed
}
}
190 changes: 190 additions & 0 deletions scalalib/src/mill/javalib/palantirformat/PalantirFormatModule.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
package mill
package javalib.palantirformat

import mill.api.{Loose, PathRef}
import mill.define.{Discover, ExternalModule}
import mill.main.Tasks
import mill.scalalib.{CoursierModule, DepSyntax, JavaModule}
import mill.util.Jvm

trait PalantirFormatBaseModule extends CoursierModule {

/**
* Classpath for running Palantir Java Format.
*/
def palantirformatClasspath: T[Loose.Agg[PathRef]] = T {
defaultResolver().resolveDeps(
Agg(ivy"com.palantir.javaformat:palantir-java-format:${palantirformatVersion()}")
)
}

/**
* JVM arguments for running Palantir Java Format. Defaults to values prescribed in
* "[[https://github.com/palantir/palantir-java-format/issues/548 Broken on Java 16]]".
*/
def palantirformatJvmArgs: T[Seq[String]] = T {
Seq(
"--add-exports",
"jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED",
"--add-exports",
"jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED",
"--add-exports",
"jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED",
"--add-exports",
"jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED",
"--add-exports",
"jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED"
)
}

/**
* Path to options file for Palantir Java Format CLI. Defaults to `millSourcePath` `/` `palantirformat.options`.
*/
def palantirformatOptions: T[PathRef] = Task.Source(
millSourcePath / "palantirformat.options"
)

/**
* Palantir Java Format version. Defaults to `2.50.0`.
*/
def palantirformatVersion: T[String] = T {
"2.50.0"
}
}

/**
* Formats Java source files using [[https://github.com/palantir/palantir-java-format Palantir Java Format]].
*/
trait PalantirFormatModule extends JavaModule with PalantirFormatBaseModule {

/**
* Formats Java source files.
*
* @param check if an exception should be raised when formatting errors are found
* - when set, files are not formatted
* @param sources list of file or folder path(s) to be processed
* - path must be relative to [[millSourcePath]]
* - when empty, all [[sources]] are processed
*/
def palantirformat(
check: mainargs.Flag = mainargs.Flag(value = false),
sources: mainargs.Leftover[String]
): Command[Unit] = Task.Command {

val _sources =
if (sources.value.isEmpty) this.sources()
else sources.value.iterator.map(rel => PathRef(millSourcePath / os.RelPath(rel)))

PalantirFormatModule.palantirAction(
_sources,
check.value,
palantirformatOptions(),
palantirformatClasspath(),
palantirformatJvmArgs()
)
}
}
object PalantirFormatModule extends ExternalModule with PalantirFormatBaseModule with TaskModule {

override def defaultCommandName(): String = "formatAll"

/**
* Formats Java source files.
*
* @param check if an exception should be raised when formatting errors are found
* - when set, files are not formatted
* @param sources list of [[JavaModule]] sources to process
*/
def formatAll(
check: mainargs.Flag = mainargs.Flag(value = false),
@mainargs.arg(positional = true) sources: Tasks[Seq[PathRef]]
): Command[Unit] = Task.Command {

val _sources = T.sequence(sources.value)().iterator.flatten

palantirAction(
_sources,
check.value,
palantirformatOptions(),
palantirformatClasspath(),
palantirformatJvmArgs()
)
}

lazy val millDiscover: Discover = Discover[this.type]

private[palantirformat] def palantirAction(
sources: IterableOnce[PathRef],
check: Boolean,
options: PathRef,
classPath: Loose.Agg[PathRef],
jvmArgs: Seq[String]
)(implicit ctx: api.Ctx): Unit = {

if (check) {
ctx.log.info("checking format in java sources ...")
} else {
ctx.log.info("formatting java sources ...")
}

val mainArgs = palantirArgs(sources, check, options)

ctx.log.debug(s"running palantirformat with $mainArgs")

val exitCode = Jvm.callSubprocess(
mainClass = "com.palantir.javaformat.java.Main",
classPath = classPath.map(_.path),
jvmArgs = jvmArgs,
mainArgs = mainArgs,
workingDir = ctx.dest,
check = false
).exitCode

if (check && exitCode != 0) {
ctx.log.error(
"palantirformat aborted due to format error(s) (or invalid plugin settings/palantirformat options)"
)
throw new RuntimeException(s"palantirformat exit($exitCode)")
}
}

private def palantirArgs(
sources: IterableOnce[PathRef],
check: Boolean,
options: PathRef
): Seq[String] = {

val args = Seq.newBuilder[String]

// https://github.com/palantir/palantir-java-format/blob/dae9be4b84e2bd4d7ea346c6374fda47eee7118f/palantir-java-format/src/main/java/com/palantir/javaformat/java/CommandLineOptionsParser.java#L199
if (os.exists(options.path)) args += s"@${options.path}"

// https://github.com/palantir/palantir-java-format/blob/dae9be4b84e2bd4d7ea346c6374fda47eee7118f/palantir-java-format/src/main/java/com/palantir/javaformat/java/CommandLineOptions.java#L27
if (check) {
// do not overwrite files and exit(1) if formatting changes were detected
args += "--dry-run" += "--set-exit-if-changed"
} else {
// format in place
args += "--replace"
}

// https://github.com/palantir/palantir-java-format/blob/dae9be4b84e2bd4d7ea346c6374fda47eee7118f/palantir-java-format/src/main/java/com/palantir/javaformat/java/CommandLineOptionsParser.java#L49
args ++=
sources
.iterator
.map(_.path)
.flatMap(os.walk(_, includeTarget = true))
.filter(os.isFile)
.filter(_.ext == "java")
.map(_.toString())

args.result()
}

/**
* Path to options file for Palantir Java Format CLI at `T.workspace` `/` `palantirformat.options`.
*/
override def palantirformatOptions: T[PathRef] = Task.Source(
T.workspace / "palantirformat.options"
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
public class A {

public static void main(String[] args) {
System.out.println("hello"); // indentation should be fixed
}
}
76 changes: 76 additions & 0 deletions scalalib/test/resources/javalib/palantirformat/after/google/A.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package com.google.googlejavaformat.java.test;

/**
* Tests for AbstractTypeDeclarations, AnnotationTypeDeclarations, AnnotationTypeMemberDeclarations,
* Annotations, AnonymousClassDeclarations, ArrayAccesses, ArrayCreations, ArrayInitializers,
* ArrayTypes, AssertStatements, and Assignments.
*/
class A {
public @interface X {
int x();

int y() default
1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1
+ 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1;
}

@X(x = 1)
private @interface Y {}

// TODO(jdd): Add annotation declaration with empty body.

@X(x = 1)
@Y
protected @interface Z {}

// TODO(jdd): Include type annotations once we can include a higher language level.

int[] array1 = new int[5];
int[] array2 = new int[] {
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25,
26, 27, 28, 29, 30, 31
};
int[] array3 = {
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25,
26, 27, 28, 29, 30, 31
};
int[][] array4 = {
{0, 1}, {2, 3}, {4, 5}, {6, 7}, {8, 9}, {10, 11}, {12, 13}, {14, 15}, {16, 17}, {18, 19},
{20, 21}, {22, 23}
};
int[][][] arrayWithLongName = new int
[0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0
+ 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0]
[0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0
+ 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0]
[];

A a1 = new A() {
int x = array1[
0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0
+ 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0]
+ array2[
0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0
+ 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0]
+ array3[
0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0
+ 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0]
+ array4[0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0][
0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0];
};

void f(int something) {
assert 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1
== 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1;
assert 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1
== 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1 + 1
: "that was certainly unexpected!";
arrayWithLongName[
0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0] =
arrayWithLongName[
0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0];
something = 2 + 2 + 2 + 2 + 2 + 2 + 2 + 2 + 2 + 2 + 2 + 2 + 2 + 2 + 2 + 2 + 2 + 2 + 2 + 2 + 2
+ 2 + 2 + 2 + 2 + 2 + 2 + 2 + 2 + 2 + 2 + 2 + 2 + 2 + 2 + 2 + 2 + 2 + 2 + 2 + 2 + 2 + 2 + 2
+ 2 + 2 + 2 + 2 + 2 + 2 + 2 + 2 + 2 + 2 + 2 + 2 + 2 + 2 + 2 + 2 + 2 + 2 + 2 + 2;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.google.googlejavaformat.java.test

object A {

// plugin should ignore non Java file
// introduce a compile error to trigger failure in case this file is processed
//}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package a;

// imports should be sorted
// unsed imports should be removed
import some.Configuration;
import some.GradleException;
import some.MavenPublication;
import some.Project;
import some.PublishingExtension;
import some.VariantVersionMappingStrategy;

public class Main {

private static void configureResolvedVersionsWithVersionMapping(Project project) {
project.getPluginManager().withPlugin("maven-publish", plugin -> {
project.getExtensions()
.getByType(PublishingExtension.class)
.getPublications()
.withType(MavenPublication.class)
.configureEach(publication -> publication.versionMapping(mapping -> {
mapping.allVariants(VariantVersionMappingStrategy::fromResolutionResult);
}));
});
}

private static GradleException notFound(String group, String name, Configuration configuration) {
String actual = configuration.getIncoming().getResolutionResult().getAllComponents().stream()
.map(ResolvedComponentResult::getModuleVersion)
.map(mvi -> String.format("\t- %s:%s:%s", mvi.getGroup(), mvi.getName(), mvi.getVersion()))
.collect(Collectors.joining("\n"));
// ...
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
public class A {

public static void main(String[] args) {
System.out.println("hello"); // indentation should be fixed
}
}
Loading

0 comments on commit 09d7784

Please sign in to comment.