Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support Rome as a formatter #1663

Merged
merged 36 commits into from
May 17, 2023
Merged
Changes from 1 commit
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
5fd904e
POC: Basic support for rome
blutorange Apr 2, 2023
f62050c
Default download directory for Rome binary to the local repository
blutorange Apr 6, 2023
7705a14
Add Rome support to unreleased changes
blutorange Apr 6, 2023
c3ffe2d
Allow a command on the path to be specified as the name of the Rome
blutorange Apr 6, 2023
0d1e9fc
Add optional configuration for the config path with the config file
blutorange Apr 6, 2023
6fb765d
Merge remote-tracking branch 'origin/main' into js-ts-rome
blutorange Apr 6, 2023
c8e5430
Limit visibility of Rome internal logic classes, JavaDoc
blutorange Apr 7, 2023
cdb0ec6
Fix changes.md + Architecture visibility
blutorange Apr 8, 2023
a158493
Support formatting JSON files with Rome
blutorange Apr 8, 2023
7461c04
Add rome to feature matrix
blutorange Apr 8, 2023
17b05d7
Add rome() step to generic format for Gradle plugin
blutorange Apr 8, 2023
7481a9b
Add license header
blutorange Apr 8, 2023
afd0edb
Run spotlessApply
blutorange Apr 8, 2023
0afd327
Add rome format step to json/javascript/typescript languages
blutorange Apr 8, 2023
8c71500
Add docs for Maven plugin
blutorange Apr 8, 2023
8fa9cda
Add docs for Gradle plugin
blutorange Apr 8, 2023
2330874
Readme: Mention Rome in TOC for JS/TS
blutorange Apr 8, 2023
850b287
Fix readme for Gradle plugin with custom binary
blutorange Apr 8, 2023
7afe75b
Minor refactor, remove useless extra builder class
blutorange Apr 8, 2023
57d36b4
Fix default download dir path for Gradle plugin
blutorange Apr 8, 2023
c483ed3
Add tests for RomeStep
blutorange Apr 8, 2023
97198cb
Add tests for Maven plugin
blutorange Apr 8, 2023
77d302e
Add tests for Gradle plugin
blutorange Apr 8, 2023
971824d
Fix spotbugs issues
blutorange Apr 13, 2023
98910ef
Merge remote-tracking branch 'origin/main' into js-ts-rome
blutorange Apr 13, 2023
071df53
Run spotlessApply again
blutorange Apr 13, 2023
20729b8
Fix conversion of file URL to Path, improve exception handling
blutorange Apr 22, 2023
bc3261b
use sha-256 for downloaded rome binary checksum
blutorange Apr 22, 2023
b516c65
Remove whitespace on empty line
awa-xima May 16, 2023
8dd9b34
Merge branch 'main' into js-ts-rome
nedtwigg May 16, 2023
39d61bc
SHA256 -> SHA-256
awa-xima May 16, 2023
6c8bff1
Update changelogs.
nedtwigg May 17, 2023
ed4e5c2
Move RomeStepConfig into its own file since it's so big.
nedtwigg May 17, 2023
09d8498
Fix root path issue in the Rome formatter.
nedtwigg May 17, 2023
d8913c4
Another changelog fix.
nedtwigg May 17, 2023
a56e7e6
Use Path.of(URI) instead of manual string wrangling.
nedtwigg May 17, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
115 changes: 91 additions & 24 deletions lib/src/main/java/com/diffplug/spotless/javascript/RomeStep.java
Original file line number Diff line number Diff line change
@@ -9,6 +9,7 @@
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.attribute.PosixFilePermission;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;

