Skip to content

Commit a158493

Browse files
committed
Support formatting JSON files with Rome
1 parent cdb0ec6 commit a158493

File tree

11 files changed

+422
-165
lines changed

11 files changed

+422
-165
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@ lib('npm.PrettierFormatterStep') +'{{yes}} | {{yes}}
9999
lib('npm.TsFmtFormatterStep') +'{{yes}} | {{yes}} | {{no}} | {{no}} |',
100100
lib('pom.SortPomStep') +'{{no}} | {{yes}} | {{no}} | {{no}} |',
101101
lib('python.BlackStep') +'{{yes}} | {{no}} | {{no}} | {{no}} |',
102+
lib('rome.RomeStep') +'{{yes}} | {{yes}} | {{no}} | {{no}} |',
102103
lib('scala.ScalaFmtStep') +'{{yes}} | {{yes}} | {{yes}} | {{no}} |',
103104
lib('sql.DBeaverSQLFormatterStep') +'{{yes}} | {{yes}} | {{yes}} | {{no}} |',
104105
extra('wtp.EclipseWtpFormatterStep') +'{{yes}} | {{yes}} | {{no}} | {{no}} |',

lib/src/main/java/com/diffplug/spotless/rome/RomeStep.java

Lines changed: 173 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -37,18 +37,38 @@ public class RomeStep {
3737
*/
3838
private final String configPath;
3939

40+
/**
41+
* The language (syntax) of the input files to format. When <code>null</code> or
42+
* the empty string, the language is detected automatically from the file name.
43+
* Currently the following languages are supported by Rome:
44+
* <ul>
45+
* <li>js (JavaScript)</li>
46+
* <li>jsx (JavaScript + JSX)</li>
47+
* <li>js? (JavaScript or JavaScript + JSX, depending on the file
48+
* extension)</li>
49+
* <li>ts (TypeScript)</li>
50+
* <li>tsx (TypeScript + JSX)</li>
51+
* <li>ts? (TypeScript or TypeScript + JSX, depending on the file
52+
* extension)</li>
53+
* <li>json (JSON)</li>
54+
* </ul>
55+
*/
56+
private final String language;
57+
4058
/**
4159
* Path to the Rome executable. Can be <code>null</code>, but either a path to
42-
* the executable of a download directory and version must be given.
60+
* the executable of a download directory and version must be given. The path
61+
* must be either an absolute path, or a file name without path separators. If
62+
* the latter, it is interpreted as a command in the user's path.
4363
*/
4464
private final String pathToExe;
4565

4666
/**
47-
* Path to the download directory for storing the download Rome executable. Can
48-
* be <code>null</code>, but either a path to the executable of a download
49-
* directory and version must be given.
67+
* Absolute path to the download directory for storing the download Rome
68+
* executable. Can be <code>null</code>, but either a path to the executable of
69+
* a download directory and version must be given.
5070
*/
51-
private final String pathToExeDownloadDir;
71+
private final String downloadDir;
5272

5373
/**
5474
* Version of Rome to download. Can be <code>null</code>, but either a path to
@@ -71,9 +91,8 @@ public static String name() {
7191
* @param downloadDir Directory where to place the downloaded executable.
7292
* @return A new Rome step that download the executable from the network.
7393
*/
74-
public static RomeStep withExeDownload(String version, String downloadDir) {
75-
version = version != null && !version.isBlank() ? version : defaultVersion();
76-
return new RomeStep(version, null, downloadDir, null);
94+
public static RomeStep.Builder withExeDownload(String version, String downloadDir) {
95+
return new RomeStep.Builder(version, null, downloadDir);
7796
}
7897

7998
/**
@@ -83,8 +102,8 @@ public static RomeStep withExeDownload(String version, String downloadDir) {
83102
* @param pathToExe Path to the Rome executable to use.
84103
* @return A new Rome step that format with the given executable.
85104
*/
86-
public static RomeStep withExePath(String pathToExe) {
87-
return new RomeStep(null, pathToExe, null, null);
105+
public static RomeStep.Builder withExePath(String pathToExe) {
106+
return new RomeStep.Builder(null, pathToExe, null);
88107
}
89108

90109
/**
@@ -180,14 +199,16 @@ private static void validateRomeExecutable(String resolvedPathToExe) {
180199
}
181200

182201
/**
183-
* Checks the Rome executable. When the executable path does not exist, an error
184-
* is thrown.
202+
* Creates a new Rome step with the configuration from the given builder.
203+
*
204+
* @param builder Builder with the configuration to use.
185205
*/
186-
private RomeStep(String version, String pathToExe, String pathToExeDownloadDir, String configPath) {
187-
this.version = version;
188-
this.pathToExe = pathToExe;
189-
this.pathToExeDownloadDir = pathToExeDownloadDir;
190-
this.configPath = configPath;
206+
private RomeStep(RomeStep.Builder builder) {
207+
this.version = builder.version != null && !builder.version.isBlank() ? builder.version : defaultVersion();
208+
this.pathToExe = builder.pathToExe;
209+
this.downloadDir = builder.downloadDir;
210+
this.configPath = builder.configPath;
211+
this.language = builder.language;
191212
}
192213

193214
/**
@@ -200,19 +221,6 @@ public FormatterStep create() {
200221
return FormatterStep.createLazy(name(), this::createState, State::toFunc);
201222
}
202223

203-
/**
204-
* Derives a new Rome step from this step by replacing the config path with the
205-
* given value.
206-
*
207-
* @param configPath Config path to use. Must point to a directory which contain
208-
* a file named {@code rome.json}.
209-
* @return A new Rome step with the same configuration as this step, but with
210-
* the given config file instead.
211-
*/
212-
public RomeStep withConfigPath(String configPath) {
213-
return new RomeStep(version, pathToExe, pathToExeDownloadDir, configPath);
214-
}
215-
216224
/**
217225
* Resolves the Rome executable, possibly downloading it from the network, and
218226
* creates a new state instance with the resolved executable that can format
@@ -234,7 +242,7 @@ private State createState() throws IOException, InterruptedException {
234242
logger.debug("Using Rome executable located at '{}'", resolvedPathToExe);
235243
var exeSignature = FileSignature.signAsList(Collections.singleton(new File(resolvedPathToExe)));
236244
makeExecutable(resolvedPathToExe);
237-
return new State(resolvedPathToExe, exeSignature, configPath);
245+
return new State(resolvedPathToExe, exeSignature, configPath, language);
238246
}
239247

240248
/**
@@ -261,13 +269,95 @@ private String resolveExe() throws IOException, InterruptedException {
261269
return pathToExe;
262270
}
263271
} else {
264-
var downloader = new RomeExecutableDownloader(Paths.get(pathToExeDownloadDir));
272+
var downloader = new RomeExecutableDownloader(Paths.get(downloadDir));
265273
var downloaded = downloader.ensureDownloaded(version).toString();
266274
makeExecutable(downloaded);
267275
return downloaded;
268276
}
269277
}
270278

279+
public final static class Builder {
280+
/** See {@link RomeStep#configPath} */
281+
private String configPath;
282+
283+
/** See {@link RomeStep#downloadDir} */
284+
private final String downloadDir;
285+
286+
/** See {@link RomeStep#language} */
287+
private String language;
288+
289+
/** See {@link RomeStep#pathToExe} */
290+
private final String pathToExe;
291+
292+
/** See {@link RomeStep#version} */
293+
private final String version;
294+
295+
/**
296+
* Creates a new builder for configuring a Rome step that can format code via
297+
* Rome. Either a version and and downloadDir, or a pathToExe must be given.
298+
*
299+
* @param version The version of Rome to download, see
300+
* {@link RomeStep#version}.
301+
* @param pathToExe The path to the Rome executable to use, see
302+
* {@link RomeStep#pathToExe}.
303+
* @param downloadDir The path to the download directory when downloading Rome
304+
* from the network, {@link RomeStep#downloadDir}.
305+
*/
306+
private Builder(String version, String pathToExe, String downloadDir) {
307+
this.version = version;
308+
this.pathToExe = pathToExe;
309+
this.downloadDir = downloadDir;
310+
}
311+
312+
/**
313+
* Creates a new Rome step for formatting code with Rome from the current
314+
* configuration of this builder.
315+
*
316+
* @return A new Rome step with the current configuration.
317+
*/
318+
public RomeStep build() {
319+
return new RomeStep(this);
320+
}
321+
322+
/**
323+
* Sets the path to the directory with the {@code rome.json} config file. When
324+
* no config path is set, the default configuration is used.
325+
*
326+
* @param configPath Config path to use. Must point to a directory which contain
327+
* a file named {@code rome.json}.
328+
* @return This builder instance for chaining method calls.
329+
*/
330+
public Builder withConfigPath(String configPath) {
331+
this.configPath = configPath;
332+
return this;
333+
}
334+
335+
/**
336+
* Sets the language of the files to format When no language is set, it is
337+
* determined automatically from the file name. The following languages are
338+
* currently supported by Rome.
339+
*
340+
* <ul>
341+
* <li>js (JavaScript)</li>
342+
* <li>jsx (JavaScript + JSX)</li>
343+
* <li>js? (JavaScript or JavaScript + JSX, depending on the file
344+
* extension)</li>
345+
* <li>ts (TypeScript)</li>
346+
* <li>tsx (TypeScript + JSX)</li>
347+
* <li>ts? (TypeScript or TypeScript + JSX, depending on the file
348+
* extension)</li>
349+
* <li>json (JSON)</li>
350+
* </ul>
351+
*
352+
* @param language The language of the files to format.
353+
* @return This builder instance for chaining method calls.
354+
*/
355+
public Builder withLanguage(String language) {
356+
this.language = language;
357+
return this;
358+
}
359+
}
360+
271361
/**
272362
* The internal state used by the Rome formatter. A state instance is created
273363
* when the spotless plugin for Maven or Gradle is executed, and reused for all
@@ -293,6 +383,12 @@ private static class State implements Serializable {
293383
*/
294384
private final String configPath;
295385

386+
/**
387+
* The language of the files to format. When <code>null</code> or the empty
388+
* string, the language is detected from the file name.
389+
*/
390+
private final String language;
391+
296392
/**
297393
* Creates a new state for instance which can format code with the given Rome
298394
* executable.
@@ -303,10 +399,11 @@ private static class State implements Serializable {
303399
* config file, can be <code>null</code>, in which case the
304400
* defaults are used.
305401
*/
306-
private State(String exe, FileSignature exeSignature, String configPath) {
402+
private State(String exe, FileSignature exeSignature, String configPath, String language) {
307403
this.pathToExe = exe;
308404
this.exeSignature = exeSignature;
309405
this.configPath = configPath;
406+
this.language = language;
310407
}
311408

312409
/**
@@ -317,11 +414,12 @@ private State(String exe, FileSignature exeSignature, String configPath) {
317414
* @return The Rome command to use for formatting code.
318415
*/
319416
private String[] buildRomeCommand(File file) {
417+
var fileName = resolveFileName(file);
320418
var argList = new ArrayList<String>();
321419
argList.add(pathToExe);
322420
argList.add("format");
323421
argList.add("--stdin-file-path");
324-
argList.add(file.getName());
422+
argList.add(fileName);
325423
if (configPath != null) {
326424
argList.add("--config-path");
327425
argList.add(configPath);
@@ -352,6 +450,47 @@ private String format(ProcessRunner runner, String input, File file) throws IOEx
352450
return runner.exec(stdin, args).assertExitZero(StandardCharsets.UTF_8);
353451
}
354452

453+
/**
454+
* The Rome executable currently does not have a parameter to specify the
455+
* expected language / syntax. Rome always determined the language from the file
456+
* extension. This method returns the file name for the desired language when a
457+
* language was requested explicitly, or the file name of the input file for
458+
* auto detection.
459+
*
460+
* @param file File to be formatted.
461+
* @return The file name to pass to the Rome executable.
462+
*/
463+
private String resolveFileName(File file) {
464+
var name = file.getName();
465+
if (language == null || language.isBlank()) {
466+
return name;
467+
}
468+
var dot = name.lastIndexOf(".");
469+
var ext = dot >= 0 ? name.substring(dot + 1) : name;
470+
switch (language) {
471+
case "js?":
472+
return "jsx".equals(ext) || "js".equals(ext) || "mjs".equals(ext) || "cjs".equals(ext) ? name
473+
: "file.js";
474+
case "ts?":
475+
return "tsx".equals(ext) || "ts".equals(ext) || "tjs".equals(ext) || "tjs".equals(ext) ? name
476+
: "file.js";
477+
case "js":
478+
return "js".equals(ext) || "mjs".equals(ext) || "cjs".equals(ext) ? name : "file.js";
479+
case "jsx":
480+
return "jsx".equals(ext) ? name : "file.jsx";
481+
case "ts":
482+
return "ts".equals(ext) || "mts".equals(ext) || "cts".equals(ext) ? name : "file.ts";
483+
case "tsx":
484+
return "tsx".equals(ext) ? name : "file.tsx";
485+
case "json":
486+
return "json".equals(ext) ? name : "file.json";
487+
// so that we can support new languages such as css or yaml when Rome adds
488+
// support for them without having to change the code
489+
default:
490+
return "file." + language;
491+
}
492+
}
493+
355494
/**
356495
* Creates a new formatter function for formatting a piece of code by delegating
357496
* to the Rome executable.

plugin-maven/src/main/java/com/diffplug/spotless/maven/FileLocator.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
import org.codehaus.plexus.resource.loader.ResourceNotFoundException;
3131
import org.codehaus.plexus.util.FileUtils;
3232

33-
import com.diffplug.spotless.maven.javascript.RomeJs;
33+
import com.diffplug.spotless.maven.rome.AbstractRome;
3434

3535
public class FileLocator {
3636

@@ -122,7 +122,7 @@ private static byte[] hash(String value) {
122122

123123
private static File findDataDir() {
124124
// E.g. ~/.m2/repository/com/diffplug/spotless/spotless-plugin-maven/1.2.3/spotless-plugin-maven-1.2.3.jar
125-
final var jarPath = Paths.get(RomeJs.class.getProtectionDomain().getCodeSource().getLocation().getPath());
125+
final var jarPath = Paths.get(AbstractRome.class.getProtectionDomain().getCodeSource().getLocation().getPath());
126126
final var base = jarPath.getParent().getParent().getParent();
127127
final var sub = base.resolve("spotless-data");
128128
return sub.toAbsolutePath().toFile();

plugin-maven/src/main/java/com/diffplug/spotless/maven/generic/Format.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,4 +40,8 @@ public String licenseHeaderDelimiter() {
4040
// do not specify a default delimiter
4141
return null;
4242
}
43+
44+
public void addRome(Rome rome) {
45+
addStepFactory(rome);
46+
}
4347
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package com.diffplug.spotless.maven.generic;
2+
3+
import org.apache.maven.plugins.annotations.Parameter;
4+
5+
import com.diffplug.spotless.maven.rome.AbstractRome;
6+
7+
/**
8+
* Generic Rome formatter step that detects the language of the input file from
9+
* the file name. It should be specified as a formatter step for a generic
10+
* {@code <format>}.
11+
*/
12+
public class Rome extends AbstractRome {
13+
/**
14+
* Gets the language (syntax) of the input files to format. When
15+
* <code>null</code> or the empty string, the language is detected automatically
16+
* from the file name. Currently the following languages are supported by Rome:
17+
* <ul>
18+
* <li>js (JavaScript)</li>
19+
* <li>jsx (JavaScript + JSX)</li>
20+
* <li>ts (TypeScript)</li>
21+
* <li>tsx (TypeScript + JSX)</li>
22+
* <li>json (JSON)</li>
23+
* </ul>
24+
*
25+
* @return The language of the input files.
26+
*/
27+
@Parameter
28+
private String language;
29+
30+
@Override
31+
protected String getLanguage() {
32+
return language;
33+
}
34+
}

0 commit comments

Comments
 (0)