@@ -32,12 +33,14 @@
public class RomeStep {
private static final Logger logger = LoggerFactory.getLogger(RomeStep.class);

private final String version;
private final String configPath;

private final String pathToExe;

private final String pathToExeDownloadDir;

private final String version;

/**
* @return The name of this format step, i.e. {@code rome}.
*/
@@ -55,7 +58,7 @@ public static String name() {
*/
public static RomeStep withExeDownload(String version, String downloadDir) {
version = version != null && !version.isBlank() ? version : defaultVersion();
return new RomeStep(version, null, downloadDir);
return new RomeStep(version, null, downloadDir, null);
}

/**
@@ -66,18 +69,7 @@ public static RomeStep withExeDownload(String version, String downloadDir) {
* @return A new Rome step that format with the given executable.
*/
public static RomeStep withExePath(String pathToExe) {
return new RomeStep(null, pathToExe, null);
}

/**
* Finds the default version for Rome when no version is specified explicitly.
* Over time this will become outdated -- people should always specify the
* version explicitly!
*
* @return The default version for Rome.
*/
private static String defaultVersion() {
return "12.0.0";
return new RomeStep(null, pathToExe, null, null);
}

/**
@@ -97,6 +89,17 @@ private static void attemptToAddPosixPermission(Path file, PosixFilePermission p
}
}

/**
* Finds the default version for Rome when no version is specified explicitly.
* Over time this will become outdated -- people should always specify the
* version explicitly!
*
* @return The default version for Rome.
*/
private static String defaultVersion() {
return "12.0.0";
}

/**
* Attempts to make the given file executable. This is a best-effort attempt,
* any errors are swallowed. Depending on the OS, the file might still be
@@ -133,10 +136,39 @@ private static String resolveNameAgainstPath(String name) throws IOException, In
}
}

private RomeStep(String version, String pathToExe, String pathToExeDownloadDir) {
/**
* Checks the config path. When the config path does not exist or when it does
* not contain a file named {@code rome.json}, an error is thrown.
*/
private static void validateRomeConfigPath(String configPath) {
if (configPath == null) {
return;
}
var path = Paths.get(configPath);
var config = path.resolve("rome.json");
if (!Files.exists(path)) {
throw new IllegalArgumentException("Rome config directory does not exist: " + path);
}
if (!Files.exists(config)) {
throw new IllegalArgumentException("Rome config does not exist: " + config);
}
}

private static void validateRomeExecutable(String resolvedPathToExe) {
if (!new File(resolvedPathToExe).isFile()) {
throw new IllegalArgumentException("Rome executable does not exist: " + resolvedPathToExe);
}
}

/**
* Checks the Rome executable. When the executable path does not exist, an error
* is thrown.
*/
private RomeStep(String version, String pathToExe, String pathToExeDownloadDir, String configPath) {
this.version = version;
this.pathToExe = pathToExe;
this.pathToExeDownloadDir = pathToExeDownloadDir;
this.configPath = configPath;
}

/**
@@ -149,6 +181,19 @@ public FormatterStep create() {
return FormatterStep.createLazy(name(), this::createState, State::toFunc);
}

/**
* Derives a new Rome step from this step by replacing the config path with the
* given value.
*
* @param configPath Config path to use. Must point to a directory which contain
* a file named {@code rome.json}.
* @return A new Rome step with the same configuration as this step, but with
* the given config file instead.
*/
public RomeStep withConfigPath(String configPath) {
return new RomeStep(version, pathToExe, pathToExeDownloadDir, configPath);
}

/**
* Resolves the Rome executable, possibly downloading it from the network, and
* creates a new state instance with the resolved executable that can format
@@ -165,14 +210,12 @@ public FormatterStep create() {
*/
private State createState() throws IOException, InterruptedException {
var resolvedPathToExe = resolveExe();
var resolvedExeFile = new File(resolvedPathToExe);
if (!resolvedExeFile.isFile()) {
throw new IllegalArgumentException("Rome executable does not exist: " + resolvedExeFile);
}
validateRomeExecutable(resolvedPathToExe);
validateRomeConfigPath(configPath);
logger.debug("Using Rome executable located at '{}'", resolvedPathToExe);
var exeSignature = FileSignature.signAsList(Collections.singleton(resolvedExeFile));
var exeSignature = FileSignature.signAsList(Collections.singleton(new File(resolvedPathToExe)));
makeExecutable(resolvedPathToExe);
return new State(resolvedPathToExe, exeSignature);
return new State(resolvedPathToExe, exeSignature, configPath);
}

/**
@@ -221,9 +264,33 @@ private static class State implements Serializable {
@SuppressWarnings("unused")
private final FileSignature exeSignature;

private State(String exe, FileSignature exeSignature) throws IOException {
/** The optional configuration file for Rome. */
private final String configPath;

private State(String exe, FileSignature exeSignature, String configPath) throws IOException {
this.pathToExe = exe;
this.exeSignature = exeSignature;
this.configPath = configPath;
}

/**
* Builds the list of arguments for the command that executes Rome to format a
* piece of code passed via stdin.
*
* @param file File to format.
* @return The Rome command to use for formatting code.
*/
private String[] buildRomeCommand(File file) {
var argList = new ArrayList<String>();
argList.add(pathToExe);
argList.add("format");
argList.add("--stdin-file-path");
argList.add(file.getName());
if (configPath != null) {
argList.add("--config-path");
argList.add(configPath);
}
return argList.toArray(String[]::new);
}

/**
@@ -242,9 +309,9 @@ private State(String exe, FileSignature exeSignature) throws IOException {
*/
private String format(ProcessRunner runner, String input, File file) throws IOException, InterruptedException {
var stdin = input.getBytes(StandardCharsets.UTF_8);
var args = new String[] { pathToExe, "format", "--stdin-file-path", file.getName() };
var args = buildRomeCommand(file);
if (logger.isDebugEnabled()) {
logger.debug("Running Rome comand to fomrat code: '{}'", String.join(", ", args));
logger.debug("Running Rome comand to format code: '{}'", String.join(", ", args));
}
return runner.exec(stdin, args).assertExitZero(StandardCharsets.UTF_8);
}
Original file line number Diff line number Diff line change
@@ -16,6 +16,15 @@
* It delegates to the Rome executable.
*/
public class RomeJs implements FormatterStepFactory {
/**
* Optional path to the directory with configuration file for Rome. The file
* must be named {@code rome.json}. When none is given, the default
* configuration is used. If this is a relative path, it is resolved against the
* project's base directory.
*/
@Parameter
private String configPath;

/**
* Optional directory where the downloaded Rome executable is placed. If this is
* a relative path, it is resolved against the project's base directory.
@@ -68,9 +77,25 @@ public FormatterStep newFormatterStep(FormatterStepConfig config) {
var downloadDir = resolveDownloadDir(config);
rome = RomeStep.withExeDownload(version, downloadDir);
}
if (configPath != null) {
var resolvedConfigFile = resolveConfigFile(config);
rome = rome.withConfigPath(resolvedConfigFile);
}
return rome.create();
}

/**
* Resolves the path to the configuration file for Rome. Relative paths are
* resolved against the project's base directory.
*
* @param config Configuration from the Maven Mojo execution with details about
* the currently executed project.
* @return The resolved path to the configuration file.
*/
private String resolveConfigFile(FormatterStepConfig config) {
return config.getFileLocator().getBaseDir().toPath().resolve(configPath).toAbsolutePath().toString();
}

/**
* Resolves the path to the Rome executable. When the path is only a file name,
* do not perform any resolution and interpret it as a command that must be on