From 5fd904e534dd8133cd49d989bc06a0c82a07b3dc Mon Sep 17 00:00:00 2001 From: Andre Wachsmuth Date: Sun, 2 Apr 2023 21:01:58 +0100 Subject: [PATCH 01/33] POC: Basic support for rome --- .../com/diffplug/spotless/Architecture.java | 39 ++ .../main/java/com/diffplug/spotless/OS.java | 21 + .../java/com/diffplug/spotless/Platform.java | 87 ++++ .../spotless/javascript/RomeStep.java | 121 ++++++ .../rome/RomeExecutableDownloader.java | 398 ++++++++++++++++++ .../spotless/maven/javascript/Javascript.java | 4 + .../spotless/maven/javascript/RomeJs.java | 61 +++ .../spotless/maven/typescript/Typescript.java | 5 + 8 files changed, 736 insertions(+) create mode 100644 lib/src/main/java/com/diffplug/spotless/Architecture.java create mode 100644 lib/src/main/java/com/diffplug/spotless/OS.java create mode 100644 lib/src/main/java/com/diffplug/spotless/Platform.java create mode 100644 lib/src/main/java/com/diffplug/spotless/javascript/RomeStep.java create mode 100644 lib/src/main/java/com/diffplug/spotless/rome/RomeExecutableDownloader.java create mode 100644 plugin-maven/src/main/java/com/diffplug/spotless/maven/javascript/RomeJs.java diff --git a/lib/src/main/java/com/diffplug/spotless/Architecture.java b/lib/src/main/java/com/diffplug/spotless/Architecture.java new file mode 100644 index 0000000000..211a5cb699 --- /dev/null +++ b/lib/src/main/java/com/diffplug/spotless/Architecture.java @@ -0,0 +1,39 @@ +package com.diffplug.spotless; + +/** + * Enumeration of possible computer architectures. + * + */ +public enum Architecture { + x86, x64, ppc64le, s390x, arm64, armv7l, ppc, ppc64; + + /** + * Attempts to guess the architecture of the environment running the JVM. + * + * @return The best guess for the architecture. + */ + public static Architecture guess() { + var arch = System.getProperty("os.arch"); + var version = System.getProperty("os.version"); + + if (arch.equals("ppc64le")) { + throw new IllegalArgumentException(); + } else if (arch.equals("aarch64")) { + return arm64; + } else if (arch.equals("s390x")) { + return s390x; + } else if (arch.equals("arm")) { + if (version.contains("v7")) { + return armv7l; + } else { + return arm64; + } + } else if (arch.equals("ppc64")) { + return ppc64; + } else if (arch.equals("ppc")) { + return ppc; + } else { + return arch.contains("64") ? x64 : x86; + } + } +} diff --git a/lib/src/main/java/com/diffplug/spotless/OS.java b/lib/src/main/java/com/diffplug/spotless/OS.java new file mode 100644 index 0000000000..0e05c948de --- /dev/null +++ b/lib/src/main/java/com/diffplug/spotless/OS.java @@ -0,0 +1,21 @@ +package com.diffplug.spotless; + +/** + * Enumeration of possible computer operation systems. + */ +public enum OS { + Windows, Mac, Linux, SunOS, AIX; + + /** + * Attempts to guess the OS of the environment running the JVM. + * + * @return The best guess for the architecture. + */ + public static OS guess() { + var osName = System.getProperty("os.name"); + return osName.contains("Windows") ? OS.Windows + : osName.contains("Mac") ? OS.Mac + : osName.contains("SunOS") ? OS.SunOS + : osName.toUpperCase().contains("AIX") ? OS.AIX : OS.Linux; + } +} \ No newline at end of file diff --git a/lib/src/main/java/com/diffplug/spotless/Platform.java b/lib/src/main/java/com/diffplug/spotless/Platform.java new file mode 100644 index 0000000000..c57932a7c6 --- /dev/null +++ b/lib/src/main/java/com/diffplug/spotless/Platform.java @@ -0,0 +1,87 @@ +package com.diffplug.spotless; + +/** + * Represents a platform where code is run, consisting of an operating system + * and an architecture. + */ +public class Platform { + /** + * Attempts to guess the platform of the hosting environment running the JVM + * machine. + * + * @return The best guess for the current OS and architecture. + */ + public static Platform guess() { + var os = OS.guess(); + var architecture = Architecture.guess(); + return new Platform(os, architecture); + } + + private final Architecture architecture; + + private final OS os; + + /** + * Creates a new Platform descriptor for the given OS and architecture. + * @param os Operating system of the platform. + * @param architecture Architecture of the platform. + */ + public Platform(OS os, Architecture architecture) { + this.os = os; + this.architecture = architecture; + } + + /** + * @return The architecture of this platform. + */ + public Architecture getArchitecture() { + return architecture; + } + + /** + * @return The operating system of this platform. + */ + public OS getOs() { + return os; + } + + /** + * @return Whether the operating system is Aix. + */ + public boolean isAix() { + return os == OS.AIX; + } + + /** + * @return Whether the operating system is Linux. + */ + public boolean isLinux() { + return os == OS.Linux; + } + + /** + * @return Whether the operating system is Mac. + */ + public boolean isMac() { + return os == OS.Mac; + } + + /** + * @return Whether the operating system is SunOS. + */ + public boolean isSunos() { + return os == OS.SunOS; + } + + /** + * @return Whether the operating system is Windows. + */ + public boolean isWindows() { + return os == OS.Windows; + } + + @Override + public String toString() { + return String.format("Platform[os=%s,architecture=%s]", os, architecture); + } +} diff --git a/lib/src/main/java/com/diffplug/spotless/javascript/RomeStep.java b/lib/src/main/java/com/diffplug/spotless/javascript/RomeStep.java new file mode 100644 index 0000000000..4c8a487456 --- /dev/null +++ b/lib/src/main/java/com/diffplug/spotless/javascript/RomeStep.java @@ -0,0 +1,121 @@ +package com.diffplug.spotless.javascript; + +import java.io.File; +import java.io.IOException; +import java.io.Serializable; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.attribute.PosixFilePermission; +import java.util.HashSet; + +import com.diffplug.spotless.FormatterFunc; +import com.diffplug.spotless.FormatterStep; +import com.diffplug.spotless.Platform; +import com.diffplug.spotless.ProcessRunner; +import com.diffplug.spotless.rome.RomeExecutableDownloader; + +/** + * formatter step that formats JavaScript and TypeScript code with Rome: + * https://github.com/rome/tools. + * It delegates to the Rome executable. + */ +public class RomeStep { + public static String name() { + return "rome"; + } + + private final String version; + + private final String pathToExe; + + private final String pathToExeDownloadDir; + + private RomeStep(String version, String pathToExe, String pathToExeDownloadDir) { + this.version = version != null && !version.isBlank() ? version : RomeStep.defaultVersion(); + this.pathToExe = pathToExe; + this.pathToExeDownloadDir = pathToExeDownloadDir; + } + + public FormatterStep create() { + return FormatterStep.createLazy(name(), this::createState, State::toFunc); + } + + private State createState() throws IOException, InterruptedException { + var resolvedPathToExe = resolveExe(); + makeExecutable(resolvedPathToExe); + return new State(this, resolvedPathToExe); + } + + private String resolveExe() throws IOException, InterruptedException { + if (pathToExe != null) { + return pathToExe; + } else { + var downloader = new RomeExecutableDownloader(Paths.get(pathToExeDownloadDir)); + var platform = Platform.guess(); + if (!downloader.isSupported(platform)) { + throw new IllegalStateException( + "Unsupported platform " + platform + ", please specifiy the Rome executable directly"); + } + var downloaded = downloader.ensureDownloaded(version, platform).toString(); + makeExecutable(downloaded); + return downloaded; + } + } + + public static RomeStep withVersionAndExe(String version, String pathToExe) { + return new RomeStep(version, pathToExe, null); + } + + public static RomeStep withVersionAndExeDownload(String version, String pathToExeDownloadDir) { + return new RomeStep(version, null, pathToExeDownloadDir); + } + + private static String defaultVersion() { + return "12.0.0"; + } + + private static void makeExecutable(String exe) { + var exePath = Paths.get(exe); + addPosixPermission(exePath, PosixFilePermission.GROUP_EXECUTE); + addPosixPermission(exePath, PosixFilePermission.OTHERS_EXECUTE); + addPosixPermission(exePath, PosixFilePermission.OWNER_EXECUTE); + } + + private static boolean addPosixPermission(Path file, PosixFilePermission permission) { + try { + var newPermissions = new HashSet<>(Files.getPosixFilePermissions(file)); + newPermissions.add(permission); + Files.setPosixFilePermissions(file, newPermissions); + return true; + } catch (final Exception e) { + return false; + } + } + + static class State implements Serializable { + private static final long serialVersionUID = -1825662356883926318L; + + // used for up-to-date checks and caching + final String version; + + final String pathToExe; + + State(RomeStep step, String exe) { + this.version = step.version; + this.pathToExe = exe; + } + + 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() }; + return runner.exec(stdin, args).assertExitZero(StandardCharsets.UTF_8); + } + + FormatterFunc.Closeable toFunc() { + var runner = new ProcessRunner(); + return FormatterFunc.Closeable.of(runner, this::format); + } + } +} diff --git a/lib/src/main/java/com/diffplug/spotless/rome/RomeExecutableDownloader.java b/lib/src/main/java/com/diffplug/spotless/rome/RomeExecutableDownloader.java new file mode 100644 index 0000000000..5df807003d --- /dev/null +++ b/lib/src/main/java/com/diffplug/spotless/rome/RomeExecutableDownloader.java @@ -0,0 +1,398 @@ +package com.diffplug.spotless.rome; + +import java.io.IOException; +import java.math.BigInteger; +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpClient.Redirect; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse.BodyHandlers; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.OpenOption; +import java.nio.file.Path; +import java.nio.file.StandardOpenOption; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.Objects; +import java.util.Optional; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.diffplug.spotless.Architecture; +import com.diffplug.spotless.OS; +import com.diffplug.spotless.Platform; + +/** + * Downloader for the Rome executable: + * https://github.com/rome/tools. + */ +public class RomeExecutableDownloader { + private static final Logger logger = LoggerFactory.getLogger(RomeExecutableDownloader.class); + + /** + * The checksum algorithm to use for checking the integrity of downloaded files. + */ + private static final String CHECKSUM_ALGORITHM = "MD5"; + + /** + * The pattern for {@link String#format(String, Object...) String.format()} for + * the file name of a Rome executable for a certain version and architecure. The + * first parameter is the platform, the second is the OS, the third is the + * architecture. + */ + private static final String DOWNLOAD_FILE_PATTERN = "rome-%s-%s-%s"; + + /** + * The pattern for {@link String#format(String, Object...) String.format()} for + * the platform part of the Rome executable download URL. First parameter is the + * OS, second parameter the architecture, the third the file extension. + */ + private static final String PLATFORM_PATTERN = "%s-%s%s"; + + /** + * {@link OpenOption Open options} for reading an existing file without write + * access. + */ + private static final OpenOption[] READ_OPTIONS = { StandardOpenOption.READ }; + + /** + * The pattern for {@link String#format(String, Object...) String.format()} for + * the URL where the Rome executables can be downloaded. The first parameter is + * the version, the second parameter is the OS / platform. + */ + private static final String URL_PATTERN = "https://github.com/rome/tools/releases/download/cli%%2Fv%s/rome-%s"; + + /** + * {@link OpenOption Open options} for creating a new file, overwriting the + * existing file if present. + */ + private static final OpenOption[] WRITE_OPTIONS = { StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING, + StandardOpenOption.WRITE }; + + private Path downloadDir; + + /** + * Creates a new downloader for the Rome executable. The executable files are + * stored in the given download directory. + * + * @param downloadDir Directory where + */ + public RomeExecutableDownloader(Path downloadDir) { + this.downloadDir = downloadDir; + } + + /** + * Downloads the Rome executable for the current platform from the network to + * the download directory. When the executable exists already, it is + * overwritten. + * + * @param version Desired Rome version. + * @param platform Desired platform. + * @return The path to the Rome executable. + * @throws IOException When the executable cannot be downloaded from + * the network or the file system could not be + * accessed. + * @throws InterruptedException When this thread was interrupted while + * downloading the file. + */ + public Path download(String version, Platform platform) throws IOException, InterruptedException { + var url = getDownloadUrl(version, platform); + var executablePath = getExecutablePath(version, platform); + var checksumPath = getChecksumPath(executablePath); + Files.createDirectories(executablePath.getParent()); + logger.info("Attempting to download Rome from '{}' to '{}'", url, executablePath); + var request = HttpRequest.newBuilder(URI.create(url)).GET().build(); + var handler = BodyHandlers.ofFile(executablePath, WRITE_OPTIONS); + var response = HttpClient.newBuilder().followRedirects(Redirect.NORMAL).build().send(request, handler); + if (response.statusCode() != 200) { + throw new IOException("Failed to download file from " + url + ", server returned " + response.statusCode()); + } + var downloadedFile = response.body(); + if (!Files.exists(downloadedFile) || Files.size(downloadedFile) == 0) { + throw new IOException("Failed to download file from " + url + ", file is empty or does not exist"); + } + writeChecksumFile(downloadedFile, checksumPath); + logger.debug("Rome was downloaded successfully to '{}'", downloadedFile); + return downloadedFile; + } + + /** + * Ensures that the Rome executable for the current platform exists in the + * download directory. When the executable does not exist in the download + * directory, an attempt is made to download the Rome executable from the + * network. When the executable exists already, no attempt to download it again + * is made. + * + * @param version Desired Rome version. + * @return The path to the Rome executable. + * @throws IOException When the executable cannot be downloaded from + * the network or the file system could not be + * accessed. + * @throws InterruptedException When this thread was interrupted while + * downloading the file. + */ + public Path ensureDownloaded(String version, Platform platform) throws IOException, InterruptedException { + logger.debug("Ensuring that Rome for platform '{}' is downloaded", platform); + var existing = findDownloaded(version, platform); + if (existing.isPresent()) { + logger.info("Rome was already downloaded, using executable at '{}'", existing.get()); + return existing.get(); + } else { + logger.debug("Rome was not yet downloaded, attempting to download executable"); + return download(version, platform); + } + } + + /** + * Attempts to find the Rome executable for the current platform in the download + * directory. No attempt is made to download the executable from the network. + * + * @param version Desired Rome version. + * @param platform Desired platform. + * @return The path to the Rome executable. + * @throws IOException When the executable does not exists in the download + * directory, or when the file system could not be accessed. + */ + public Optional findDownloaded(String version, Platform platform) throws IOException { + var executablePath = getExecutablePath(version, platform); + logger.debug("Checking rome executable at {}", executablePath); + return checkFileWithChecksum(executablePath) ? Optional.ofNullable(executablePath) : Optional.empty(); + } + + /** + * @param platform Platform to check. + * @return true if Rome officially supports the given platform, + * false otherwise. + */ + public boolean isSupported(Platform platform) { + var architecture = platform.getArchitecture(); + switch (platform.getOs()) { + case AIX: + return false; + case Linux: + return architecture == Architecture.x64 || architecture == Architecture.arm64; + case Mac: + return architecture == Architecture.x64 || architecture == Architecture.arm64; + case SunOS: + return false; + case Windows: + return architecture == Architecture.x64 || architecture == Architecture.arm64; + default: + return false; + } + } + + /** + * Checks whether the given file exists and matches the checksum. The checksum + * must be contained in a file next to the file to check. + * + * @param filePath File to check. + * @return true if the file exists and matches the checksum, + * false otherwise. + */ + private boolean checkFileWithChecksum(Path filePath) { + if (!Files.exists(filePath)) { + logger.debug("File '{}' does not exist yet", filePath); + return false; + } + if (Files.isDirectory(filePath)) { + logger.debug("File '{}' exists, but is a directory", filePath); + return false; + } + var checksumPath = getChecksumPath(filePath); + if (!Files.exists(checksumPath)) { + logger.debug("File '{}' exists, but checksum file '{}' does not", filePath, checksumPath); + return false; + } + if (Files.isDirectory(checksumPath)) { + logger.debug("Checksum file '{}' exists, but is a directory", checksumPath); + return false; + } + try { + var actualChecksum = computeChecksum(filePath, CHECKSUM_ALGORITHM); + var expectedChecksum = readTextFile(checksumPath, StandardCharsets.ISO_8859_1); + logger.debug("Expected checksum: {}, actual checksum: {}", expectedChecksum, actualChecksum); + return Objects.equals(expectedChecksum, actualChecksum); + } catch (final IOException ignored) { + return false; + } + } + + /** + * Computes the checksum of the given file. + * + * @param file File to process. + * @param algorithm The checksum algorithm to use. + * @return The MD5 checksum of the given file. + * @throws IOException When the file does not exist or could not be read. + */ + private String computeChecksum(Path file, String algorithm) throws IOException { + var buffer = new byte[4192]; + try (var in = Files.newInputStream(file, READ_OPTIONS)) { + var digest = MessageDigest.getInstance(algorithm); + int result; + while ((result = in.read(buffer, 0, buffer.length)) != -1) { + digest.update(buffer, 0, result); + } + var bytes = digest.digest(); + return String.format("%0" + (bytes.length * 2) + "X", new BigInteger(1, bytes)); + } catch (NoSuchAlgorithmException e) { + throw new RuntimeException(e); + } + } + + /** + * Finds the code name for the given operating system used by the Rome + * executable download URL. + * + * @param os Desired operating system. + * @return Code name for the Rome download URL. + * @throws IOException When the given OS is not supported by Rome. + */ + private String getArchitectureCodeName(Architecture architecture) throws IOException { + switch (architecture) { + case arm64: + return "arm64"; + case armv7l: + throw new IOException("Unsupported architecture: " + architecture); + case ppc: + throw new IOException("Unsupported architecture: " + architecture); + case ppc64: + throw new IOException("Unsupported architecture: " + architecture); + case ppc64le: + throw new IOException("Unsupported architecture: " + architecture); + case s390x: + throw new IOException("Unsupported architecture: " + architecture); + case x64: + return "x64"; + case x86: + throw new IOException("Unsupported architecture: " + architecture); + default: + throw new IOException("Unsupported architecture: " + architecture); + } + } + + /** + * Derives a path for the file which contains the checksum of the given file. + * + * @param file A file for which to derive the checksum file path. + * @return The path with the checksum for the given file. + */ + private Path getChecksumPath(Path file) { + return file.getParent().resolve(file.getFileName().toString() + ".md5"); + } + + /** + * Finds the URL from which the Rome executable can be downloaded. + * + * @param version Desired Rome version. + * @param platform Desired platform. + * @return The URL for the Rome executable. + * @throws IOException When the platform is not supported by Rome. + */ + private String getDownloadUrl(String version, Platform platform) throws IOException { + if (!isSupported(platform)) { + throw new IOException("Unsupported platform: " + platform); + } + var osCodeName = getOsCodeName(platform.getOs()); + var architectureCodeName = getArchitectureCodeName(platform.getArchitecture()); + var extension = getDownloadUrlExtension(platform.getOs()); + var platformString = String.format(PLATFORM_PATTERN, osCodeName, architectureCodeName, extension); + return String.format(URL_PATTERN, version, platformString); + } + + /** + * Finds the file extension of the Rome download URL for the given operating + * system. + * + * @param os Desired operating system. + * @return Extension for the Rome download URL. + * @throws IOException When the given OS is not supported by Rome. + */ + private String getDownloadUrlExtension(OS os) throws IOException { + switch (os) { + case AIX: + throw new IOException("Unsupported OS: " + os); + case Linux: + return ""; + case Mac: + return ""; + case SunOS: + throw new IOException("Unsupported OS: " + os); + case Windows: + return ".exe"; + default: + throw new IOException("Unsupported OS: " + os); + } + } + + /** + * Finds the path on the file system for the Rome executable with a given + * version and platform. + * + * @param version Desired Rome version. + * @param platform Desired platform. + * @return The path for the Rome executable. + */ + private Path getExecutablePath(String version, Platform platform) { + var fileName = String.format(DOWNLOAD_FILE_PATTERN, platform.getOs(), platform.getArchitecture(), version); + return downloadDir.resolve(fileName); + } + + /** + * Finds the code name for the given operating system used by the Rome + * executable download URL. + * + * @param os Desired operating system. + * @return Code name for the Rome download URL. + * @throws IOException When the given OS is not supported by Rome. + */ + private String getOsCodeName(OS os) throws IOException { + switch (os) { + case AIX: + throw new IOException("Unsupported OS: " + os); + case Linux: + return "linux"; + case Mac: + return "darwin"; + case SunOS: + throw new IOException("Unsupported OS: " + os); + case Windows: + return "win32"; + default: + throw new IOException("Unsupported OS: " + os); + } + } + + /** + * Reads a plain text file with the given encoding into a string. + * + * @param file File to read. + * @param charset Encoding to use. + * @return The contents of the file as a string. + * @throws IOException When the file could not be read. + */ + private String readTextFile(Path file, Charset charset) throws IOException { + try (var in = Files.newInputStream(file, READ_OPTIONS)) { + return new String(in.readAllBytes(), charset); + } + } + + /** + * Computes the checksum of the given file and writes it to the target checksum + * file, using the {@code ISO_8859_1} encoding. + * + * @param file + * @param checksumPath + * @throws IOException + */ + private void writeChecksumFile(Path file, Path checksumPath) throws IOException { + var checksum = computeChecksum(file, CHECKSUM_ALGORITHM); + try (var out = Files.newOutputStream(checksumPath, WRITE_OPTIONS)) { + out.write(checksum.getBytes(StandardCharsets.ISO_8859_1)); + } + } +} diff --git a/plugin-maven/src/main/java/com/diffplug/spotless/maven/javascript/Javascript.java b/plugin-maven/src/main/java/com/diffplug/spotless/maven/javascript/Javascript.java index 31a5917e06..b254110486 100644 --- a/plugin-maven/src/main/java/com/diffplug/spotless/maven/javascript/Javascript.java +++ b/plugin-maven/src/main/java/com/diffplug/spotless/maven/javascript/Javascript.java @@ -41,4 +41,8 @@ public String licenseHeaderDelimiter() { public void addEslint(EslintJs eslint) { addStepFactory(eslint); } + + public void addRome(RomeJs rome) { + addStepFactory(rome); + } } diff --git a/plugin-maven/src/main/java/com/diffplug/spotless/maven/javascript/RomeJs.java b/plugin-maven/src/main/java/com/diffplug/spotless/maven/javascript/RomeJs.java new file mode 100644 index 0000000000..337b05bebf --- /dev/null +++ b/plugin-maven/src/main/java/com/diffplug/spotless/maven/javascript/RomeJs.java @@ -0,0 +1,61 @@ +package com.diffplug.spotless.maven.javascript; + +import org.apache.maven.plugins.annotations.Parameter; + +import com.diffplug.spotless.FormatterStep; +import com.diffplug.spotless.javascript.RomeStep; +import com.diffplug.spotless.maven.FormatterStepConfig; +import com.diffplug.spotless.maven.FormatterStepFactory; + +/** + * Factory for creating the Rome formatter step that formats JavaScript and + * TypeScript code with Rome: + * https://github.com/rome/tools. + * It delegates to the Rome executable. + */ +public class RomeJs implements FormatterStepFactory { + /** + * Optional directory where the downloaded Rome executable is placed. This + * defaults to ${project.buildDir}/spotless/rome. You may want to + * change this to a directory outside the build directory to preserve downloaded + * files even when cleaning the project. + */ + @Parameter + private String downloadDir; + + /** + * Optional path to the Rome executable. When not given, an attempt is made to + * download the executable for the given version from the network. + */ + @Parameter + private String pathToExe; + + /** + * Rome version. When not given, a default known version is used. For stable + * builds, it is recommended that you always set the version explicitly. This + * parameter is ignored when you specify a pathToExe explicitly. + */ + @Parameter + private String version; + + @Override + public FormatterStep newFormatterStep(FormatterStepConfig config) { + RomeStep rome; + if (pathToExe != null) { + rome = RomeStep.withVersionAndExe(version, pathToExe); + } else { + var downloadDir = resolveDownloadDir(config); + rome = RomeStep.withVersionAndExeDownload(version, downloadDir); + } + return rome.create(); + } + + private String resolveDownloadDir(FormatterStepConfig config) { + // e.g. /home/user/.m2/repository/com/diffplug/spotless/spotless-maven-plugin/2.35.1-SNAPSHOT/spotless-maven-plugin-2.35.1-SNAPSHOT.jar + // var self = config.getProvisioner().provisionWithTransitives(false, "com.diffplug.spotless:spotless-maven-plugin:2.35.1-SNAPSHOT"); + // e.g. /home/user/.m2/repository/com/diffplug/spotless/spotless-maven-plugin + // return self.iterator().next().getParentFile().getParent(); + var buildDir = config.getFileLocator().getBuildDir().toPath(); + return buildDir.resolve("spotless").resolve("rome").toAbsolutePath().toString(); + } +} diff --git a/plugin-maven/src/main/java/com/diffplug/spotless/maven/typescript/Typescript.java b/plugin-maven/src/main/java/com/diffplug/spotless/maven/typescript/Typescript.java index 6ba45ab719..929ea334b1 100644 --- a/plugin-maven/src/main/java/com/diffplug/spotless/maven/typescript/Typescript.java +++ b/plugin-maven/src/main/java/com/diffplug/spotless/maven/typescript/Typescript.java @@ -21,6 +21,7 @@ import org.apache.maven.project.MavenProject; import com.diffplug.spotless.maven.FormatterFactory; +import com.diffplug.spotless.maven.javascript.RomeJs; /** * A {@link FormatterFactory} implementation that corresponds to {@code ...} configuration element. @@ -45,4 +46,8 @@ public void addTsfmt(Tsfmt tsfmt) { public void addEslint(EslintTs eslint) { addStepFactory(eslint); } + + public void addRome(RomeJs rome) { + addStepFactory(rome); + } } From f62050cc3df7446ccb45f54b91086615633588cb Mon Sep 17 00:00:00 2001 From: Andre Wachsmuth Date: Thu, 6 Apr 2023 06:52:33 +0100 Subject: [PATCH 02/33] Default download directory for Rome binary to the local repository E.g. ~/.m2/repository/com/diffplug/spotless/spotless-data/rome This is also done by e.g. the dependency-check-plugin or by the frontend-maven-plugin https://github.com/jeremylong/DependencyCheck/blob/21cd89bdc2531632e18d0b0e085c6d85112853d0/core/src/main/java/org/owasp/dependencycheck/agent/DependencyCheckScanAgent.java#L937-L941 --- .../spotless/javascript/RomeStep.java | 48 ++++++++++++----- .../diffplug/spotless/maven/FileLocator.java | 46 ++++++++++++++-- .../spotless/maven/javascript/RomeJs.java | 53 +++++++++++++------ 3 files changed, 114 insertions(+), 33 deletions(-) diff --git a/lib/src/main/java/com/diffplug/spotless/javascript/RomeStep.java b/lib/src/main/java/com/diffplug/spotless/javascript/RomeStep.java index 4c8a487456..a79281119c 100644 --- a/lib/src/main/java/com/diffplug/spotless/javascript/RomeStep.java +++ b/lib/src/main/java/com/diffplug/spotless/javascript/RomeStep.java @@ -8,8 +8,10 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.attribute.PosixFilePermission; +import java.util.Collections; import java.util.HashSet; +import com.diffplug.spotless.FileSignature; import com.diffplug.spotless.FormatterFunc; import com.diffplug.spotless.FormatterStep; import com.diffplug.spotless.Platform; @@ -19,7 +21,8 @@ /** * formatter step that formats JavaScript and TypeScript code with Rome: * https://github.com/rome/tools. - * It delegates to the Rome executable. + * It delegates to the Rome executable. The Rome executable is downloaded from + * the network when no executable path is provided explicitly. */ public class RomeStep { public static String name() { @@ -38,14 +41,21 @@ private RomeStep(String version, String pathToExe, String pathToExeDownloadDir) this.pathToExeDownloadDir = pathToExeDownloadDir; } + /** + * Creates a formatter step with the current configuration, which formats code + * by passing it to the Rome executable. + * + * @return A new formatter step for formatting with Rome. + */ public FormatterStep create() { return FormatterStep.createLazy(name(), this::createState, State::toFunc); } private State createState() throws IOException, InterruptedException { var resolvedPathToExe = resolveExe(); + var exeSignature = FileSignature.signAsList(Collections.singleton(new File(resolvedPathToExe))); makeExecutable(resolvedPathToExe); - return new State(this, resolvedPathToExe); + return new State(resolvedPathToExe, exeSignature); } private String resolveExe() throws IOException, InterruptedException { @@ -64,12 +74,25 @@ private String resolveExe() throws IOException, InterruptedException { } } - public static RomeStep withVersionAndExe(String version, String pathToExe) { - return new RomeStep(version, pathToExe, null); + /** + * Creates a Rome step that uses an executable from the given path. + * + * @param pathToExe Path to the Rome executable to use. + * @return A new Rome step that format with the given executable. + */ + public static RomeStep withExePath(String pathToExe) { + return new RomeStep(null, pathToExe, null); } - public static RomeStep withVersionAndExeDownload(String version, String pathToExeDownloadDir) { - return new RomeStep(version, null, pathToExeDownloadDir); + /** + * Creates a Rome step that downloads the Rome executable for the given version. + * + * @param version Version of the Rome executable to download. + * @param downloadDir Directory where to place the downloaded executable. + * @return A new Rome step that download the executable from the network. + */ + public static RomeStep withExeDownload(String version, String downloadDir) { + return new RomeStep(version, null, downloadDir); } private static String defaultVersion() { @@ -95,16 +118,17 @@ private static boolean addPosixPermission(Path file, PosixFilePermission permiss } static class State implements Serializable { - private static final long serialVersionUID = -1825662356883926318L; - - // used for up-to-date checks and caching - final String version; + private static final long serialVersionUID = -5884229077231467806L; + /** Path to the exe file */ final String pathToExe; - State(RomeStep step, String exe) { - this.version = step.version; + /** The signature of the exe file, if any, used for caching. */ + final FileSignature exeSignature; + + State(String exe, FileSignature exeSignature) throws IOException { this.pathToExe = exe; + this.exeSignature = exeSignature; } String format(ProcessRunner runner, String input, File file) throws IOException, InterruptedException { diff --git a/plugin-maven/src/main/java/com/diffplug/spotless/maven/FileLocator.java b/plugin-maven/src/main/java/com/diffplug/spotless/maven/FileLocator.java index 7ea998dc95..1450b2873a 100644 --- a/plugin-maven/src/main/java/com/diffplug/spotless/maven/FileLocator.java +++ b/plugin-maven/src/main/java/com/diffplug/spotless/maven/FileLocator.java @@ -19,6 +19,7 @@ import static java.nio.charset.StandardCharsets.UTF_8; import java.io.File; +import java.nio.file.Paths; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.Base64; @@ -29,22 +30,25 @@ import org.codehaus.plexus.resource.loader.ResourceNotFoundException; import org.codehaus.plexus.util.FileUtils; +import com.diffplug.spotless.maven.javascript.RomeJs; + public class FileLocator { static final String TMP_RESOURCE_FILE_PREFIX = "spotless-resource-"; private final ResourceManager resourceManager; - private final File baseDir, buildDir; + private final File baseDir, buildDir, dataDir; public FileLocator(ResourceManager resourceManager, File baseDir, File buildDir) { this.resourceManager = Objects.requireNonNull(resourceManager); this.baseDir = Objects.requireNonNull(baseDir); this.buildDir = Objects.requireNonNull(buildDir); + this.dataDir = findDataDir(); } /** - * If the given path is a local file returns it as such unchanged, - * otherwise extracts the given resource to a randomly-named file in the build folder. + * If the given path is a local file returns it as such unchanged, otherwise + * extracts the given resource to a randomly-named file in the build folder. */ public File locateFile(String path) { if (isNullOrEmpty(path)) { @@ -62,18 +66,42 @@ public File locateFile(String path) { } catch (ResourceNotFoundException e) { throw new RuntimeException("Unable to locate file with path: " + path, e); } catch (FileResourceCreationException e) { - throw new RuntimeException("Unable to create temporary file '" + outputFile + "' in the output directory", e); + throw new RuntimeException("Unable to create temporary file '" + outputFile + "' in the output directory", + e); } } + /** + * Finds the base directory of the Maven or Gradle project on which spotless is + * currently being executed. + * + * @return The base directory of the current Maven or Gradel project. + */ public File getBaseDir() { return baseDir; } + /** + * Finds the build directory (e.g. /target) of the Maven or Gradle + * project on which spotless is currently being executed. + * + * @return The project build directory of the current Maven or Gradle project. + */ public File getBuildDir() { return buildDir; } + /** + * Finds the data directory that can be used for storing shared data such as + * downloaded files globally. This is a directory in the local repository, e.g. + * ~/.m2/repository/com/diffplus/spotless/spotless-data. + * + * @return The directory for storing shared data. + */ + public File getDataDir() { + return dataDir; + } + private static String tmpOutputFileName(String path) { String extension = FileUtils.extension(path); byte[] pathHash = hash(path); @@ -91,4 +119,14 @@ private static byte[] hash(String value) { messageDigest.update(value.getBytes(UTF_8)); return messageDigest.digest(); } + + private static File findDataDir() { + // One of + // ~/.m2/repository/com/diffplug/spotless/spotless-plugin-maven/1.2.3/spotless-plugin-maven-1.2.3.jar + // ~/.m2/repository/com/diffplug/spotless/spotless-plugin-maven/1.2.3/spotless-plugin-gradle-1.2.3.jar + final var jarPath = Paths.get(RomeJs.class.getProtectionDomain().getCodeSource().getLocation().getPath()); + final var base = jarPath.getParent().getParent().getParent(); + final var sub = base.resolve("spotless-data"); + return sub.toAbsolutePath().toFile(); + } } diff --git a/plugin-maven/src/main/java/com/diffplug/spotless/maven/javascript/RomeJs.java b/plugin-maven/src/main/java/com/diffplug/spotless/maven/javascript/RomeJs.java index 337b05bebf..b68e5fef42 100644 --- a/plugin-maven/src/main/java/com/diffplug/spotless/maven/javascript/RomeJs.java +++ b/plugin-maven/src/main/java/com/diffplug/spotless/maven/javascript/RomeJs.java @@ -15,25 +15,35 @@ */ public class RomeJs implements FormatterStepFactory { /** - * Optional directory where the downloaded Rome executable is placed. This - * defaults to ${project.buildDir}/spotless/rome. You may want to - * change this to a directory outside the build directory to preserve downloaded - * files even when cleaning the project. + * Optional directory where the downloaded Rome executable is placed. If this is + * a relative path, it is resolved against the project's base directory. + * Defaults to + * ~/.m2/repository/com/diffplug/spotless/spotless-data/rome. + *

+ * You can use an expression like ${user.home}/rome if you want to + * use the home directory, or ${project.build.directory if you want + * to use the ptarget directory of the current project. */ @Parameter private String downloadDir; /** - * Optional path to the Rome executable. When not given, an attempt is made to - * download the executable for the given version from the network. + * Optional path to the Rome executable. Either a version or a + * pathToExe should be specified. When not given, an attempt is + * made to download the executable for the given version from the network. When + * given, the executable is used and the version parameter is + * ignored. */ @Parameter private String pathToExe; /** - * Rome version. When not given, a default known version is used. For stable - * builds, it is recommended that you always set the version explicitly. This - * parameter is ignored when you specify a pathToExe explicitly. + * Rome version to download, applies only when no pathToExe is + * specified explicitly. Either a version or a + * pathToExe should be specified. When not given, a default known + * version is used. For stable builds, it is recommended that you always set the + * version explicitly. This parameter is ignored when you specify a + * pathToExe explicitly. */ @Parameter private String version; @@ -42,20 +52,29 @@ public class RomeJs implements FormatterStepFactory { public FormatterStep newFormatterStep(FormatterStepConfig config) { RomeStep rome; if (pathToExe != null) { - rome = RomeStep.withVersionAndExe(version, pathToExe); + rome = RomeStep.withExePath(pathToExe); } else { var downloadDir = resolveDownloadDir(config); - rome = RomeStep.withVersionAndExeDownload(version, downloadDir); + rome = RomeStep.withExeDownload(version, downloadDir); } return rome.create(); } + /** + * Resolves the directory to use for storing downloaded Rome executable. When a + * {@link #downloadDir} is given, use that directory, resolved against the + * current project's directory. Otherwise, use the {@code Rome} sub folder in + * the shared data directory. + * + * @param config Configuration for this step. + * @return The download directory for the Rome executable. + */ private String resolveDownloadDir(FormatterStepConfig config) { - // e.g. /home/user/.m2/repository/com/diffplug/spotless/spotless-maven-plugin/2.35.1-SNAPSHOT/spotless-maven-plugin-2.35.1-SNAPSHOT.jar - // var self = config.getProvisioner().provisionWithTransitives(false, "com.diffplug.spotless:spotless-maven-plugin:2.35.1-SNAPSHOT"); - // e.g. /home/user/.m2/repository/com/diffplug/spotless/spotless-maven-plugin - // return self.iterator().next().getParentFile().getParent(); - var buildDir = config.getFileLocator().getBuildDir().toPath(); - return buildDir.resolve("spotless").resolve("rome").toAbsolutePath().toString(); + final var fileLocator = config.getFileLocator(); + if (downloadDir != null && !downloadDir.isBlank()) { + return fileLocator.getBaseDir().toPath().resolve(downloadDir).toAbsolutePath().toString(); + } else { + return fileLocator.getDataDir().toPath().resolve("rome").toString(); + } } } From 7705a1453c425fdaeee41ee6892a4d605c3cb22f Mon Sep 17 00:00:00 2001 From: Andre Wachsmuth Date: Thu, 6 Apr 2023 07:03:39 +0100 Subject: [PATCH 03/33] Add Rome support to unreleased changes --- CHANGES.md | 1 + plugin-gradle/CHANGES.md | 1 + plugin-maven/CHANGES.md | 1 + 3 files changed, 3 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index 77c49efe2a..e4d2fcb10c 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -15,6 +15,7 @@ We adhere to the [keepachangelog](https://keepachangelog.com/en/1.0.0/) format ( ### Changes * **POTENTIALLY BREAKING** Converted `googleJavaFormat` to a compile-only dependency and drop support for versions < `1.8`. ([#1630](https://github.com/diffplug/spotless/pull/1630)) * Bump default `googleJavaFormat` version `1.15.0` -> `1.16.0`. ([#1630](https://github.com/diffplug/spotless/pull/1630)) +* Support Rome as a formatter for JavaScript and TypeScript code. Adds a new `rome` step to `javascript` and `typescript` formatter configurations. https://github.com/diffplug/spotless/pull/1663 ## [2.37.0] - 2023-03-13 ### Added diff --git a/plugin-gradle/CHANGES.md b/plugin-gradle/CHANGES.md index 0a0cabcca0..e9cbf115f0 100644 --- a/plugin-gradle/CHANGES.md +++ b/plugin-gradle/CHANGES.md @@ -17,6 +17,7 @@ We adhere to the [keepachangelog](https://keepachangelog.com/en/1.0.0/) format ( ### Changes * **POTENTIALLY BREAKING** Drop support for `googleJavaFormat` versions < `1.8`. ([#1630](https://github.com/diffplug/spotless/pull/1630)) * Bump default `googleJavaFormat` version `1.15.0` -> `1.16.0`. ([#1630](https://github.com/diffplug/spotless/pull/1630)) +* Support Rome as a formatter for JavaScript and TypeScript code. Adds a new `rome` step to `javascript` and `typescript` formatter configurations. https://github.com/diffplug/spotless/pull/1663 ## [6.17.0] - 2023-03-13 ### Added diff --git a/plugin-maven/CHANGES.md b/plugin-maven/CHANGES.md index 421b5e56e7..f13f989028 100644 --- a/plugin-maven/CHANGES.md +++ b/plugin-maven/CHANGES.md @@ -6,6 +6,7 @@ We adhere to the [keepachangelog](https://keepachangelog.com/en/1.0.0/) format ( ### Changes * **POTENTIALLY BREAKING** Drop support for `googleJavaFormat` versions < `1.8`. ([#1630](https://github.com/diffplug/spotless/pull/1630)) * Bump default `googleJavaFormat` version `1.15.0` -> `1.16.0`. ([#1630](https://github.com/diffplug/spotless/pull/1630)) +* Support Rome as a formatter for JavaScript and TypeScript code. Adds a new `rome` step to `javascript` and `typescript` formatter configurations. https://github.com/diffplug/spotless/pull/1663 ## [2.35.0] - 2023-03-13 ### Added From c3ffe2dd716582cc81f96449148c0a4d653dc159 Mon Sep 17 00:00:00 2001 From: Andre Wachsmuth Date: Thu, 6 Apr 2023 19:33:31 +0100 Subject: [PATCH 04/33] Allow a command on the path to be specified as the name of the Rome executable instead of the path to the executable --- .../spotless/javascript/RomeStep.java | 222 +++++++++++++----- .../rome/RomeExecutableDownloader.java | 2 +- .../spotless/maven/javascript/RomeJs.java | 34 ++- 3 files changed, 200 insertions(+), 58 deletions(-) diff --git a/lib/src/main/java/com/diffplug/spotless/javascript/RomeStep.java b/lib/src/main/java/com/diffplug/spotless/javascript/RomeStep.java index a79281119c..8147464f7f 100644 --- a/lib/src/main/java/com/diffplug/spotless/javascript/RomeStep.java +++ b/lib/src/main/java/com/diffplug/spotless/javascript/RomeStep.java @@ -3,6 +3,7 @@ import java.io.File; import java.io.IOException; import java.io.Serializable; +import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; @@ -11,7 +12,11 @@ import java.util.Collections; import java.util.HashSet; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + import com.diffplug.spotless.FileSignature; +import com.diffplug.spotless.ForeignExe; import com.diffplug.spotless.FormatterFunc; import com.diffplug.spotless.FormatterStep; import com.diffplug.spotless.Platform; @@ -25,9 +30,7 @@ * the network when no executable path is provided explicitly. */ public class RomeStep { - public static String name() { - return "rome"; - } + private static final Logger logger = LoggerFactory.getLogger(RomeStep.class); private final String version; @@ -35,8 +38,103 @@ public static String name() { private final String pathToExeDownloadDir; + /** + * @return The name of this format step, i.e. {@code rome}. + */ + public static String name() { + return "rome"; + } + + /** + * Creates a Rome step that format code by downloading to the given Rome + * version. The executable is downloaded from the network. + * + * @param version Version of the Rome executable to download. + * @param downloadDir Directory where to place the downloaded executable. + * @return A new Rome step that download the executable from the network. + */ + public static RomeStep withExeDownload(String version, String downloadDir) { + version = version != null && !version.isBlank() ? version : defaultVersion(); + return new RomeStep(version, null, downloadDir); + } + + /** + * Creates a Rome step that formats code by delegating to the Rome executable + * located at the given path. + * + * @param pathToExe Path to the Rome executable to use. + * @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"; + } + + /** + * Attempts to add a POSIX permission to the given file, ignoring any errors. + * All existing permissions on the file are preserved and the new permission is + * added, if possible. + * + * @param file File or directory to which to add a permission. + * @param permission The POSIX permission to add. + */ + private static void attemptToAddPosixPermission(Path file, PosixFilePermission permission) { + try { + var newPermissions = new HashSet<>(Files.getPosixFilePermissions(file)); + newPermissions.add(permission); + Files.setPosixFilePermissions(file, newPermissions); + } catch (final Exception ignore) { + } + } + + /** + * 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 + * executable even if this method fails. The user will get a descriptive error + * later when we attempt to execute the Rome executable. + * + * @param filePath Path to the file to make executable. + */ + private static void makeExecutable(String filePath) { + var exePath = Paths.get(filePath); + attemptToAddPosixPermission(exePath, PosixFilePermission.GROUP_EXECUTE); + attemptToAddPosixPermission(exePath, PosixFilePermission.OTHERS_EXECUTE); + attemptToAddPosixPermission(exePath, PosixFilePermission.OWNER_EXECUTE); + } + + /** + * Finds the absolute path of a command on the user's path. Uses {@code which} + * for Linux and {@code where} for Windows. + * + * @param name Name of the command to resolve. + * @return The absolute path of the command's executable. + * @throws IOException When the command could not be resolved. + * @throws InterruptedException When this thread was interrupted while waiting + * to the which command to finish. + */ + private static String resolveNameAgainstPath(String name) throws IOException, InterruptedException { + try (var runner = new ProcessRunner()) { + var cmdWhich = runner.shellWinUnix("where " + name, "which " + name); + if (cmdWhich.exitNotZero()) { + throw new IOException("Unable to find " + name + " on path via command " + cmdWhich); + } else { + return cmdWhich.assertExitZero(Charset.defaultCharset()).trim(); + } + } + } + private RomeStep(String version, String pathToExe, String pathToExeDownloadDir) { - this.version = version != null && !version.isBlank() ? version : RomeStep.defaultVersion(); + this.version = version; this.pathToExe = pathToExe; this.pathToExeDownloadDir = pathToExeDownloadDir; } @@ -51,16 +149,55 @@ public FormatterStep create() { return FormatterStep.createLazy(name(), this::createState, State::toFunc); } + /** + * Resolves the Rome executable, possibly downloading it from the network, and + * creates a new state instance with the resolved executable that can format + * code via Rome. + * + * @return The state instance for formatting code via Rome. + * @throws IOException When any file system or network operations + * failed, such as when the Rome executable could + * not be downloaded, or when the given executable + * does not exist. + * @throws InterruptedException When the Rome executable needs to be downloaded + * and this thread was interrupted while waiting + * for the download to complete. + */ private State createState() throws IOException, InterruptedException { var resolvedPathToExe = resolveExe(); - var exeSignature = FileSignature.signAsList(Collections.singleton(new File(resolvedPathToExe))); + var resolvedExeFile = new File(resolvedPathToExe); + if (!resolvedExeFile.isFile()) { + throw new IllegalArgumentException("Rome executable does not exist: " + resolvedExeFile); + } + logger.debug("Using Rome executable located at '{}'", resolvedPathToExe); + var exeSignature = FileSignature.signAsList(Collections.singleton(resolvedExeFile)); makeExecutable(resolvedPathToExe); return new State(resolvedPathToExe, exeSignature); } + /** + * Resolves the path to the Rome executable, given the configuration of this + * step. When the path to the Rome executable is given explicitly, that path is + * used as-is. Otherwise, at attempt is made to download the Rome executable for + * the configured version from the network, unless it was already downloaded and + * is available in the cache. + * + * @return The path to the resolved Rome executable. + * @throws IOException When any file system or network operations + * failed, such as when the Rome executable could + * not be downloaded. + * @throws InterruptedException When the Rome executable needs to be downloaded + * and this thread was interrupted while waiting + * for the download to complete. + */ private String resolveExe() throws IOException, InterruptedException { + new ForeignExe(); if (pathToExe != null) { - return pathToExe; + if (Paths.get(pathToExe).getNameCount() == 1) { + return resolveNameAgainstPath(pathToExe); + } else { + return pathToExe; + } } else { var downloader = new RomeExecutableDownloader(Paths.get(pathToExeDownloadDir)); var platform = Platform.guess(); @@ -74,70 +211,45 @@ private String resolveExe() throws IOException, InterruptedException { } } - /** - * Creates a Rome step that uses an executable from the given path. - * - * @param pathToExe Path to the Rome executable to use. - * @return A new Rome step that format with the given executable. - */ - public static RomeStep withExePath(String pathToExe) { - return new RomeStep(null, pathToExe, null); - } - - /** - * Creates a Rome step that downloads the Rome executable for the given version. - * - * @param version Version of the Rome executable to download. - * @param downloadDir Directory where to place the downloaded executable. - * @return A new Rome step that download the executable from the network. - */ - public static RomeStep withExeDownload(String version, String downloadDir) { - return new RomeStep(version, null, downloadDir); - } - - private static String defaultVersion() { - return "12.0.0"; - } - - private static void makeExecutable(String exe) { - var exePath = Paths.get(exe); - addPosixPermission(exePath, PosixFilePermission.GROUP_EXECUTE); - addPosixPermission(exePath, PosixFilePermission.OTHERS_EXECUTE); - addPosixPermission(exePath, PosixFilePermission.OWNER_EXECUTE); - } - - private static boolean addPosixPermission(Path file, PosixFilePermission permission) { - try { - var newPermissions = new HashSet<>(Files.getPosixFilePermissions(file)); - newPermissions.add(permission); - Files.setPosixFilePermissions(file, newPermissions); - return true; - } catch (final Exception e) { - return false; - } - } - - static class State implements Serializable { + private static class State implements Serializable { private static final long serialVersionUID = -5884229077231467806L; /** Path to the exe file */ - final String pathToExe; + private final String pathToExe; /** The signature of the exe file, if any, used for caching. */ - final FileSignature exeSignature; + @SuppressWarnings("unused") + private final FileSignature exeSignature; - State(String exe, FileSignature exeSignature) throws IOException { + private State(String exe, FileSignature exeSignature) throws IOException { this.pathToExe = exe; this.exeSignature = exeSignature; } - String format(ProcessRunner runner, String input, File file) throws IOException, InterruptedException { + /** + * Formats the given piece of code by delegating to the Rome executable. The + * code is passed to Rome via stdin, the file name is used by Rome only to + * determine the code syntax (e.g. JavaScript or TypeScript). + * + * @param runner Process runner for invoking the Rome executable. + * @param input Code to format. + * @param file File to format. + * @return The formatted code. + * @throws IOException When a file system error occurred while + * executing Rome. + * @throws InterruptedException When this thread was interrupted while waiting + * for Rome to finish formatting. + */ + 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() }; + if (logger.isDebugEnabled()) { + logger.debug("Running Rome comand to fomrat code: '{}'", String.join(", ", args)); + } return runner.exec(stdin, args).assertExitZero(StandardCharsets.UTF_8); } - FormatterFunc.Closeable toFunc() { + private FormatterFunc.Closeable toFunc() { var runner = new ProcessRunner(); return FormatterFunc.Closeable.of(runner, this::format); } diff --git a/lib/src/main/java/com/diffplug/spotless/rome/RomeExecutableDownloader.java b/lib/src/main/java/com/diffplug/spotless/rome/RomeExecutableDownloader.java index 5df807003d..dfddb4358a 100644 --- a/lib/src/main/java/com/diffplug/spotless/rome/RomeExecutableDownloader.java +++ b/lib/src/main/java/com/diffplug/spotless/rome/RomeExecutableDownloader.java @@ -138,7 +138,7 @@ public Path ensureDownloaded(String version, Platform platform) throws IOExcepti logger.debug("Ensuring that Rome for platform '{}' is downloaded", platform); var existing = findDownloaded(version, platform); if (existing.isPresent()) { - logger.info("Rome was already downloaded, using executable at '{}'", existing.get()); + logger.debug("Rome was already downloaded, using executable at '{}'", existing.get()); return existing.get(); } else { logger.debug("Rome was not yet downloaded, attempting to download executable"); diff --git a/plugin-maven/src/main/java/com/diffplug/spotless/maven/javascript/RomeJs.java b/plugin-maven/src/main/java/com/diffplug/spotless/maven/javascript/RomeJs.java index b68e5fef42..0fc37ebdfc 100644 --- a/plugin-maven/src/main/java/com/diffplug/spotless/maven/javascript/RomeJs.java +++ b/plugin-maven/src/main/java/com/diffplug/spotless/maven/javascript/RomeJs.java @@ -1,5 +1,7 @@ package com.diffplug.spotless.maven.javascript; +import java.nio.file.Paths; + import org.apache.maven.plugins.annotations.Parameter; import com.diffplug.spotless.FormatterStep; @@ -22,7 +24,7 @@ public class RomeJs implements FormatterStepFactory { *

* You can use an expression like ${user.home}/rome if you want to * use the home directory, or ${project.build.directory if you want - * to use the ptarget directory of the current project. + * to use the target directory of the current project. */ @Parameter private String downloadDir; @@ -33,6 +35,14 @@ public class RomeJs implements FormatterStepFactory { * made to download the executable for the given version from the network. When * given, the executable is used and the version parameter is * ignored. + *

+ * When an absolute path is given, that path is used as-is. When a relative path + * is given, it is resolved against the project's base directory. When only a + * file name (i.e. without any slashes or back slash path separators such as + * {@code rome}) is given, this is interpreted as the name of a command with + * executable that is in your {@code path} environment variable. Use + * {@code ./executable-name} if you want to use an executable in the project's + * base directory. */ @Parameter private String pathToExe; @@ -52,7 +62,8 @@ public class RomeJs implements FormatterStepFactory { public FormatterStep newFormatterStep(FormatterStepConfig config) { RomeStep rome; if (pathToExe != null) { - rome = RomeStep.withExePath(pathToExe); + var resolvedExePath = resolveExePath(config); + rome = RomeStep.withExePath(resolvedExePath); } else { var downloadDir = resolveDownloadDir(config); rome = RomeStep.withExeDownload(version, downloadDir); @@ -60,6 +71,25 @@ public FormatterStep newFormatterStep(FormatterStepConfig config) { return rome.create(); } + /** + * 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 + * the user's path. Otherwise resolve the executable path 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 Rome executable. + */ + private String resolveExePath(FormatterStepConfig config) { + var path = Paths.get(pathToExe); + if (path.getNameCount() == 1) { + return path.toString(); + } else { + return config.getFileLocator().getBaseDir().toPath().resolve(path).toAbsolutePath().toString(); + } + } + /** * Resolves the directory to use for storing downloaded Rome executable. When a * {@link #downloadDir} is given, use that directory, resolved against the From 0d1e9fcf8ba0fdb5d0abbc67a685191992e70769 Mon Sep 17 00:00:00 2001 From: Andre Wachsmuth Date: Thu, 6 Apr 2023 20:24:59 +0100 Subject: [PATCH 05/33] Add optional configuration for the config path with the config file --- .../spotless/javascript/RomeStep.java | 115 ++++++++++++++---- .../spotless/maven/javascript/RomeJs.java | 25 ++++ 2 files changed, 116 insertions(+), 24 deletions(-) diff --git a/lib/src/main/java/com/diffplug/spotless/javascript/RomeStep.java b/lib/src/main/java/com/diffplug/spotless/javascript/RomeStep.java index 8147464f7f..2794b38f44 100644 --- a/lib/src/main/java/com/diffplug/spotless/javascript/RomeStep.java +++ b/lib/src/main/java/com/diffplug/spotless/javascript/RomeStep.java @@ -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(); + 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); } diff --git a/plugin-maven/src/main/java/com/diffplug/spotless/maven/javascript/RomeJs.java b/plugin-maven/src/main/java/com/diffplug/spotless/maven/javascript/RomeJs.java index 0fc37ebdfc..180fb9274f 100644 --- a/plugin-maven/src/main/java/com/diffplug/spotless/maven/javascript/RomeJs.java +++ b/plugin-maven/src/main/java/com/diffplug/spotless/maven/javascript/RomeJs.java @@ -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 From c8e5430a1564f3504181e8689a47e5476ebc77a7 Mon Sep 17 00:00:00 2001 From: Andre Wachsmuth Date: Fri, 7 Apr 2023 08:06:36 +0100 Subject: [PATCH 06/33] Limit visibility of Rome internal logic classes, JavaDoc --- CHANGES.md | 3 +- .../com/diffplug/spotless/Architecture.java | 39 ------ .../main/java/com/diffplug/spotless/OS.java | 21 --- .../diffplug/spotless/rome/Architecture.java | 54 ++++++++ .../java/com/diffplug/spotless/rome/OS.java | 41 ++++++ .../spotless/{ => rome}/Platform.java | 29 ++-- .../rome/RomeExecutableDownloader.java | 126 +++++++----------- .../{javascript => rome}/RomeStep.java | 70 ++++++++-- plugin-gradle/CHANGES.md | 3 +- plugin-maven/CHANGES.md | 4 +- .../diffplug/spotless/maven/FileLocator.java | 4 +- .../spotless/maven/javascript/RomeJs.java | 2 +- 12 files changed, 213 insertions(+), 183 deletions(-) delete mode 100644 lib/src/main/java/com/diffplug/spotless/Architecture.java delete mode 100644 lib/src/main/java/com/diffplug/spotless/OS.java create mode 100644 lib/src/main/java/com/diffplug/spotless/rome/Architecture.java create mode 100644 lib/src/main/java/com/diffplug/spotless/rome/OS.java rename lib/src/main/java/com/diffplug/spotless/{ => rome}/Platform.java (76%) rename lib/src/main/java/com/diffplug/spotless/{javascript => rome}/RomeStep.java (82%) diff --git a/CHANGES.md b/CHANGES.md index ab49d9d096..57b831b8ad 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -16,13 +16,14 @@ We adhere to the [keepachangelog](https://keepachangelog.com/en/1.0.0/) format ( * Support configuration of mirrors for P2 repositories in `EquoBasedStepBuilder` ([#1629](https://github.com/diffplug/spotless/issues/1629)). * The `style` option in Palantir Java Format ([#1654](https://github.com/diffplug/spotless/pull/1654)). * Added formatter for Gherkin feature files ([#1649](https://github.com/diffplug/spotless/issues/1649)). +* Support Rome as a formatter for JavaScript and TypeScript code. Adds a new `rome` step to `javascript` and `typescript` formatter configurations. https://github.com/diffplug/spotless/pull/1663 ### Changes * **POTENTIALLY BREAKING** Converted `googleJavaFormat` to a compile-only dependency and drop support for versions < `1.8`. ([#1630](https://github.com/diffplug/spotless/pull/1630)) * Bump default `cleanthat` version to latest `2.6` -> `2.13`. ([#1589](https://github.com/diffplug/spotless/pull/1589) and [#1661](https://github.com/diffplug/spotless/pull/1661)) * Bump default `diktat` version `1.2.4.2` -> `1.2.5`. ([#1631](https://github.com/diffplug/spotless/pull/1631)) * Bump default `flexmark` version `0.62.2` -> `0.64.0`. ([#1302](https://github.com/diffplug/spotless/pull/1302)) * Bump default `googleJavaFormat` version `1.15.0` -> `1.16.0`. ([#1630](https://github.com/diffplug/spotless/pull/1630)) -* Support Rome as a formatter for JavaScript and TypeScript code. Adds a new `rome` step to `javascript` and `typescript` formatter configurations. https://github.com/diffplug/spotless/pull/1663 + * Bump default `scalafmt` version `3.7.1` -> `3.7.3`. ([#1584](https://github.com/diffplug/spotless/pull/1584)) * Bump default Eclipse formatters for the 2023-03 release. ([#1662](https://github.com/diffplug/spotless/pull/1662)) * JDT and GrEclipse `4.26` -> `4.27` diff --git a/lib/src/main/java/com/diffplug/spotless/Architecture.java b/lib/src/main/java/com/diffplug/spotless/Architecture.java deleted file mode 100644 index 211a5cb699..0000000000 --- a/lib/src/main/java/com/diffplug/spotless/Architecture.java +++ /dev/null @@ -1,39 +0,0 @@ -package com.diffplug.spotless; - -/** - * Enumeration of possible computer architectures. - * - */ -public enum Architecture { - x86, x64, ppc64le, s390x, arm64, armv7l, ppc, ppc64; - - /** - * Attempts to guess the architecture of the environment running the JVM. - * - * @return The best guess for the architecture. - */ - public static Architecture guess() { - var arch = System.getProperty("os.arch"); - var version = System.getProperty("os.version"); - - if (arch.equals("ppc64le")) { - throw new IllegalArgumentException(); - } else if (arch.equals("aarch64")) { - return arm64; - } else if (arch.equals("s390x")) { - return s390x; - } else if (arch.equals("arm")) { - if (version.contains("v7")) { - return armv7l; - } else { - return arm64; - } - } else if (arch.equals("ppc64")) { - return ppc64; - } else if (arch.equals("ppc")) { - return ppc; - } else { - return arch.contains("64") ? x64 : x86; - } - } -} diff --git a/lib/src/main/java/com/diffplug/spotless/OS.java b/lib/src/main/java/com/diffplug/spotless/OS.java deleted file mode 100644 index 0e05c948de..0000000000 --- a/lib/src/main/java/com/diffplug/spotless/OS.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.diffplug.spotless; - -/** - * Enumeration of possible computer operation systems. - */ -public enum OS { - Windows, Mac, Linux, SunOS, AIX; - - /** - * Attempts to guess the OS of the environment running the JVM. - * - * @return The best guess for the architecture. - */ - public static OS guess() { - var osName = System.getProperty("os.name"); - return osName.contains("Windows") ? OS.Windows - : osName.contains("Mac") ? OS.Mac - : osName.contains("SunOS") ? OS.SunOS - : osName.toUpperCase().contains("AIX") ? OS.AIX : OS.Linux; - } -} \ No newline at end of file diff --git a/lib/src/main/java/com/diffplug/spotless/rome/Architecture.java b/lib/src/main/java/com/diffplug/spotless/rome/Architecture.java new file mode 100644 index 0000000000..064bd52b32 --- /dev/null +++ b/lib/src/main/java/com/diffplug/spotless/rome/Architecture.java @@ -0,0 +1,54 @@ +package com.diffplug.spotless.rome; + +/** + * Enumeration of possible computer architectures. + */ +public enum Architecture { + /** The arm64 architecture */ + ARM64, + /** Either x64 or x64_32 architecture */ + X64; + + /** + * Attempts to guess the architecture of the environment running the JVM. + * + * @return The best guess for the architecture. + */ + public static Architecture guess() { + var arch = System.getProperty("os.arch"); + var version = System.getProperty("os.version"); + + if (arch == null || arch.isBlank()) { + throw new IllegalStateException("No OS information is available, specify the Rome executable manually"); + } + + var msg = "Unsupported architecture " + arch + "/" + version + + ", specify the path to the Rome executable manually"; + + if (arch.equals("ppc64le")) { + throw new IllegalStateException(msg); + } + if (arch.equals("aarch64")) { + throw new IllegalStateException(msg); + } + if (arch.equals("s390x")) { + throw new IllegalStateException(msg); + } + if (arch.equals("ppc64")) { + throw new IllegalStateException(msg); + } + if (arch.equals("ppc")) { + throw new IllegalStateException(msg); + } + if (arch.equals("arm")) { + if (version.contains("v7")) { + throw new IllegalStateException(msg); + } + return ARM64; + } + if (arch.contains("64")) { + return X64; + } + throw new IllegalStateException(msg); + } +} diff --git a/lib/src/main/java/com/diffplug/spotless/rome/OS.java b/lib/src/main/java/com/diffplug/spotless/rome/OS.java new file mode 100644 index 0000000000..c7b874bb24 --- /dev/null +++ b/lib/src/main/java/com/diffplug/spotless/rome/OS.java @@ -0,0 +1,41 @@ +package com.diffplug.spotless.rome; + +import java.util.Locale; + +/** + * Enumeration of possible computer operation systems. + */ +enum OS { + /** Any derivate of a Linux operation system. */ + LINUX, + /** The Macintosh operating system/ */ + MAC_OS, + /** The Microsoft Windows operating system. */ + WINDOWS,; + + /** + * Attempts to guess the OS of the environment running the JVM. + * + * @return The best guess for the architecture. + * @throws IllegalStateException When the OS is either unsupported or no + * information about the OS could be retrieved. + */ + public static OS guess() { + var osName = System.getProperty("os.name"); + if (osName == null || osName.isBlank()) { + throw new IllegalStateException("No OS information is available, specify the Rome executable manually"); + } + var osNameUpper = osName.toUpperCase(Locale.ROOT); + if (osNameUpper.contains("SUNOS") || osName.contains("AIX")) { + throw new IllegalStateException( + "Unsupported OS " + osName + ", specify the path to the Rome executable manually"); + } + if (osNameUpper.contains("WINDOWS")) { + return OS.WINDOWS; + } else if (osNameUpper.contains("MAC")) { + return OS.MAC_OS; + } else { + return OS.LINUX; + } + } +} \ No newline at end of file diff --git a/lib/src/main/java/com/diffplug/spotless/Platform.java b/lib/src/main/java/com/diffplug/spotless/rome/Platform.java similarity index 76% rename from lib/src/main/java/com/diffplug/spotless/Platform.java rename to lib/src/main/java/com/diffplug/spotless/rome/Platform.java index c57932a7c6..2631cf3131 100644 --- a/lib/src/main/java/com/diffplug/spotless/Platform.java +++ b/lib/src/main/java/com/diffplug/spotless/rome/Platform.java @@ -1,15 +1,17 @@ -package com.diffplug.spotless; +package com.diffplug.spotless.rome; /** * Represents a platform where code is run, consisting of an operating system * and an architecture. */ -public class Platform { +class Platform { /** * Attempts to guess the platform of the hosting environment running the JVM * machine. * * @return The best guess for the current OS and architecture. + * @throws IllegalStateException When no OS information is available, or when + * the OS or architecture is unsupported. */ public static Platform guess() { var os = OS.guess(); @@ -23,7 +25,8 @@ public static Platform guess() { /** * Creates a new Platform descriptor for the given OS and architecture. - * @param os Operating system of the platform. + * + * @param os Operating system of the platform. * @param architecture Architecture of the platform. */ public Platform(OS os, Architecture architecture) { @@ -45,39 +48,25 @@ public OS getOs() { return os; } - /** - * @return Whether the operating system is Aix. - */ - public boolean isAix() { - return os == OS.AIX; - } - /** * @return Whether the operating system is Linux. */ public boolean isLinux() { - return os == OS.Linux; + return os == OS.LINUX; } /** * @return Whether the operating system is Mac. */ public boolean isMac() { - return os == OS.Mac; - } - - /** - * @return Whether the operating system is SunOS. - */ - public boolean isSunos() { - return os == OS.SunOS; + return os == OS.MAC_OS; } /** * @return Whether the operating system is Windows. */ public boolean isWindows() { - return os == OS.Windows; + return os == OS.WINDOWS; } @Override diff --git a/lib/src/main/java/com/diffplug/spotless/rome/RomeExecutableDownloader.java b/lib/src/main/java/com/diffplug/spotless/rome/RomeExecutableDownloader.java index dfddb4358a..72df92224f 100644 --- a/lib/src/main/java/com/diffplug/spotless/rome/RomeExecutableDownloader.java +++ b/lib/src/main/java/com/diffplug/spotless/rome/RomeExecutableDownloader.java @@ -15,21 +15,18 @@ import java.nio.file.StandardOpenOption; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; +import java.util.Locale; import java.util.Objects; import java.util.Optional; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import com.diffplug.spotless.Architecture; -import com.diffplug.spotless.OS; -import com.diffplug.spotless.Platform; - /** * Downloader for the Rome executable: * https://github.com/rome/tools. */ -public class RomeExecutableDownloader { +final class RomeExecutableDownloader { private static final Logger logger = LoggerFactory.getLogger(RomeExecutableDownloader.class); /** @@ -89,16 +86,19 @@ public RomeExecutableDownloader(Path downloadDir) { * the download directory. When the executable exists already, it is * overwritten. * - * @param version Desired Rome version. - * @param platform Desired platform. + * @param version Desired Rome version. * @return The path to the Rome executable. - * @throws IOException When the executable cannot be downloaded from - * the network or the file system could not be - * accessed. - * @throws InterruptedException When this thread was interrupted while - * downloading the file. + * @throws IOException When the executable cannot be downloaded from + * the network or the file system could not be + * accessed. + * @throws InterruptedException When this thread was interrupted while + * downloading the file. + * @throws IllegalStateException When no information about the current OS and + * architecture could be obtained, or when the OS + * or architecture is not supported. */ - public Path download(String version, Platform platform) throws IOException, InterruptedException { + public Path download(String version) throws IOException, InterruptedException { + var platform = Platform.guess(); var url = getDownloadUrl(version, platform); var executablePath = getExecutablePath(version, platform); var checksumPath = getChecksumPath(executablePath); @@ -128,21 +128,25 @@ public Path download(String version, Platform platform) throws IOException, Inte * * @param version Desired Rome version. * @return The path to the Rome executable. - * @throws IOException When the executable cannot be downloaded from - * the network or the file system could not be - * accessed. - * @throws InterruptedException When this thread was interrupted while - * downloading the file. + * @throws IOException When the executable cannot be downloaded from + * the network or the file system could not be + * accessed. + * @throws InterruptedException When this thread was interrupted while + * downloading the file. + * @throws IllegalStateException When no information about the current OS and + * architecture could be obtained, or when the OS + * or architecture is not supported. */ - public Path ensureDownloaded(String version, Platform platform) throws IOException, InterruptedException { + public Path ensureDownloaded(String version) throws IOException, InterruptedException { + var platform = Platform.guess(); logger.debug("Ensuring that Rome for platform '{}' is downloaded", platform); - var existing = findDownloaded(version, platform); + var existing = findDownloaded(version); if (existing.isPresent()) { logger.debug("Rome was already downloaded, using executable at '{}'", existing.get()); return existing.get(); } else { logger.debug("Rome was not yet downloaded, attempting to download executable"); - return download(version, platform); + return download(version); } } @@ -150,41 +154,22 @@ public Path ensureDownloaded(String version, Platform platform) throws IOExcepti * Attempts to find the Rome executable for the current platform in the download * directory. No attempt is made to download the executable from the network. * - * @param version Desired Rome version. - * @param platform Desired platform. + * @param version Desired Rome version. * @return The path to the Rome executable. - * @throws IOException When the executable does not exists in the download - * directory, or when the file system could not be accessed. + * @throws IOException When the executable does not exists in the + * download directory, or when the file system + * could not be accessed. + * @throws IllegalStateException When no information about the current OS and + * architecture could be obtained, or when the OS + * or architecture is not supported. */ - public Optional findDownloaded(String version, Platform platform) throws IOException { + public Optional findDownloaded(String version) throws IOException { + var platform = Platform.guess(); var executablePath = getExecutablePath(version, platform); logger.debug("Checking rome executable at {}", executablePath); return checkFileWithChecksum(executablePath) ? Optional.ofNullable(executablePath) : Optional.empty(); } - /** - * @param platform Platform to check. - * @return true if Rome officially supports the given platform, - * false otherwise. - */ - public boolean isSupported(Platform platform) { - var architecture = platform.getArchitecture(); - switch (platform.getOs()) { - case AIX: - return false; - case Linux: - return architecture == Architecture.x64 || architecture == Architecture.arm64; - case Mac: - return architecture == Architecture.x64 || architecture == Architecture.arm64; - case SunOS: - return false; - case Windows: - return architecture == Architecture.x64 || architecture == Architecture.arm64; - default: - return false; - } - } - /** * Checks whether the given file exists and matches the checksum. The checksum * must be contained in a file next to the file to check. @@ -254,22 +239,10 @@ private String computeChecksum(Path file, String algorithm) throws IOException { */ private String getArchitectureCodeName(Architecture architecture) throws IOException { switch (architecture) { - case arm64: + case ARM64: return "arm64"; - case armv7l: - throw new IOException("Unsupported architecture: " + architecture); - case ppc: - throw new IOException("Unsupported architecture: " + architecture); - case ppc64: - throw new IOException("Unsupported architecture: " + architecture); - case ppc64le: - throw new IOException("Unsupported architecture: " + architecture); - case s390x: - throw new IOException("Unsupported architecture: " + architecture); - case x64: + case X64: return "x64"; - case x86: - throw new IOException("Unsupported architecture: " + architecture); default: throw new IOException("Unsupported architecture: " + architecture); } @@ -294,9 +267,6 @@ private Path getChecksumPath(Path file) { * @throws IOException When the platform is not supported by Rome. */ private String getDownloadUrl(String version, Platform platform) throws IOException { - if (!isSupported(platform)) { - throw new IOException("Unsupported platform: " + platform); - } var osCodeName = getOsCodeName(platform.getOs()); var architectureCodeName = getArchitectureCodeName(platform.getArchitecture()); var extension = getDownloadUrlExtension(platform.getOs()); @@ -314,15 +284,11 @@ private String getDownloadUrl(String version, Platform platform) throws IOExcept */ private String getDownloadUrlExtension(OS os) throws IOException { switch (os) { - case AIX: - throw new IOException("Unsupported OS: " + os); - case Linux: + case LINUX: return ""; - case Mac: + case MAC_OS: return ""; - case SunOS: - throw new IOException("Unsupported OS: " + os); - case Windows: + case WINDOWS: return ".exe"; default: throw new IOException("Unsupported OS: " + os); @@ -338,7 +304,9 @@ private String getDownloadUrlExtension(OS os) throws IOException { * @return The path for the Rome executable. */ private Path getExecutablePath(String version, Platform platform) { - var fileName = String.format(DOWNLOAD_FILE_PATTERN, platform.getOs(), platform.getArchitecture(), version); + var os = platform.getOs().name().toLowerCase(Locale.ROOT); + var arch = platform.getArchitecture().name().toLowerCase(Locale.ROOT); + var fileName = String.format(DOWNLOAD_FILE_PATTERN, os, arch, version); return downloadDir.resolve(fileName); } @@ -352,15 +320,11 @@ private Path getExecutablePath(String version, Platform platform) { */ private String getOsCodeName(OS os) throws IOException { switch (os) { - case AIX: - throw new IOException("Unsupported OS: " + os); - case Linux: + case LINUX: return "linux"; - case Mac: + case MAC_OS: return "darwin"; - case SunOS: - throw new IOException("Unsupported OS: " + os); - case Windows: + case WINDOWS: return "win32"; default: throw new IOException("Unsupported OS: " + os); diff --git a/lib/src/main/java/com/diffplug/spotless/javascript/RomeStep.java b/lib/src/main/java/com/diffplug/spotless/rome/RomeStep.java similarity index 82% rename from lib/src/main/java/com/diffplug/spotless/javascript/RomeStep.java rename to lib/src/main/java/com/diffplug/spotless/rome/RomeStep.java index 2794b38f44..1d67743f41 100644 --- a/lib/src/main/java/com/diffplug/spotless/javascript/RomeStep.java +++ b/lib/src/main/java/com/diffplug/spotless/rome/RomeStep.java @@ -1,4 +1,4 @@ -package com.diffplug.spotless.javascript; +package com.diffplug.spotless.rome; import java.io.File; import java.io.IOException; @@ -20,9 +20,7 @@ import com.diffplug.spotless.ForeignExe; import com.diffplug.spotless.FormatterFunc; import com.diffplug.spotless.FormatterStep; -import com.diffplug.spotless.Platform; import com.diffplug.spotless.ProcessRunner; -import com.diffplug.spotless.rome.RomeExecutableDownloader; /** * formatter step that formats JavaScript and TypeScript code with Rome: @@ -33,12 +31,29 @@ public class RomeStep { private static final Logger logger = LoggerFactory.getLogger(RomeStep.class); + /** + * Path to the directory with the {@code rome.json} config file, can be + * null, in which case the defaults are used. + */ private final String configPath; + /** + * Path to the Rome executable. Can be null, but either a path to + * the executable of a download directory and version must be given. + */ private final String pathToExe; + /** + * Path to the download directory for storing the download Rome executable. Can + * be null, but either a path to the executable of a download + * directory and version must be given. + */ private final String pathToExeDownloadDir; + /** + * Version of Rome to download. Can be null, but either a path to + * the executable of a download directory and version must be given. + */ private final String version; /** @@ -137,8 +152,8 @@ private static String resolveNameAgainstPath(String name) throws IOException, In } /** - * 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. + * Checks the Rome 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) { @@ -154,6 +169,10 @@ private static void validateRomeConfigPath(String configPath) { } } + /** + * Checks the Rome executable file. When the file does not exist, an error is + * thrown. + */ private static void validateRomeExecutable(String resolvedPathToExe) { if (!new File(resolvedPathToExe).isFile()) { throw new IllegalArgumentException("Rome executable does not exist: " + resolvedPathToExe); @@ -243,19 +262,24 @@ private String resolveExe() throws IOException, InterruptedException { } } else { var downloader = new RomeExecutableDownloader(Paths.get(pathToExeDownloadDir)); - var platform = Platform.guess(); - if (!downloader.isSupported(platform)) { - throw new IllegalStateException( - "Unsupported platform " + platform + ", please specifiy the Rome executable directly"); - } - var downloaded = downloader.ensureDownloaded(version, platform).toString(); + var downloaded = downloader.ensureDownloaded(version).toString(); makeExecutable(downloaded); return downloaded; } } + /** + * The internal state used by the Rome formatter. A state instance is created + * when the spotless plugin for Maven or Gradle is executed, and reused for all + * formatting requests for different files. The lifetime of the instance ends + * when the Maven or Gradle plugin was successfully executed. + *

+ * The state encapsulated a particular executable. It is serializable for + * caching purposes. Spotless keeps a cache of which files need to be formatted. + * The cache is busted when the serialized form of a state instance changes. + */ private static class State implements Serializable { - private static final long serialVersionUID = -5884229077231467806L; + private static final long serialVersionUID = 6846790911054484379L; /** Path to the exe file */ private final String pathToExe; @@ -264,10 +288,22 @@ private static class State implements Serializable { @SuppressWarnings("unused") private final FileSignature exeSignature; - /** The optional configuration file for Rome. */ + /** + * The optional path to the directory with the {@code rome.json} config file. + */ private final String configPath; - private State(String exe, FileSignature exeSignature, String configPath) throws IOException { + /** + * Creates a new state for instance which can format code with the given Rome + * executable. + * + * @param exe Path to the Rome executable. + * @param exeSignature Signature (e.g. SHA-256 checksum) of the Rome executable. + * @param configPath Path to the optional directory with the {@code rome.json} + * config file, can be null, in which case the + * defaults are used. + */ + private State(String exe, FileSignature exeSignature, String configPath) { this.pathToExe = exe; this.exeSignature = exeSignature; this.configPath = configPath; @@ -316,6 +352,12 @@ private String format(ProcessRunner runner, String input, File file) throws IOEx return runner.exec(stdin, args).assertExitZero(StandardCharsets.UTF_8); } + /** + * Creates a new formatter function for formatting a piece of code by delegating + * to the Rome executable. + * + * @return A formatter function for formatting code. + */ private FormatterFunc.Closeable toFunc() { var runner = new ProcessRunner(); return FormatterFunc.Closeable.of(runner, this::format); diff --git a/plugin-gradle/CHANGES.md b/plugin-gradle/CHANGES.md index b9f692bd9a..4fb0d57e14 100644 --- a/plugin-gradle/CHANGES.md +++ b/plugin-gradle/CHANGES.md @@ -19,6 +19,7 @@ We adhere to the [keepachangelog](https://keepachangelog.com/en/1.0.0/) format ( Mirrors are selected by prefix match, for example `https://download.eclipse.org/eclipse/updates/4.26/` will be redirected to `https://some.internal.mirror/eclipse/eclipse/updates/4.26/`. The same configuration exists for `greclipse` and `eclipseCdt`. * The `style` option in Palantir Java Format ([#1654](https://github.com/diffplug/spotless/pull/1654)). +* Support Rome as a formatter for JavaScript and TypeScript code. Adds a new `rome` step to `javascript` and `typescript` formatter configurations. https://github.com/diffplug/spotless/pull/1663 ### Fixed * Stop using deprecated conventions when used in Gradle >= `7.1`. ([#1618](https://github.com/diffplug/spotless/pull/1618)) ### Changes @@ -27,7 +28,7 @@ We adhere to the [keepachangelog](https://keepachangelog.com/en/1.0.0/) format ( * Bump default `diktat` version `1.2.4.2` -> `1.2.5`. ([#1631](https://github.com/diffplug/spotless/pull/1631)) * Bump default `flexmark` version `0.62.2` -> `0.64.0`. ([#1302](https://github.com/diffplug/spotless/pull/1302)) * Bump default `googleJavaFormat` version `1.15.0` -> `1.16.0`. ([#1630](https://github.com/diffplug/spotless/pull/1630)) -* Support Rome as a formatter for JavaScript and TypeScript code. Adds a new `rome` step to `javascript` and `typescript` formatter configurations. https://github.com/diffplug/spotless/pull/1663 + * Bump default `scalafmt` version `3.7.1` -> `3.7.3`. ([#1584](https://github.com/diffplug/spotless/pull/1584)) * Bump default Eclipse formatters for the 2023-03 release. ([#1662](https://github.com/diffplug/spotless/pull/1662)) * JDT and GrEclipse `4.26` -> `4.27` diff --git a/plugin-maven/CHANGES.md b/plugin-maven/CHANGES.md index 46c82361b5..f0134bc2f2 100644 --- a/plugin-maven/CHANGES.md +++ b/plugin-maven/CHANGES.md @@ -9,7 +9,7 @@ We adhere to the [keepachangelog](https://keepachangelog.com/en/1.0.0/) format ( * `removeUnusedImport` can be configured to rely on `cleanthat-javaparser-unnecessaryimport`. Default remains `google-java-format`. ([#1589](https://github.com/diffplug/spotless/pull/1589)) * The `style` option in Palantir Java Format ([#1654](https://github.com/diffplug/spotless/pull/1654)). * Added formatter for Gherkin feature files ([#1649](https://github.com/diffplug/spotless/issues/1649)). -### Fixed +* Support Rome as a formatter for JavaScript and TypeScript code. Adds a new `rome` step to `javascript` and `typescript` formatter configurations. https://github.com/diffplug/spotless/pull/1663### Fixed * Fix non deterministic computation of cache fingerprint when using multiple formatters. ([#1643](https://github.com/diffplug/spotless/pull/1643) fixes [#1642](https://github.com/diffplug/spotless/pull/1642)) ### Changes * **POTENTIALLY BREAKING** Drop support for `googleJavaFormat` versions < `1.8`. ([#1630](https://github.com/diffplug/spotless/pull/1630)) @@ -17,7 +17,7 @@ We adhere to the [keepachangelog](https://keepachangelog.com/en/1.0.0/) format ( * Bump default `diktat` version `1.2.4.2` -> `1.2.5`. ([#1631](https://github.com/diffplug/spotless/pull/1631)) * Bump default `flexmark` version `0.62.2` -> `0.64.0`. ([#1302](https://github.com/diffplug/spotless/pull/1302)) * Bump default `googleJavaFormat` version `1.15.0` -> `1.16.0`. ([#1630](https://github.com/diffplug/spotless/pull/1630)) -* Support Rome as a formatter for JavaScript and TypeScript code. Adds a new `rome` step to `javascript` and `typescript` formatter configurations. https://github.com/diffplug/spotless/pull/1663 + * Bump default `scalafmt` version `3.7.1` -> `3.7.3`. ([#1584](https://github.com/diffplug/spotless/pull/1584)) * Bump default Eclipse formatters for the 2023-03 release. ([#1662](https://github.com/diffplug/spotless/pull/1662)) * JDT and GrEclipse `4.26` -> `4.27` diff --git a/plugin-maven/src/main/java/com/diffplug/spotless/maven/FileLocator.java b/plugin-maven/src/main/java/com/diffplug/spotless/maven/FileLocator.java index 1450b2873a..3e6d4cfeae 100644 --- a/plugin-maven/src/main/java/com/diffplug/spotless/maven/FileLocator.java +++ b/plugin-maven/src/main/java/com/diffplug/spotless/maven/FileLocator.java @@ -121,9 +121,7 @@ private static byte[] hash(String value) { } private static File findDataDir() { - // One of - // ~/.m2/repository/com/diffplug/spotless/spotless-plugin-maven/1.2.3/spotless-plugin-maven-1.2.3.jar - // ~/.m2/repository/com/diffplug/spotless/spotless-plugin-maven/1.2.3/spotless-plugin-gradle-1.2.3.jar + // E.g. ~/.m2/repository/com/diffplug/spotless/spotless-plugin-maven/1.2.3/spotless-plugin-maven-1.2.3.jar final var jarPath = Paths.get(RomeJs.class.getProtectionDomain().getCodeSource().getLocation().getPath()); final var base = jarPath.getParent().getParent().getParent(); final var sub = base.resolve("spotless-data"); diff --git a/plugin-maven/src/main/java/com/diffplug/spotless/maven/javascript/RomeJs.java b/plugin-maven/src/main/java/com/diffplug/spotless/maven/javascript/RomeJs.java index 180fb9274f..4a4a57c646 100644 --- a/plugin-maven/src/main/java/com/diffplug/spotless/maven/javascript/RomeJs.java +++ b/plugin-maven/src/main/java/com/diffplug/spotless/maven/javascript/RomeJs.java @@ -5,9 +5,9 @@ import org.apache.maven.plugins.annotations.Parameter; import com.diffplug.spotless.FormatterStep; -import com.diffplug.spotless.javascript.RomeStep; import com.diffplug.spotless.maven.FormatterStepConfig; import com.diffplug.spotless.maven.FormatterStepFactory; +import com.diffplug.spotless.rome.RomeStep; /** * Factory for creating the Rome formatter step that formats JavaScript and From cdb0ec62b99845b360281ae0d9284f1cbb072105 Mon Sep 17 00:00:00 2001 From: Andre Wachsmuth Date: Sat, 8 Apr 2023 10:13:00 +0100 Subject: [PATCH 07/33] Fix changes.md + Architecture visibility --- CHANGES.md | 3 ++- .../main/java/com/diffplug/spotless/rome/Architecture.java | 2 +- plugin-gradle/CHANGES.md | 4 +++- plugin-maven/CHANGES.md | 3 ++- 4 files changed, 8 insertions(+), 4 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 57b831b8ad..a5d783b64b 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -11,12 +11,13 @@ We adhere to the [keepachangelog](https://keepachangelog.com/en/1.0.0/) format ( ## [Unreleased] +* * Support Rome as a formatter for JavaScript and TypeScript code. Adds a new `rome` step to `javascript` and `typescript` formatter configurations. https://github.com/diffplug/spotless/pull/1663 + ## [2.38.0] - 2023-04-06 ### Added * Support configuration of mirrors for P2 repositories in `EquoBasedStepBuilder` ([#1629](https://github.com/diffplug/spotless/issues/1629)). * The `style` option in Palantir Java Format ([#1654](https://github.com/diffplug/spotless/pull/1654)). * Added formatter for Gherkin feature files ([#1649](https://github.com/diffplug/spotless/issues/1649)). -* Support Rome as a formatter for JavaScript and TypeScript code. Adds a new `rome` step to `javascript` and `typescript` formatter configurations. https://github.com/diffplug/spotless/pull/1663 ### Changes * **POTENTIALLY BREAKING** Converted `googleJavaFormat` to a compile-only dependency and drop support for versions < `1.8`. ([#1630](https://github.com/diffplug/spotless/pull/1630)) * Bump default `cleanthat` version to latest `2.6` -> `2.13`. ([#1589](https://github.com/diffplug/spotless/pull/1589) and [#1661](https://github.com/diffplug/spotless/pull/1661)) diff --git a/lib/src/main/java/com/diffplug/spotless/rome/Architecture.java b/lib/src/main/java/com/diffplug/spotless/rome/Architecture.java index 064bd52b32..1207f2ddc7 100644 --- a/lib/src/main/java/com/diffplug/spotless/rome/Architecture.java +++ b/lib/src/main/java/com/diffplug/spotless/rome/Architecture.java @@ -3,7 +3,7 @@ /** * Enumeration of possible computer architectures. */ -public enum Architecture { +enum Architecture { /** The arm64 architecture */ ARM64, /** Either x64 or x64_32 architecture */ diff --git a/plugin-gradle/CHANGES.md b/plugin-gradle/CHANGES.md index 4fb0d57e14..9fa9415dd9 100644 --- a/plugin-gradle/CHANGES.md +++ b/plugin-gradle/CHANGES.md @@ -4,6 +4,8 @@ We adhere to the [keepachangelog](https://keepachangelog.com/en/1.0.0/) format ( ## [Unreleased] +* Support Rome as a formatter for JavaScript and TypeScript code. Adds a new `rome` step to `javascript` and `typescript` formatter configurations. https://github.com/diffplug/spotless/pull/1663 + ## [6.18.0] - 2023-04-06 ### Added * `removeUnusedImport` can be configured to rely on `cleanthat-javaparser-unnecessaryimport`. Default remains `google-java-format`. ([#1589](https://github.com/diffplug/spotless/pull/1589)) @@ -19,7 +21,7 @@ We adhere to the [keepachangelog](https://keepachangelog.com/en/1.0.0/) format ( Mirrors are selected by prefix match, for example `https://download.eclipse.org/eclipse/updates/4.26/` will be redirected to `https://some.internal.mirror/eclipse/eclipse/updates/4.26/`. The same configuration exists for `greclipse` and `eclipseCdt`. * The `style` option in Palantir Java Format ([#1654](https://github.com/diffplug/spotless/pull/1654)). -* Support Rome as a formatter for JavaScript and TypeScript code. Adds a new `rome` step to `javascript` and `typescript` formatter configurations. https://github.com/diffplug/spotless/pull/1663 + ### Fixed * Stop using deprecated conventions when used in Gradle >= `7.1`. ([#1618](https://github.com/diffplug/spotless/pull/1618)) ### Changes diff --git a/plugin-maven/CHANGES.md b/plugin-maven/CHANGES.md index f0134bc2f2..7d79f14784 100644 --- a/plugin-maven/CHANGES.md +++ b/plugin-maven/CHANGES.md @@ -3,13 +3,14 @@ We adhere to the [keepachangelog](https://keepachangelog.com/en/1.0.0/) format (starting after version `1.27.0`). ## [Unreleased] +* Support Rome as a formatter for JavaScript and TypeScript code. Adds a new `rome` step to `javascript` and `typescript` formatter configurations. https://github.com/diffplug/spotless/pull/1663### Fixed ## [2.36.0] - 2023-04-06 ### Added * `removeUnusedImport` can be configured to rely on `cleanthat-javaparser-unnecessaryimport`. Default remains `google-java-format`. ([#1589](https://github.com/diffplug/spotless/pull/1589)) * The `style` option in Palantir Java Format ([#1654](https://github.com/diffplug/spotless/pull/1654)). * Added formatter for Gherkin feature files ([#1649](https://github.com/diffplug/spotless/issues/1649)). -* Support Rome as a formatter for JavaScript and TypeScript code. Adds a new `rome` step to `javascript` and `typescript` formatter configurations. https://github.com/diffplug/spotless/pull/1663### Fixed + * Fix non deterministic computation of cache fingerprint when using multiple formatters. ([#1643](https://github.com/diffplug/spotless/pull/1643) fixes [#1642](https://github.com/diffplug/spotless/pull/1642)) ### Changes * **POTENTIALLY BREAKING** Drop support for `googleJavaFormat` versions < `1.8`. ([#1630](https://github.com/diffplug/spotless/pull/1630)) From a15849312327fdb7947be704b227628041f2eb15 Mon Sep 17 00:00:00 2001 From: Andre Wachsmuth Date: Sat, 8 Apr 2023 13:54:12 +0100 Subject: [PATCH 08/33] Support formatting JSON files with Rome --- README.md | 1 + .../com/diffplug/spotless/rome/RomeStep.java | 207 +++++++++++++++--- .../diffplug/spotless/maven/FileLocator.java | 4 +- .../spotless/maven/generic/Format.java | 4 + .../diffplug/spotless/maven/generic/Rome.java | 34 +++ .../spotless/maven/javascript/RomeJs.java | 132 +---------- .../diffplug/spotless/maven/json/Json.java | 3 + .../spotless/maven/json/RomeJson.java | 13 ++ .../spotless/maven/rome/AbstractRome.java | 173 +++++++++++++++ .../spotless/maven/typescript/RomeTs.java | 13 ++ .../spotless/maven/typescript/Typescript.java | 3 +- 11 files changed, 422 insertions(+), 165 deletions(-) create mode 100644 plugin-maven/src/main/java/com/diffplug/spotless/maven/generic/Rome.java create mode 100644 plugin-maven/src/main/java/com/diffplug/spotless/maven/json/RomeJson.java create mode 100644 plugin-maven/src/main/java/com/diffplug/spotless/maven/rome/AbstractRome.java create mode 100644 plugin-maven/src/main/java/com/diffplug/spotless/maven/typescript/RomeTs.java diff --git a/README.md b/README.md index 52628574d9..001b00b5e2 100644 --- a/README.md +++ b/README.md @@ -99,6 +99,7 @@ lib('npm.PrettierFormatterStep') +'{{yes}} | {{yes}} lib('npm.TsFmtFormatterStep') +'{{yes}} | {{yes}} | {{no}} | {{no}} |', lib('pom.SortPomStep') +'{{no}} | {{yes}} | {{no}} | {{no}} |', lib('python.BlackStep') +'{{yes}} | {{no}} | {{no}} | {{no}} |', +lib('rome.RomeStep') +'{{yes}} | {{yes}} | {{no}} | {{no}} |', lib('scala.ScalaFmtStep') +'{{yes}} | {{yes}} | {{yes}} | {{no}} |', lib('sql.DBeaverSQLFormatterStep') +'{{yes}} | {{yes}} | {{yes}} | {{no}} |', extra('wtp.EclipseWtpFormatterStep') +'{{yes}} | {{yes}} | {{no}} | {{no}} |', diff --git a/lib/src/main/java/com/diffplug/spotless/rome/RomeStep.java b/lib/src/main/java/com/diffplug/spotless/rome/RomeStep.java index 1d67743f41..820d1af66e 100644 --- a/lib/src/main/java/com/diffplug/spotless/rome/RomeStep.java +++ b/lib/src/main/java/com/diffplug/spotless/rome/RomeStep.java @@ -37,18 +37,38 @@ public class RomeStep { */ private final String configPath; + /** + * The language (syntax) of the input files to format. When null or + * the empty string, the language is detected automatically from the file name. + * Currently the following languages are supported by Rome: + *

    + *
  • js (JavaScript)
  • + *
  • jsx (JavaScript + JSX)
  • + *
  • js? (JavaScript or JavaScript + JSX, depending on the file + * extension)
  • + *
  • ts (TypeScript)
  • + *
  • tsx (TypeScript + JSX)
  • + *
  • ts? (TypeScript or TypeScript + JSX, depending on the file + * extension)
  • + *
  • json (JSON)
  • + *
+ */ + private final String language; + /** * Path to the Rome executable. Can be null, but either a path to - * the executable of a download directory and version must be given. + * the executable of a download directory and version must be given. The path + * must be either an absolute path, or a file name without path separators. If + * the latter, it is interpreted as a command in the user's path. */ private final String pathToExe; /** - * Path to the download directory for storing the download Rome executable. Can - * be null, but either a path to the executable of a download - * directory and version must be given. + * Absolute path to the download directory for storing the download Rome + * executable. Can be null, but either a path to the executable of + * a download directory and version must be given. */ - private final String pathToExeDownloadDir; + private final String downloadDir; /** * Version of Rome to download. Can be null, but either a path to @@ -71,9 +91,8 @@ public static String name() { * @param downloadDir Directory where to place the downloaded executable. * @return A new Rome step that download the executable from the network. */ - public static RomeStep withExeDownload(String version, String downloadDir) { - version = version != null && !version.isBlank() ? version : defaultVersion(); - return new RomeStep(version, null, downloadDir, null); + public static RomeStep.Builder withExeDownload(String version, String downloadDir) { + return new RomeStep.Builder(version, null, downloadDir); } /** @@ -83,8 +102,8 @@ public static RomeStep withExeDownload(String version, String downloadDir) { * @param pathToExe Path to the Rome executable to use. * @return A new Rome step that format with the given executable. */ - public static RomeStep withExePath(String pathToExe) { - return new RomeStep(null, pathToExe, null, null); + public static RomeStep.Builder withExePath(String pathToExe) { + return new RomeStep.Builder(null, pathToExe, null); } /** @@ -180,14 +199,16 @@ private static void validateRomeExecutable(String resolvedPathToExe) { } /** - * Checks the Rome executable. When the executable path does not exist, an error - * is thrown. + * Creates a new Rome step with the configuration from the given builder. + * + * @param builder Builder with the configuration to use. */ - private RomeStep(String version, String pathToExe, String pathToExeDownloadDir, String configPath) { - this.version = version; - this.pathToExe = pathToExe; - this.pathToExeDownloadDir = pathToExeDownloadDir; - this.configPath = configPath; + private RomeStep(RomeStep.Builder builder) { + this.version = builder.version != null && !builder.version.isBlank() ? builder.version : defaultVersion(); + this.pathToExe = builder.pathToExe; + this.downloadDir = builder.downloadDir; + this.configPath = builder.configPath; + this.language = builder.language; } /** @@ -200,19 +221,6 @@ 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 @@ -234,7 +242,7 @@ private State createState() throws IOException, InterruptedException { logger.debug("Using Rome executable located at '{}'", resolvedPathToExe); var exeSignature = FileSignature.signAsList(Collections.singleton(new File(resolvedPathToExe))); makeExecutable(resolvedPathToExe); - return new State(resolvedPathToExe, exeSignature, configPath); + return new State(resolvedPathToExe, exeSignature, configPath, language); } /** @@ -261,13 +269,95 @@ private String resolveExe() throws IOException, InterruptedException { return pathToExe; } } else { - var downloader = new RomeExecutableDownloader(Paths.get(pathToExeDownloadDir)); + var downloader = new RomeExecutableDownloader(Paths.get(downloadDir)); var downloaded = downloader.ensureDownloaded(version).toString(); makeExecutable(downloaded); return downloaded; } } + public final static class Builder { + /** See {@link RomeStep#configPath} */ + private String configPath; + + /** See {@link RomeStep#downloadDir} */ + private final String downloadDir; + + /** See {@link RomeStep#language} */ + private String language; + + /** See {@link RomeStep#pathToExe} */ + private final String pathToExe; + + /** See {@link RomeStep#version} */ + private final String version; + + /** + * Creates a new builder for configuring a Rome step that can format code via + * Rome. Either a version and and downloadDir, or a pathToExe must be given. + * + * @param version The version of Rome to download, see + * {@link RomeStep#version}. + * @param pathToExe The path to the Rome executable to use, see + * {@link RomeStep#pathToExe}. + * @param downloadDir The path to the download directory when downloading Rome + * from the network, {@link RomeStep#downloadDir}. + */ + private Builder(String version, String pathToExe, String downloadDir) { + this.version = version; + this.pathToExe = pathToExe; + this.downloadDir = downloadDir; + } + + /** + * Creates a new Rome step for formatting code with Rome from the current + * configuration of this builder. + * + * @return A new Rome step with the current configuration. + */ + public RomeStep build() { + return new RomeStep(this); + } + + /** + * Sets the path to the directory with the {@code rome.json} config file. When + * no config path is set, the default configuration is used. + * + * @param configPath Config path to use. Must point to a directory which contain + * a file named {@code rome.json}. + * @return This builder instance for chaining method calls. + */ + public Builder withConfigPath(String configPath) { + this.configPath = configPath; + return this; + } + + /** + * Sets the language of the files to format When no language is set, it is + * determined automatically from the file name. The following languages are + * currently supported by Rome. + * + *
    + *
  • js (JavaScript)
  • + *
  • jsx (JavaScript + JSX)
  • + *
  • js? (JavaScript or JavaScript + JSX, depending on the file + * extension)
  • + *
  • ts (TypeScript)
  • + *
  • tsx (TypeScript + JSX)
  • + *
  • ts? (TypeScript or TypeScript + JSX, depending on the file + * extension)
  • + *
  • json (JSON)
  • + *
+ * + * @param language The language of the files to format. + * @return This builder instance for chaining method calls. + */ + public Builder withLanguage(String language) { + this.language = language; + return this; + } + } + /** * The internal state used by the Rome formatter. A state instance is created * when the spotless plugin for Maven or Gradle is executed, and reused for all @@ -293,6 +383,12 @@ private static class State implements Serializable { */ private final String configPath; + /** + * The language of the files to format. When null or the empty + * string, the language is detected from the file name. + */ + private final String language; + /** * Creates a new state for instance which can format code with the given Rome * executable. @@ -303,10 +399,11 @@ private static class State implements Serializable { * config file, can be null, in which case the * defaults are used. */ - private State(String exe, FileSignature exeSignature, String configPath) { + private State(String exe, FileSignature exeSignature, String configPath, String language) { this.pathToExe = exe; this.exeSignature = exeSignature; this.configPath = configPath; + this.language = language; } /** @@ -317,11 +414,12 @@ private State(String exe, FileSignature exeSignature, String configPath) { * @return The Rome command to use for formatting code. */ private String[] buildRomeCommand(File file) { + var fileName = resolveFileName(file); var argList = new ArrayList(); argList.add(pathToExe); argList.add("format"); argList.add("--stdin-file-path"); - argList.add(file.getName()); + argList.add(fileName); if (configPath != null) { argList.add("--config-path"); argList.add(configPath); @@ -352,6 +450,47 @@ private String format(ProcessRunner runner, String input, File file) throws IOEx return runner.exec(stdin, args).assertExitZero(StandardCharsets.UTF_8); } + /** + * The Rome executable currently does not have a parameter to specify the + * expected language / syntax. Rome always determined the language from the file + * extension. This method returns the file name for the desired language when a + * language was requested explicitly, or the file name of the input file for + * auto detection. + * + * @param file File to be formatted. + * @return The file name to pass to the Rome executable. + */ + private String resolveFileName(File file) { + var name = file.getName(); + if (language == null || language.isBlank()) { + return name; + } + var dot = name.lastIndexOf("."); + var ext = dot >= 0 ? name.substring(dot + 1) : name; + switch (language) { + case "js?": + return "jsx".equals(ext) || "js".equals(ext) || "mjs".equals(ext) || "cjs".equals(ext) ? name + : "file.js"; + case "ts?": + return "tsx".equals(ext) || "ts".equals(ext) || "tjs".equals(ext) || "tjs".equals(ext) ? name + : "file.js"; + case "js": + return "js".equals(ext) || "mjs".equals(ext) || "cjs".equals(ext) ? name : "file.js"; + case "jsx": + return "jsx".equals(ext) ? name : "file.jsx"; + case "ts": + return "ts".equals(ext) || "mts".equals(ext) || "cts".equals(ext) ? name : "file.ts"; + case "tsx": + return "tsx".equals(ext) ? name : "file.tsx"; + case "json": + return "json".equals(ext) ? name : "file.json"; + // so that we can support new languages such as css or yaml when Rome adds + // support for them without having to change the code + default: + return "file." + language; + } + } + /** * Creates a new formatter function for formatting a piece of code by delegating * to the Rome executable. diff --git a/plugin-maven/src/main/java/com/diffplug/spotless/maven/FileLocator.java b/plugin-maven/src/main/java/com/diffplug/spotless/maven/FileLocator.java index 3e6d4cfeae..7def4423f8 100644 --- a/plugin-maven/src/main/java/com/diffplug/spotless/maven/FileLocator.java +++ b/plugin-maven/src/main/java/com/diffplug/spotless/maven/FileLocator.java @@ -30,7 +30,7 @@ import org.codehaus.plexus.resource.loader.ResourceNotFoundException; import org.codehaus.plexus.util.FileUtils; -import com.diffplug.spotless.maven.javascript.RomeJs; +import com.diffplug.spotless.maven.rome.AbstractRome; public class FileLocator { @@ -122,7 +122,7 @@ private static byte[] hash(String value) { private static File findDataDir() { // E.g. ~/.m2/repository/com/diffplug/spotless/spotless-plugin-maven/1.2.3/spotless-plugin-maven-1.2.3.jar - final var jarPath = Paths.get(RomeJs.class.getProtectionDomain().getCodeSource().getLocation().getPath()); + final var jarPath = Paths.get(AbstractRome.class.getProtectionDomain().getCodeSource().getLocation().getPath()); final var base = jarPath.getParent().getParent().getParent(); final var sub = base.resolve("spotless-data"); return sub.toAbsolutePath().toFile(); diff --git a/plugin-maven/src/main/java/com/diffplug/spotless/maven/generic/Format.java b/plugin-maven/src/main/java/com/diffplug/spotless/maven/generic/Format.java index a696e13ffb..6cf843255a 100644 --- a/plugin-maven/src/main/java/com/diffplug/spotless/maven/generic/Format.java +++ b/plugin-maven/src/main/java/com/diffplug/spotless/maven/generic/Format.java @@ -40,4 +40,8 @@ public String licenseHeaderDelimiter() { // do not specify a default delimiter return null; } + + public void addRome(Rome rome) { + addStepFactory(rome); + } } diff --git a/plugin-maven/src/main/java/com/diffplug/spotless/maven/generic/Rome.java b/plugin-maven/src/main/java/com/diffplug/spotless/maven/generic/Rome.java new file mode 100644 index 0000000000..b3d645f9e6 --- /dev/null +++ b/plugin-maven/src/main/java/com/diffplug/spotless/maven/generic/Rome.java @@ -0,0 +1,34 @@ +package com.diffplug.spotless.maven.generic; + +import org.apache.maven.plugins.annotations.Parameter; + +import com.diffplug.spotless.maven.rome.AbstractRome; + +/** + * Generic Rome formatter step that detects the language of the input file from + * the file name. It should be specified as a formatter step for a generic + * {@code }. + */ +public class Rome extends AbstractRome { + /** + * Gets the language (syntax) of the input files to format. When + * null or the empty string, the language is detected automatically + * from the file name. Currently the following languages are supported by Rome: + *
    + *
  • js (JavaScript)
  • + *
  • jsx (JavaScript + JSX)
  • + *
  • ts (TypeScript)
  • + *
  • tsx (TypeScript + JSX)
  • + *
  • json (JSON)
  • + *
+ * + * @return The language of the input files. + */ + @Parameter + private String language; + + @Override + protected String getLanguage() { + return language; + } +} diff --git a/plugin-maven/src/main/java/com/diffplug/spotless/maven/javascript/RomeJs.java b/plugin-maven/src/main/java/com/diffplug/spotless/maven/javascript/RomeJs.java index 4a4a57c646..11467a2820 100644 --- a/plugin-maven/src/main/java/com/diffplug/spotless/maven/javascript/RomeJs.java +++ b/plugin-maven/src/main/java/com/diffplug/spotless/maven/javascript/RomeJs.java @@ -1,135 +1,13 @@ package com.diffplug.spotless.maven.javascript; -import java.nio.file.Paths; - -import org.apache.maven.plugins.annotations.Parameter; - -import com.diffplug.spotless.FormatterStep; -import com.diffplug.spotless.maven.FormatterStepConfig; -import com.diffplug.spotless.maven.FormatterStepFactory; -import com.diffplug.spotless.rome.RomeStep; +import com.diffplug.spotless.maven.rome.AbstractRome; /** - * Factory for creating the Rome formatter step that formats JavaScript and - * TypeScript code with Rome: - * https://github.com/rome/tools. - * It delegates to the Rome executable. + * Rome formatter step for JavaScript. */ -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. - * Defaults to - * ~/.m2/repository/com/diffplug/spotless/spotless-data/rome. - *

- * You can use an expression like ${user.home}/rome if you want to - * use the home directory, or ${project.build.directory if you want - * to use the target directory of the current project. - */ - @Parameter - private String downloadDir; - - /** - * Optional path to the Rome executable. Either a version or a - * pathToExe should be specified. When not given, an attempt is - * made to download the executable for the given version from the network. When - * given, the executable is used and the version parameter is - * ignored. - *

- * When an absolute path is given, that path is used as-is. When a relative path - * is given, it is resolved against the project's base directory. When only a - * file name (i.e. without any slashes or back slash path separators such as - * {@code rome}) is given, this is interpreted as the name of a command with - * executable that is in your {@code path} environment variable. Use - * {@code ./executable-name} if you want to use an executable in the project's - * base directory. - */ - @Parameter - private String pathToExe; - - /** - * Rome version to download, applies only when no pathToExe is - * specified explicitly. Either a version or a - * pathToExe should be specified. When not given, a default known - * version is used. For stable builds, it is recommended that you always set the - * version explicitly. This parameter is ignored when you specify a - * pathToExe explicitly. - */ - @Parameter - private String version; - +public class RomeJs extends AbstractRome { @Override - public FormatterStep newFormatterStep(FormatterStepConfig config) { - RomeStep rome; - if (pathToExe != null) { - var resolvedExePath = resolveExePath(config); - rome = RomeStep.withExePath(resolvedExePath); - } else { - 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 - * the user's path. Otherwise resolve the executable path 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 Rome executable. - */ - private String resolveExePath(FormatterStepConfig config) { - var path = Paths.get(pathToExe); - if (path.getNameCount() == 1) { - return path.toString(); - } else { - return config.getFileLocator().getBaseDir().toPath().resolve(path).toAbsolutePath().toString(); - } - } - - /** - * Resolves the directory to use for storing downloaded Rome executable. When a - * {@link #downloadDir} is given, use that directory, resolved against the - * current project's directory. Otherwise, use the {@code Rome} sub folder in - * the shared data directory. - * - * @param config Configuration for this step. - * @return The download directory for the Rome executable. - */ - private String resolveDownloadDir(FormatterStepConfig config) { - final var fileLocator = config.getFileLocator(); - if (downloadDir != null && !downloadDir.isBlank()) { - return fileLocator.getBaseDir().toPath().resolve(downloadDir).toAbsolutePath().toString(); - } else { - return fileLocator.getDataDir().toPath().resolve("rome").toString(); - } + protected String getLanguage() { + return "js?"; } } diff --git a/plugin-maven/src/main/java/com/diffplug/spotless/maven/json/Json.java b/plugin-maven/src/main/java/com/diffplug/spotless/maven/json/Json.java index 5bb7c17b3a..adbb2b7883 100644 --- a/plugin-maven/src/main/java/com/diffplug/spotless/maven/json/Json.java +++ b/plugin-maven/src/main/java/com/diffplug/spotless/maven/json/Json.java @@ -50,4 +50,7 @@ public void addJackson(JacksonJson jackson) { addStepFactory(jackson); } + public void addRome(RomeJson rome) { + addStepFactory(rome); + } } diff --git a/plugin-maven/src/main/java/com/diffplug/spotless/maven/json/RomeJson.java b/plugin-maven/src/main/java/com/diffplug/spotless/maven/json/RomeJson.java new file mode 100644 index 0000000000..df75b7ffbe --- /dev/null +++ b/plugin-maven/src/main/java/com/diffplug/spotless/maven/json/RomeJson.java @@ -0,0 +1,13 @@ +package com.diffplug.spotless.maven.json; + +import com.diffplug.spotless.maven.rome.AbstractRome; + +/** + * Rome formatter step for JSON. + */ +public class RomeJson extends AbstractRome { + @Override + protected String getLanguage() { + return "json"; + } +} diff --git a/plugin-maven/src/main/java/com/diffplug/spotless/maven/rome/AbstractRome.java b/plugin-maven/src/main/java/com/diffplug/spotless/maven/rome/AbstractRome.java new file mode 100644 index 0000000000..c9e28ea9b3 --- /dev/null +++ b/plugin-maven/src/main/java/com/diffplug/spotless/maven/rome/AbstractRome.java @@ -0,0 +1,173 @@ +package com.diffplug.spotless.maven.rome; + +import java.nio.file.Paths; + +import org.apache.maven.plugins.annotations.Parameter; + +import com.diffplug.spotless.FormatterStep; +import com.diffplug.spotless.maven.FormatterStepConfig; +import com.diffplug.spotless.maven.FormatterStepFactory; +import com.diffplug.spotless.rome.RomeStep; +import com.diffplug.spotless.rome.RomeStep.Builder; + +/** + * Factory for creating the Rome formatter step that can format format code in + * various types of language with Rome. Currently Rome support JavaScript, + * TypeScript, JSX, TSX, and JSON. See also + * https://github.com/rome/tools. + * It delegates to the Rome CLI executable. + */ +public abstract class AbstractRome 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. + * Defaults to + * ~/.m2/repository/com/diffplug/spotless/spotless-data/rome. + *

+ * You can use an expression like ${user.home}/rome if you want to + * use the home directory, or ${project.build.directory if you want + * to use the target directory of the current project. + */ + @Parameter + private String downloadDir; + + /** + * Optional path to the Rome executable. Either a version or a + * pathToExe should be specified. When not given, an attempt is + * made to download the executable for the given version from the network. When + * given, the executable is used and the version parameter is + * ignored. + *

+ * When an absolute path is given, that path is used as-is. When a relative path + * is given, it is resolved against the project's base directory. When only a + * file name (i.e. without any slashes or back slash path separators such as + * {@code rome}) is given, this is interpreted as the name of a command with + * executable that is in your {@code path} environment variable. Use + * {@code ./executable-name} if you want to use an executable in the project's + * base directory. + */ + @Parameter + private String pathToExe; + + /** + * Rome version to download, applies only when no pathToExe is + * specified explicitly. Either a version or a + * pathToExe should be specified. When not given, a default known + * version is used. For stable builds, it is recommended that you always set the + * version explicitly. This parameter is ignored when you specify a + * pathToExe explicitly. + */ + @Parameter + private String version; + + @Override + public FormatterStep newFormatterStep(FormatterStepConfig config) { + var builder = newBuilder(config); + if (configPath != null) { + var resolvedConfigFile = resolveConfigFile(config); + builder.withConfigPath(resolvedConfigFile); + } + if (getLanguage() != null) { + builder.withLanguage(getLanguage()); + } + var step = builder.build(); + return step.create(); + } + + /** + * Gets the language (syntax) of the input files to format. When + * null or the empty string, the language is detected automatically + * from the file name. Currently the following languages are supported by Rome: + *

    + *
  • js (JavaScript)
  • + *
  • jsx (JavaScript + JSX)
  • + *
  • js? (JavaScript or JavaScript + JSX, depending on the file + * extension)
  • + *
  • ts (TypeScript)
  • + *
  • tsx (TypeScript + JSX)
  • + *
  • ts? (TypeScript or TypeScript + JSX, depending on the file + * extension)
  • + *
  • json (JSON)
  • + *
+ * + * @return The language of the input files. + */ + protected abstract String getLanguage(); + + /** + * A new builder for configuring a Rome step that either downloads the Rome + * executable with the given version from the network, or uses the executable + * from the given path. + * + * @param config Configuration from the Maven Mojo execution with details about + * the currently executed project. + * @return A builder for a Rome step. + */ + private Builder newBuilder(FormatterStepConfig config) { + if (pathToExe != null) { + var resolvedExePath = resolveExePath(config); + return RomeStep.withExePath(resolvedExePath); + } else { + var downloadDir = resolveDownloadDir(config); + return RomeStep.withExeDownload(version, downloadDir); + } + } + + /** + * 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 + * the user's path. Otherwise resolve the executable path 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 Rome executable. + */ + private String resolveExePath(FormatterStepConfig config) { + var path = Paths.get(pathToExe); + if (path.getNameCount() == 1) { + return path.toString(); + } else { + return config.getFileLocator().getBaseDir().toPath().resolve(path).toAbsolutePath().toString(); + } + } + + /** + * Resolves the directory to use for storing downloaded Rome executable. When a + * {@link #downloadDir} is given, use that directory, resolved against the + * current project's directory. Otherwise, use the {@code Rome} sub folder in + * the shared data directory. + * + * @param config Configuration for this step. + * @return The download directory for the Rome executable. + */ + private String resolveDownloadDir(FormatterStepConfig config) { + final var fileLocator = config.getFileLocator(); + if (downloadDir != null && !downloadDir.isBlank()) { + return fileLocator.getBaseDir().toPath().resolve(downloadDir).toAbsolutePath().toString(); + } else { + return fileLocator.getDataDir().toPath().resolve("rome").toString(); + } + } +} diff --git a/plugin-maven/src/main/java/com/diffplug/spotless/maven/typescript/RomeTs.java b/plugin-maven/src/main/java/com/diffplug/spotless/maven/typescript/RomeTs.java new file mode 100644 index 0000000000..dfc736dc2a --- /dev/null +++ b/plugin-maven/src/main/java/com/diffplug/spotless/maven/typescript/RomeTs.java @@ -0,0 +1,13 @@ +package com.diffplug.spotless.maven.typescript; + +import com.diffplug.spotless.maven.rome.AbstractRome; + +/** + * Rome formatter step for TypeScript. + */ +public class RomeTs extends AbstractRome { + @Override + protected String getLanguage() { + return "ts?"; + } +} diff --git a/plugin-maven/src/main/java/com/diffplug/spotless/maven/typescript/Typescript.java b/plugin-maven/src/main/java/com/diffplug/spotless/maven/typescript/Typescript.java index 929ea334b1..ddae74db82 100644 --- a/plugin-maven/src/main/java/com/diffplug/spotless/maven/typescript/Typescript.java +++ b/plugin-maven/src/main/java/com/diffplug/spotless/maven/typescript/Typescript.java @@ -21,7 +21,6 @@ import org.apache.maven.project.MavenProject; import com.diffplug.spotless.maven.FormatterFactory; -import com.diffplug.spotless.maven.javascript.RomeJs; /** * A {@link FormatterFactory} implementation that corresponds to {@code ...} configuration element. @@ -47,7 +46,7 @@ public void addEslint(EslintTs eslint) { addStepFactory(eslint); } - public void addRome(RomeJs rome) { + public void addRome(RomeTs rome) { addStepFactory(rome); } } From 7461c040fb7d15ecbfdcfc9f8992538b9f8a452b Mon Sep 17 00:00:00 2001 From: Andre Wachsmuth Date: Sat, 8 Apr 2023 13:57:09 +0100 Subject: [PATCH 09/33] Add rome to feature matrix --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 001b00b5e2..1809fc5d5f 100644 --- a/README.md +++ b/README.md @@ -149,6 +149,7 @@ lib('yaml.JacksonYamlStep') +'{{yes}} | {{yes}} | [`npm.TsFmtFormatterStep`](lib/src/main/java/com/diffplug/spotless/npm/TsFmtFormatterStep.java) | :+1: | :+1: | :white_large_square: | :white_large_square: | | [`pom.SortPomStep`](lib/src/main/java/com/diffplug/spotless/pom/SortPomStep.java) | :white_large_square: | :+1: | :white_large_square: | :white_large_square: | | [`python.BlackStep`](lib/src/main/java/com/diffplug/spotless/python/BlackStep.java) | :+1: | :white_large_square: | :white_large_square: | :white_large_square: | +| [`rome.RomeStep`](lib/src/main/java/com/diffplug/spotless/rome/RomeStep.java) | :+1: | :+1: | :white_large_square: | :white_large_square: | | [`scala.ScalaFmtStep`](lib/src/main/java/com/diffplug/spotless/scala/ScalaFmtStep.java) | :+1: | :+1: | :+1: | :white_large_square: | | [`sql.DBeaverSQLFormatterStep`](lib/src/main/java/com/diffplug/spotless/sql/DBeaverSQLFormatterStep.java) | :+1: | :+1: | :+1: | :white_large_square: | | [`wtp.EclipseWtpFormatterStep`](lib-extra/src/main/java/com/diffplug/spotless/extra/wtp/EclipseWtpFormatterStep.java) | :+1: | :+1: | :white_large_square: | :white_large_square: | From 17b05d70894adfb3953bad7e78f461deab7cb3ea Mon Sep 17 00:00:00 2001 From: Andre Wachsmuth Date: Sat, 8 Apr 2023 17:19:57 +0100 Subject: [PATCH 10/33] Add rome() step to generic format for Gradle plugin --- .../gradle/spotless/FormatExtension.java | 309 ++++++++++++++++++ .../diffplug/spotless/maven/generic/Rome.java | 6 + 2 files changed, 315 insertions(+) diff --git a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/FormatExtension.java b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/FormatExtension.java index fd613e82e1..b7c8dc2d2b 100644 --- a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/FormatExtension.java +++ b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/FormatExtension.java @@ -22,6 +22,7 @@ import java.io.Serializable; import java.nio.charset.Charset; import java.nio.file.Files; +import java.nio.file.Paths; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -37,6 +38,7 @@ import org.gradle.api.Action; import org.gradle.api.GradleException; import org.gradle.api.Project; +import org.gradle.api.artifacts.repositories.MavenArtifactRepository; import org.gradle.api.file.ConfigurableFileTree; import org.gradle.api.file.FileCollection; import org.gradle.api.plugins.BasePlugin; @@ -63,6 +65,7 @@ import com.diffplug.spotless.generic.TrimTrailingWhitespaceStep; import com.diffplug.spotless.npm.NpmPathResolver; import com.diffplug.spotless.npm.PrettierFormatterStep; +import com.diffplug.spotless.rome.RomeStep; import groovy.lang.Closure; @@ -648,6 +651,296 @@ protected FormatterStep createStep() { this.prettierConfig)); } } + + public abstract static class RomeStepConfig> { + /** + * 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. + */ + @Nullable + private Object 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. + * Defaults to + * ~/.m2/repository/com/diffplug/spotless/spotless-data/rome. + */ + @Nullable + private Object downloadDir; + + /** + * Optional path to the Rome executable. Either a version or a + * pathToExe should be specified. When not given, an attempt is + * made to download the executable for the given version from the network. When + * given, the executable is used and the version parameter is + * ignored. + *

+ * When an absolute path is given, that path is used as-is. When a relative path + * is given, it is resolved against the project's base directory. When only a + * file name (i.e. without any slashes or back slash path separators such as + * {@code rome}) is given, this is interpreted as the name of a command with + * executable that is in your {@code path} environment variable. Use + * {@code ./executable-name} if you want to use an executable in the project's + * base directory. + */ + @Nullable + private Object pathToExe; + + /** A reference to the Gradle project for which spotless is executed. */ + private final Project project; + + /** Replaces the current Rome formatter step with the given step. */ + private final Consumer replaceStep; + + /** + * Rome version to download, applies only when no pathToExe is + * specified explicitly. Either a version or a + * pathToExe should be specified. When not given, a default known + * version is used. For stable builds, it is recommended that you always set the + * version explicitly. This parameter is ignored when you specify a + * pathToExe explicitly. + */ + @Nullable + private String version; + + protected RomeStepConfig(Project project, Consumer replaceStep, String version) { + this.project = requireNonNull(project); + this.replaceStep = requireNonNull(replaceStep); + this.version = version; + } + + /** + * 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. + * @return This step for further configuration. + */ + public Self configPath(Object configPath) { + this.configPath = configPath; + replaceStep(); + return getThis(); + } + + /** + * Optional directory where the downloaded Rome executable is placed. If this is + * a relative path, it is resolved against the project's base directory. + * Defaults to + * ~/.m2/repository/com/diffplug/spotless/spotless-data/rome. + * @return This step for further configuration. + */ + public Self downloadDir(Object downloadDir) { + this.downloadDir = downloadDir; + replaceStep(); + return getThis(); + } + + /** + * Optional path to the Rome executable. Overwrites the configured version. No + * attempt is made to download the Rome executable from the network. + *

+ * When an absolute path is given, that path is used as-is. When a relative path + * is given, it is resolved against the project's base directory. When only a + * file name (i.e. without any slashes or back slash path separators such as + * {@code rome}) is given, this is interpreted as the name of a command with + * executable that is in your {@code path} environment variable. Use + * {@code ./executable-name} if you want to use an executable in the project's + * base directory. + * @return This step for further configuration. + */ + public Self pathToExe(Object pathToExe) { + this.pathToExe = pathToExe; + replaceStep(); + return getThis(); + } + + /** + * Creates a new formatter step that formats code by calling the Rome + * executable, using the current configuration. + * + * @return A new formatter step for the Rome formatter. + */ + protected FormatterStep createStep() { + var builder = newBuilder(); + if (configPath != null) { + var resolvedConfigPath = project.file(configPath); + builder.withConfigPath(resolvedConfigPath.toString()); + } + builder.withLanguage(getLanguage()); + var rome = builder.build(); + return rome.create(); + } + + /** + * Gets the language (syntax) of the input files to format. When + * null or the empty string, the language is detected automatically + * from the file name. Currently the following languages are supported by Rome: + *

    + *
  • js (JavaScript)
  • + *
  • jsx (JavaScript + JSX)
  • + *
  • js? (JavaScript or JavaScript + JSX, depending on the file + * extension)
  • + *
  • ts (TypeScript)
  • + *
  • tsx (TypeScript + JSX)
  • + *
  • ts? (TypeScript or TypeScript + JSX, depending on the file + * extension)
  • + *
  • json (JSON)
  • + *
+ * + * @return The language of the input files. + */ + protected abstract String getLanguage(); + + /** + * @return This Rome config instance. + */ + protected abstract Self getThis(); + + /** + * Creates a new Rome step and replaces the existing Rome step in the list of + * format steps. + */ + protected void replaceStep() { + replaceStep.accept(createStep()); + } + + /** + * Finds the data directory that can be used for storing shared data such as + * Rome executable globally. This is a directory in the local repository, e.g. + * ~/.m2/repository/com/diffplus/spotless/spotless-data. + * + * @return The directory for storing shared data. + */ + private File findDataDir() { + var currentRepo = project.getRepositories().stream() + .filter(r -> r instanceof MavenArtifactRepository) + .map(r -> (MavenArtifactRepository) r) + .filter(r -> "file".equals(r.getUrl().getScheme())) + .findAny().orElse(null); + // Temporarily add mavenLocal() repository to get its file URL + var localRepo = currentRepo != null ? (MavenArtifactRepository)currentRepo : project.getRepositories().mavenLocal(); + try { + // e.g. ~/.m2/repository/ + var repoPath = Paths.get(localRepo.getUrl().getPath()); + var dataPath = repoPath.resolve("com").resolve("diffplus").resolve("spotless").resolve("spotless-data"); + return dataPath.toAbsolutePath().toFile(); + } + finally { + // Remove mavenLocal() repository again if it was not part of the project + if (currentRepo == null) { + project.getRepositories().remove(localRepo); + } + } + } + + /** + * A new builder for configuring a Rome step that either downloads the Rome + * executable with the given version from the network, or uses the executable + * from the given path. + * + * @return A builder for a Rome step. + */ + private RomeStep.Builder newBuilder() { + if (pathToExe != null) { + var resolvedPathToExe = resolvePathToExe(); + return RomeStep.withExePath(resolvedPathToExe); + } else { + var downloadDir = resolveDownloadDir(); + return RomeStep.withExeDownload(version, downloadDir); + } + } + + /** + * 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 + * the user's path. Otherwise resolve the executable path 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 Rome executable. + */ + private String resolvePathToExe() { + var fileNameOnly = pathToExe instanceof String && Paths.get(pathToExe.toString()).getNameCount() == 1; + if (fileNameOnly) { + return pathToExe.toString(); + } else { + return project.file(pathToExe).toString(); + } + } + + /** + * Resolves the directory to use for storing downloaded Rome executable. When a + * {@link #downloadDir} is given, use that directory, resolved against the + * current project's directory. Otherwise, use the {@code Rome} sub folder in + * the shared data directory. + * + * @param config Configuration for this step. + * @return The download directory for the Rome executable. + */ + private String resolveDownloadDir() { + if (downloadDir != null) { + return project.file(downloadDir).toString(); + } else { + return findDataDir().toPath().resolve("rome").toString(); + } + } + } + + /** + * Generic Rome formatter step that detects the language of the input file from + * the file name. It should be specified as a formatter step for a generic + * format{ ... }. + */ + public class RomeGeneric extends RomeStepConfig { + @Nullable + String language; + + /** + * Creates a new Rome config that downloads the Rome executable for the given version from the network. + * @param version Rome version to use. The default version is used when null. + */ + public RomeGeneric(String version) { + super(getProject(), FormatExtension.this::replaceStep, version); + } + + /** + * Sets the language (syntax) of the input files to format. When + * null or the empty string, the language is detected automatically + * from the file name. Currently the following languages are supported by Rome: + *
    + *
  • js (JavaScript)
  • + *
  • jsx (JavaScript + JSX)
  • + *
  • js? (JavaScript or JavaScript + JSX, depending on the file + * extension)
  • + *
  • ts (TypeScript)
  • + *
  • tsx (TypeScript + JSX)
  • + *
  • ts? (TypeScript or TypeScript + JSX, depending on the file + * extension)
  • + *
  • json (JSON)
  • + *
+ * @param language The language of the files to format. + * @return This step for further configuration. + */ + public RomeGeneric language(String language) { + this.language = language; + replaceStep(); + return this; + } + + @Override + protected String getLanguage() { + return language; + } + + @Override + protected RomeGeneric getThis() { + return this; + } + } /** Uses the default version of prettier. */ public PrettierConfig prettier() { @@ -665,6 +958,22 @@ public PrettierConfig prettier(Map devDependencies) { addStep(prettierConfig.createStep()); return prettierConfig; } + + /** + * Defaults to downloading the default Rome version from the network. To work + * offline, you can specify the path to the Rome executable via + * {@code rome().pathToExe(...)}. + */ + public RomeGeneric rome() { + return rome(null); + } + + /** Downloads the given Rome version from the network. */ + public RomeGeneric rome(String version) { + var romeConfig = new RomeGeneric(version); + addStep(romeConfig.createStep()); + return romeConfig; + } /** Uses the default version of clang-format. */ public ClangFormatConfig clangFormat() { diff --git a/plugin-maven/src/main/java/com/diffplug/spotless/maven/generic/Rome.java b/plugin-maven/src/main/java/com/diffplug/spotless/maven/generic/Rome.java index b3d645f9e6..c39bb5df3b 100644 --- a/plugin-maven/src/main/java/com/diffplug/spotless/maven/generic/Rome.java +++ b/plugin-maven/src/main/java/com/diffplug/spotless/maven/generic/Rome.java @@ -15,12 +15,18 @@ public class Rome extends AbstractRome { * null or the empty string, the language is detected automatically * from the file name. Currently the following languages are supported by Rome: *
    + *
      *
    • js (JavaScript)
    • *
    • jsx (JavaScript + JSX)
    • + *
    • js? (JavaScript or JavaScript + JSX, depending on the file + * extension)
    • *
    • ts (TypeScript)
    • *
    • tsx (TypeScript + JSX)
    • + *
    • ts? (TypeScript or TypeScript + JSX, depending on the file + * extension)
    • *
    • json (JSON)
    • *
    + *
* * @return The language of the input files. */ From 7481a9bb86655be6b01f668bdecb524bd051073f Mon Sep 17 00:00:00 2001 From: Andre Wachsmuth Date: Sat, 8 Apr 2023 17:27:20 +0100 Subject: [PATCH 11/33] Add license header --- .../com/diffplug/spotless/rome/Architecture.java | 15 +++++++++++++++ .../main/java/com/diffplug/spotless/rome/OS.java | 15 +++++++++++++++ .../java/com/diffplug/spotless/rome/Platform.java | 15 +++++++++++++++ .../spotless/rome/RomeExecutableDownloader.java | 15 +++++++++++++++ .../java/com/diffplug/spotless/rome/RomeStep.java | 15 +++++++++++++++ .../com/diffplug/spotless/maven/generic/Rome.java | 15 +++++++++++++++ .../spotless/maven/javascript/RomeJs.java | 15 +++++++++++++++ .../diffplug/spotless/maven/json/RomeJson.java | 15 +++++++++++++++ .../spotless/maven/rome/AbstractRome.java | 15 +++++++++++++++ .../spotless/maven/typescript/RomeTs.java | 15 +++++++++++++++ 10 files changed, 150 insertions(+) diff --git a/lib/src/main/java/com/diffplug/spotless/rome/Architecture.java b/lib/src/main/java/com/diffplug/spotless/rome/Architecture.java index 1207f2ddc7..835bf8367a 100644 --- a/lib/src/main/java/com/diffplug/spotless/rome/Architecture.java +++ b/lib/src/main/java/com/diffplug/spotless/rome/Architecture.java @@ -1,3 +1,18 @@ +/* + * Copyright 2016-2022 DiffPlug + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package com.diffplug.spotless.rome; /** diff --git a/lib/src/main/java/com/diffplug/spotless/rome/OS.java b/lib/src/main/java/com/diffplug/spotless/rome/OS.java index c7b874bb24..cc5338ba0e 100644 --- a/lib/src/main/java/com/diffplug/spotless/rome/OS.java +++ b/lib/src/main/java/com/diffplug/spotless/rome/OS.java @@ -1,3 +1,18 @@ +/* + * Copyright 2016-2022 DiffPlug + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package com.diffplug.spotless.rome; import java.util.Locale; diff --git a/lib/src/main/java/com/diffplug/spotless/rome/Platform.java b/lib/src/main/java/com/diffplug/spotless/rome/Platform.java index 2631cf3131..8a551d1810 100644 --- a/lib/src/main/java/com/diffplug/spotless/rome/Platform.java +++ b/lib/src/main/java/com/diffplug/spotless/rome/Platform.java @@ -1,3 +1,18 @@ +/* + * Copyright 2016-2022 DiffPlug + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package com.diffplug.spotless.rome; /** diff --git a/lib/src/main/java/com/diffplug/spotless/rome/RomeExecutableDownloader.java b/lib/src/main/java/com/diffplug/spotless/rome/RomeExecutableDownloader.java index 72df92224f..35bb4966c3 100644 --- a/lib/src/main/java/com/diffplug/spotless/rome/RomeExecutableDownloader.java +++ b/lib/src/main/java/com/diffplug/spotless/rome/RomeExecutableDownloader.java @@ -1,3 +1,18 @@ +/* + * Copyright 2016-2022 DiffPlug + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package com.diffplug.spotless.rome; import java.io.IOException; diff --git a/lib/src/main/java/com/diffplug/spotless/rome/RomeStep.java b/lib/src/main/java/com/diffplug/spotless/rome/RomeStep.java index 820d1af66e..8ede197a36 100644 --- a/lib/src/main/java/com/diffplug/spotless/rome/RomeStep.java +++ b/lib/src/main/java/com/diffplug/spotless/rome/RomeStep.java @@ -1,3 +1,18 @@ +/* + * Copyright 2016-2022 DiffPlug + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package com.diffplug.spotless.rome; import java.io.File; diff --git a/plugin-maven/src/main/java/com/diffplug/spotless/maven/generic/Rome.java b/plugin-maven/src/main/java/com/diffplug/spotless/maven/generic/Rome.java index c39bb5df3b..29b290a746 100644 --- a/plugin-maven/src/main/java/com/diffplug/spotless/maven/generic/Rome.java +++ b/plugin-maven/src/main/java/com/diffplug/spotless/maven/generic/Rome.java @@ -1,3 +1,18 @@ +/* + * Copyright 2016-2022 DiffPlug + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package com.diffplug.spotless.maven.generic; import org.apache.maven.plugins.annotations.Parameter; diff --git a/plugin-maven/src/main/java/com/diffplug/spotless/maven/javascript/RomeJs.java b/plugin-maven/src/main/java/com/diffplug/spotless/maven/javascript/RomeJs.java index 11467a2820..cf40f53f47 100644 --- a/plugin-maven/src/main/java/com/diffplug/spotless/maven/javascript/RomeJs.java +++ b/plugin-maven/src/main/java/com/diffplug/spotless/maven/javascript/RomeJs.java @@ -1,3 +1,18 @@ +/* + * Copyright 2016-2022 DiffPlug + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package com.diffplug.spotless.maven.javascript; import com.diffplug.spotless.maven.rome.AbstractRome; diff --git a/plugin-maven/src/main/java/com/diffplug/spotless/maven/json/RomeJson.java b/plugin-maven/src/main/java/com/diffplug/spotless/maven/json/RomeJson.java index df75b7ffbe..5b2eec6abb 100644 --- a/plugin-maven/src/main/java/com/diffplug/spotless/maven/json/RomeJson.java +++ b/plugin-maven/src/main/java/com/diffplug/spotless/maven/json/RomeJson.java @@ -1,3 +1,18 @@ +/* + * Copyright 2016-2022 DiffPlug + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package com.diffplug.spotless.maven.json; import com.diffplug.spotless.maven.rome.AbstractRome; diff --git a/plugin-maven/src/main/java/com/diffplug/spotless/maven/rome/AbstractRome.java b/plugin-maven/src/main/java/com/diffplug/spotless/maven/rome/AbstractRome.java index c9e28ea9b3..ccf253d770 100644 --- a/plugin-maven/src/main/java/com/diffplug/spotless/maven/rome/AbstractRome.java +++ b/plugin-maven/src/main/java/com/diffplug/spotless/maven/rome/AbstractRome.java @@ -1,3 +1,18 @@ +/* + * Copyright 2016-2022 DiffPlug + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package com.diffplug.spotless.maven.rome; import java.nio.file.Paths; diff --git a/plugin-maven/src/main/java/com/diffplug/spotless/maven/typescript/RomeTs.java b/plugin-maven/src/main/java/com/diffplug/spotless/maven/typescript/RomeTs.java index dfc736dc2a..21f182079b 100644 --- a/plugin-maven/src/main/java/com/diffplug/spotless/maven/typescript/RomeTs.java +++ b/plugin-maven/src/main/java/com/diffplug/spotless/maven/typescript/RomeTs.java @@ -1,3 +1,18 @@ +/* + * Copyright 2016-2022 DiffPlug + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package com.diffplug.spotless.maven.typescript; import com.diffplug.spotless.maven.rome.AbstractRome; From afd0edb6df7a708c39d6b5baab8d895c662306de Mon Sep 17 00:00:00 2001 From: Andre Wachsmuth Date: Sat, 8 Apr 2023 17:30:48 +0100 Subject: [PATCH 12/33] Run spotlessApply --- README.md | 2 +- .../diffplug/spotless/rome/Architecture.java | 4 +- .../java/com/diffplug/spotless/rome/OS.java | 6 +-- .../com/diffplug/spotless/rome/Platform.java | 6 +-- .../rome/RomeExecutableDownloader.java | 36 +++++++-------- .../com/diffplug/spotless/rome/RomeStep.java | 45 +++++++++---------- .../gradle/spotless/FormatExtension.java | 23 +++++----- .../diffplug/spotless/maven/FileLocator.java | 8 ++-- .../spotless/maven/generic/Format.java | 2 +- .../diffplug/spotless/maven/generic/Rome.java | 4 +- .../spotless/maven/javascript/RomeJs.java | 2 +- .../spotless/maven/json/RomeJson.java | 2 +- .../spotless/maven/rome/AbstractRome.java | 12 ++--- .../spotless/maven/typescript/RomeTs.java | 2 +- 14 files changed, 76 insertions(+), 78 deletions(-) diff --git a/README.md b/README.md index 1809fc5d5f..aceb9c22e9 100644 --- a/README.md +++ b/README.md @@ -149,7 +149,7 @@ lib('yaml.JacksonYamlStep') +'{{yes}} | {{yes}} | [`npm.TsFmtFormatterStep`](lib/src/main/java/com/diffplug/spotless/npm/TsFmtFormatterStep.java) | :+1: | :+1: | :white_large_square: | :white_large_square: | | [`pom.SortPomStep`](lib/src/main/java/com/diffplug/spotless/pom/SortPomStep.java) | :white_large_square: | :+1: | :white_large_square: | :white_large_square: | | [`python.BlackStep`](lib/src/main/java/com/diffplug/spotless/python/BlackStep.java) | :+1: | :white_large_square: | :white_large_square: | :white_large_square: | -| [`rome.RomeStep`](lib/src/main/java/com/diffplug/spotless/rome/RomeStep.java) | :+1: | :+1: | :white_large_square: | :white_large_square: | +| [`rome.RomeStep`](lib/src/main/java/com/diffplug/spotless/rome/RomeStep.java) | :+1: | :+1: | :white_large_square: | :white_large_square: | | [`scala.ScalaFmtStep`](lib/src/main/java/com/diffplug/spotless/scala/ScalaFmtStep.java) | :+1: | :+1: | :+1: | :white_large_square: | | [`sql.DBeaverSQLFormatterStep`](lib/src/main/java/com/diffplug/spotless/sql/DBeaverSQLFormatterStep.java) | :+1: | :+1: | :+1: | :white_large_square: | | [`wtp.EclipseWtpFormatterStep`](lib-extra/src/main/java/com/diffplug/spotless/extra/wtp/EclipseWtpFormatterStep.java) | :+1: | :+1: | :white_large_square: | :white_large_square: | diff --git a/lib/src/main/java/com/diffplug/spotless/rome/Architecture.java b/lib/src/main/java/com/diffplug/spotless/rome/Architecture.java index 835bf8367a..64e0a94640 100644 --- a/lib/src/main/java/com/diffplug/spotless/rome/Architecture.java +++ b/lib/src/main/java/com/diffplug/spotless/rome/Architecture.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2022 DiffPlug + * Copyright 2016-2023 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,7 +26,7 @@ enum Architecture { /** * Attempts to guess the architecture of the environment running the JVM. - * + * * @return The best guess for the architecture. */ public static Architecture guess() { diff --git a/lib/src/main/java/com/diffplug/spotless/rome/OS.java b/lib/src/main/java/com/diffplug/spotless/rome/OS.java index cc5338ba0e..44c87d9ece 100644 --- a/lib/src/main/java/com/diffplug/spotless/rome/OS.java +++ b/lib/src/main/java/com/diffplug/spotless/rome/OS.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2022 DiffPlug + * Copyright 2016-2023 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -30,7 +30,7 @@ enum OS { /** * Attempts to guess the OS of the environment running the JVM. - * + * * @return The best guess for the architecture. * @throws IllegalStateException When the OS is either unsupported or no * information about the OS could be retrieved. @@ -53,4 +53,4 @@ public static OS guess() { return OS.LINUX; } } -} \ No newline at end of file +} diff --git a/lib/src/main/java/com/diffplug/spotless/rome/Platform.java b/lib/src/main/java/com/diffplug/spotless/rome/Platform.java index 8a551d1810..c50608830a 100644 --- a/lib/src/main/java/com/diffplug/spotless/rome/Platform.java +++ b/lib/src/main/java/com/diffplug/spotless/rome/Platform.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2022 DiffPlug + * Copyright 2016-2023 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,7 +23,7 @@ class Platform { /** * Attempts to guess the platform of the hosting environment running the JVM * machine. - * + * * @return The best guess for the current OS and architecture. * @throws IllegalStateException When no OS information is available, or when * the OS or architecture is unsupported. @@ -40,7 +40,7 @@ public static Platform guess() { /** * Creates a new Platform descriptor for the given OS and architecture. - * + * * @param os Operating system of the platform. * @param architecture Architecture of the platform. */ diff --git a/lib/src/main/java/com/diffplug/spotless/rome/RomeExecutableDownloader.java b/lib/src/main/java/com/diffplug/spotless/rome/RomeExecutableDownloader.java index 35bb4966c3..c55a18f754 100644 --- a/lib/src/main/java/com/diffplug/spotless/rome/RomeExecutableDownloader.java +++ b/lib/src/main/java/com/diffplug/spotless/rome/RomeExecutableDownloader.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2022 DiffPlug + * Copyright 2016-2023 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -68,7 +68,7 @@ final class RomeExecutableDownloader { * {@link OpenOption Open options} for reading an existing file without write * access. */ - private static final OpenOption[] READ_OPTIONS = { StandardOpenOption.READ }; + private static final OpenOption[] READ_OPTIONS = {StandardOpenOption.READ}; /** * The pattern for {@link String#format(String, Object...) String.format()} for @@ -81,15 +81,15 @@ final class RomeExecutableDownloader { * {@link OpenOption Open options} for creating a new file, overwriting the * existing file if present. */ - private static final OpenOption[] WRITE_OPTIONS = { StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING, - StandardOpenOption.WRITE }; + private static final OpenOption[] WRITE_OPTIONS = {StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING, + StandardOpenOption.WRITE}; private Path downloadDir; /** * Creates a new downloader for the Rome executable. The executable files are * stored in the given download directory. - * + * * @param downloadDir Directory where */ public RomeExecutableDownloader(Path downloadDir) { @@ -100,7 +100,7 @@ public RomeExecutableDownloader(Path downloadDir) { * Downloads the Rome executable for the current platform from the network to * the download directory. When the executable exists already, it is * overwritten. - * + * * @param version Desired Rome version. * @return The path to the Rome executable. * @throws IOException When the executable cannot be downloaded from @@ -140,7 +140,7 @@ public Path download(String version) throws IOException, InterruptedException { * directory, an attempt is made to download the Rome executable from the * network. When the executable exists already, no attempt to download it again * is made. - * + * * @param version Desired Rome version. * @return The path to the Rome executable. * @throws IOException When the executable cannot be downloaded from @@ -168,7 +168,7 @@ public Path ensureDownloaded(String version) throws IOException, InterruptedExce /** * Attempts to find the Rome executable for the current platform in the download * directory. No attempt is made to download the executable from the network. - * + * * @param version Desired Rome version. * @return The path to the Rome executable. * @throws IOException When the executable does not exists in the @@ -188,7 +188,7 @@ public Optional findDownloaded(String version) throws IOException { /** * Checks whether the given file exists and matches the checksum. The checksum * must be contained in a file next to the file to check. - * + * * @param filePath File to check. * @return true if the file exists and matches the checksum, * false otherwise. @@ -223,7 +223,7 @@ private boolean checkFileWithChecksum(Path filePath) { /** * Computes the checksum of the given file. - * + * * @param file File to process. * @param algorithm The checksum algorithm to use. * @return The MD5 checksum of the given file. @@ -247,7 +247,7 @@ private String computeChecksum(Path file, String algorithm) throws IOException { /** * Finds the code name for the given operating system used by the Rome * executable download URL. - * + * * @param os Desired operating system. * @return Code name for the Rome download URL. * @throws IOException When the given OS is not supported by Rome. @@ -265,7 +265,7 @@ private String getArchitectureCodeName(Architecture architecture) throws IOExcep /** * Derives a path for the file which contains the checksum of the given file. - * + * * @param file A file for which to derive the checksum file path. * @return The path with the checksum for the given file. */ @@ -275,7 +275,7 @@ private Path getChecksumPath(Path file) { /** * Finds the URL from which the Rome executable can be downloaded. - * + * * @param version Desired Rome version. * @param platform Desired platform. * @return The URL for the Rome executable. @@ -292,7 +292,7 @@ private String getDownloadUrl(String version, Platform platform) throws IOExcept /** * Finds the file extension of the Rome download URL for the given operating * system. - * + * * @param os Desired operating system. * @return Extension for the Rome download URL. * @throws IOException When the given OS is not supported by Rome. @@ -313,7 +313,7 @@ private String getDownloadUrlExtension(OS os) throws IOException { /** * Finds the path on the file system for the Rome executable with a given * version and platform. - * + * * @param version Desired Rome version. * @param platform Desired platform. * @return The path for the Rome executable. @@ -328,7 +328,7 @@ private Path getExecutablePath(String version, Platform platform) { /** * Finds the code name for the given operating system used by the Rome * executable download URL. - * + * * @param os Desired operating system. * @return Code name for the Rome download URL. * @throws IOException When the given OS is not supported by Rome. @@ -348,7 +348,7 @@ private String getOsCodeName(OS os) throws IOException { /** * Reads a plain text file with the given encoding into a string. - * + * * @param file File to read. * @param charset Encoding to use. * @return The contents of the file as a string. @@ -363,7 +363,7 @@ private String readTextFile(Path file, Charset charset) throws IOException { /** * Computes the checksum of the given file and writes it to the target checksum * file, using the {@code ISO_8859_1} encoding. - * + * * @param file * @param checksumPath * @throws IOException diff --git a/lib/src/main/java/com/diffplug/spotless/rome/RomeStep.java b/lib/src/main/java/com/diffplug/spotless/rome/RomeStep.java index 8ede197a36..e48a78c2ba 100644 --- a/lib/src/main/java/com/diffplug/spotless/rome/RomeStep.java +++ b/lib/src/main/java/com/diffplug/spotless/rome/RomeStep.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2022 DiffPlug + * Copyright 2016-2023 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -101,7 +101,7 @@ public static String name() { /** * Creates a Rome step that format code by downloading to the given Rome * version. The executable is downloaded from the network. - * + * * @param version Version of the Rome executable to download. * @param downloadDir Directory where to place the downloaded executable. * @return A new Rome step that download the executable from the network. @@ -113,7 +113,7 @@ public static RomeStep.Builder withExeDownload(String version, String downloadDi /** * Creates a Rome step that formats code by delegating to the Rome executable * located at the given path. - * + * * @param pathToExe Path to the Rome executable to use. * @return A new Rome step that format with the given executable. */ @@ -125,7 +125,7 @@ public static RomeStep.Builder withExePath(String pathToExe) { * Attempts to add a POSIX permission to the given file, ignoring any errors. * All existing permissions on the file are preserved and the new permission is * added, if possible. - * + * * @param file File or directory to which to add a permission. * @param permission The POSIX permission to add. */ @@ -134,15 +134,14 @@ private static void attemptToAddPosixPermission(Path file, PosixFilePermission p var newPermissions = new HashSet<>(Files.getPosixFilePermissions(file)); newPermissions.add(permission); Files.setPosixFilePermissions(file, newPermissions); - } catch (final Exception ignore) { - } + } catch (final Exception ignore) {} } /** * 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() { @@ -154,7 +153,7 @@ private static String defaultVersion() { * any errors are swallowed. Depending on the OS, the file might still be * executable even if this method fails. The user will get a descriptive error * later when we attempt to execute the Rome executable. - * + * * @param filePath Path to the file to make executable. */ private static void makeExecutable(String filePath) { @@ -167,7 +166,7 @@ private static void makeExecutable(String filePath) { /** * Finds the absolute path of a command on the user's path. Uses {@code which} * for Linux and {@code where} for Windows. - * + * * @param name Name of the command to resolve. * @return The absolute path of the command's executable. * @throws IOException When the command could not be resolved. @@ -215,7 +214,7 @@ private static void validateRomeExecutable(String resolvedPathToExe) { /** * Creates a new Rome step with the configuration from the given builder. - * + * * @param builder Builder with the configuration to use. */ private RomeStep(RomeStep.Builder builder) { @@ -229,7 +228,7 @@ private RomeStep(RomeStep.Builder builder) { /** * Creates a formatter step with the current configuration, which formats code * by passing it to the Rome executable. - * + * * @return A new formatter step for formatting with Rome. */ public FormatterStep create() { @@ -240,7 +239,7 @@ public FormatterStep create() { * Resolves the Rome executable, possibly downloading it from the network, and * creates a new state instance with the resolved executable that can format * code via Rome. - * + * * @return The state instance for formatting code via Rome. * @throws IOException When any file system or network operations * failed, such as when the Rome executable could @@ -266,7 +265,7 @@ private State createState() throws IOException, InterruptedException { * used as-is. Otherwise, at attempt is made to download the Rome executable for * the configured version from the network, unless it was already downloaded and * is available in the cache. - * + * * @return The path to the resolved Rome executable. * @throws IOException When any file system or network operations * failed, such as when the Rome executable could @@ -310,7 +309,7 @@ public final static class Builder { /** * Creates a new builder for configuring a Rome step that can format code via * Rome. Either a version and and downloadDir, or a pathToExe must be given. - * + * * @param version The version of Rome to download, see * {@link RomeStep#version}. * @param pathToExe The path to the Rome executable to use, see @@ -327,7 +326,7 @@ private Builder(String version, String pathToExe, String downloadDir) { /** * Creates a new Rome step for formatting code with Rome from the current * configuration of this builder. - * + * * @return A new Rome step with the current configuration. */ public RomeStep build() { @@ -337,7 +336,7 @@ public RomeStep build() { /** * Sets the path to the directory with the {@code rome.json} config file. When * no config path is set, the default configuration is used. - * + * * @param configPath Config path to use. Must point to a directory which contain * a file named {@code rome.json}. * @return This builder instance for chaining method calls. @@ -351,7 +350,7 @@ public Builder withConfigPath(String configPath) { * Sets the language of the files to format When no language is set, it is * determined automatically from the file name. The following languages are * currently supported by Rome. - * + * *
    *
  • js (JavaScript)
  • *
  • jsx (JavaScript + JSX)
  • @@ -363,7 +362,7 @@ public Builder withConfigPath(String configPath) { * extension) *
  • json (JSON)
  • *
- * + * * @param language The language of the files to format. * @return This builder instance for chaining method calls. */ @@ -407,7 +406,7 @@ private static class State implements Serializable { /** * Creates a new state for instance which can format code with the given Rome * executable. - * + * * @param exe Path to the Rome executable. * @param exeSignature Signature (e.g. SHA-256 checksum) of the Rome executable. * @param configPath Path to the optional directory with the {@code rome.json} @@ -424,7 +423,7 @@ private State(String exe, FileSignature exeSignature, String configPath, String /** * 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. */ @@ -446,7 +445,7 @@ private String[] buildRomeCommand(File file) { * Formats the given piece of code by delegating to the Rome executable. The * code is passed to Rome via stdin, the file name is used by Rome only to * determine the code syntax (e.g. JavaScript or TypeScript). - * + * * @param runner Process runner for invoking the Rome executable. * @param input Code to format. * @param file File to format. @@ -471,7 +470,7 @@ private String format(ProcessRunner runner, String input, File file) throws IOEx * extension. This method returns the file name for the desired language when a * language was requested explicitly, or the file name of the input file for * auto detection. - * + * * @param file File to be formatted. * @return The file name to pass to the Rome executable. */ @@ -509,7 +508,7 @@ private String resolveFileName(File file) { /** * Creates a new formatter function for formatting a piece of code by delegating * to the Rome executable. - * + * * @return A formatter function for formatting code. */ private FormatterFunc.Closeable toFunc() { diff --git a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/FormatExtension.java b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/FormatExtension.java index b7c8dc2d2b..c3c366f3ea 100644 --- a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/FormatExtension.java +++ b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/FormatExtension.java @@ -651,7 +651,7 @@ protected FormatterStep createStep() { this.prettierConfig)); } } - + public abstract static class RomeStepConfig> { /** * Optional path to the directory with configuration file for Rome. The file @@ -760,7 +760,7 @@ public Self pathToExe(Object pathToExe) { /** * Creates a new formatter step that formats code by calling the Rome * executable, using the current configuration. - * + * * @return A new formatter step for the Rome formatter. */ protected FormatterStep createStep() { @@ -789,7 +789,7 @@ protected FormatterStep createStep() { * extension) *
  • json (JSON)
  • * - * + * * @return The language of the input files. */ protected abstract String getLanguage(); @@ -806,12 +806,12 @@ protected FormatterStep createStep() { protected void replaceStep() { replaceStep.accept(createStep()); } - + /** * Finds the data directory that can be used for storing shared data such as * Rome executable globally. This is a directory in the local repository, e.g. * ~/.m2/repository/com/diffplus/spotless/spotless-data. - * + * * @return The directory for storing shared data. */ private File findDataDir() { @@ -821,14 +821,13 @@ private File findDataDir() { .filter(r -> "file".equals(r.getUrl().getScheme())) .findAny().orElse(null); // Temporarily add mavenLocal() repository to get its file URL - var localRepo = currentRepo != null ? (MavenArtifactRepository)currentRepo : project.getRepositories().mavenLocal(); + var localRepo = currentRepo != null ? (MavenArtifactRepository) currentRepo : project.getRepositories().mavenLocal(); try { // e.g. ~/.m2/repository/ var repoPath = Paths.get(localRepo.getUrl().getPath()); var dataPath = repoPath.resolve("com").resolve("diffplus").resolve("spotless").resolve("spotless-data"); return dataPath.toAbsolutePath().toFile(); - } - finally { + } finally { // Remove mavenLocal() repository again if it was not part of the project if (currentRepo == null) { project.getRepositories().remove(localRepo); @@ -840,7 +839,7 @@ private File findDataDir() { * A new builder for configuring a Rome step that either downloads the Rome * executable with the given version from the network, or uses the executable * from the given path. - * + * * @return A builder for a Rome step. */ private RomeStep.Builder newBuilder() { @@ -858,7 +857,7 @@ private RomeStep.Builder newBuilder() { * do not perform any resolution and interpret it as a command that must be on * the user's path. Otherwise resolve the executable path 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 Rome executable. @@ -877,7 +876,7 @@ private String resolvePathToExe() { * {@link #downloadDir} is given, use that directory, resolved against the * current project's directory. Otherwise, use the {@code Rome} sub folder in * the shared data directory. - * + * * @param config Configuration for this step. * @return The download directory for the Rome executable. */ @@ -958,7 +957,7 @@ public PrettierConfig prettier(Map devDependencies) { addStep(prettierConfig.createStep()); return prettierConfig; } - + /** * Defaults to downloading the default Rome version from the network. To work * offline, you can specify the path to the Rome executable via diff --git a/plugin-maven/src/main/java/com/diffplug/spotless/maven/FileLocator.java b/plugin-maven/src/main/java/com/diffplug/spotless/maven/FileLocator.java index 7def4423f8..a8c3973dd3 100644 --- a/plugin-maven/src/main/java/com/diffplug/spotless/maven/FileLocator.java +++ b/plugin-maven/src/main/java/com/diffplug/spotless/maven/FileLocator.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2020 DiffPlug + * Copyright 2016-2023 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -74,7 +74,7 @@ public File locateFile(String path) { /** * Finds the base directory of the Maven or Gradle project on which spotless is * currently being executed. - * + * * @return The base directory of the current Maven or Gradel project. */ public File getBaseDir() { @@ -84,7 +84,7 @@ public File getBaseDir() { /** * Finds the build directory (e.g. /target) of the Maven or Gradle * project on which spotless is currently being executed. - * + * * @return The project build directory of the current Maven or Gradle project. */ public File getBuildDir() { @@ -95,7 +95,7 @@ public File getBuildDir() { * Finds the data directory that can be used for storing shared data such as * downloaded files globally. This is a directory in the local repository, e.g. * ~/.m2/repository/com/diffplus/spotless/spotless-data. - * + * * @return The directory for storing shared data. */ public File getDataDir() { diff --git a/plugin-maven/src/main/java/com/diffplug/spotless/maven/generic/Format.java b/plugin-maven/src/main/java/com/diffplug/spotless/maven/generic/Format.java index 6cf843255a..ed08718c65 100644 --- a/plugin-maven/src/main/java/com/diffplug/spotless/maven/generic/Format.java +++ b/plugin-maven/src/main/java/com/diffplug/spotless/maven/generic/Format.java @@ -40,7 +40,7 @@ public String licenseHeaderDelimiter() { // do not specify a default delimiter return null; } - + public void addRome(Rome rome) { addStepFactory(rome); } diff --git a/plugin-maven/src/main/java/com/diffplug/spotless/maven/generic/Rome.java b/plugin-maven/src/main/java/com/diffplug/spotless/maven/generic/Rome.java index 29b290a746..62d9d3fdec 100644 --- a/plugin-maven/src/main/java/com/diffplug/spotless/maven/generic/Rome.java +++ b/plugin-maven/src/main/java/com/diffplug/spotless/maven/generic/Rome.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2022 DiffPlug + * Copyright 2016-2023 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -42,7 +42,7 @@ public class Rome extends AbstractRome { *
  • json (JSON)
  • * * - * + * * @return The language of the input files. */ @Parameter diff --git a/plugin-maven/src/main/java/com/diffplug/spotless/maven/javascript/RomeJs.java b/plugin-maven/src/main/java/com/diffplug/spotless/maven/javascript/RomeJs.java index cf40f53f47..60fb7077df 100644 --- a/plugin-maven/src/main/java/com/diffplug/spotless/maven/javascript/RomeJs.java +++ b/plugin-maven/src/main/java/com/diffplug/spotless/maven/javascript/RomeJs.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2022 DiffPlug + * Copyright 2016-2023 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/plugin-maven/src/main/java/com/diffplug/spotless/maven/json/RomeJson.java b/plugin-maven/src/main/java/com/diffplug/spotless/maven/json/RomeJson.java index 5b2eec6abb..1cd044b759 100644 --- a/plugin-maven/src/main/java/com/diffplug/spotless/maven/json/RomeJson.java +++ b/plugin-maven/src/main/java/com/diffplug/spotless/maven/json/RomeJson.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2022 DiffPlug + * Copyright 2016-2023 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/plugin-maven/src/main/java/com/diffplug/spotless/maven/rome/AbstractRome.java b/plugin-maven/src/main/java/com/diffplug/spotless/maven/rome/AbstractRome.java index ccf253d770..8af56c5a00 100644 --- a/plugin-maven/src/main/java/com/diffplug/spotless/maven/rome/AbstractRome.java +++ b/plugin-maven/src/main/java/com/diffplug/spotless/maven/rome/AbstractRome.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2022 DiffPlug + * Copyright 2016-2023 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -113,7 +113,7 @@ public FormatterStep newFormatterStep(FormatterStepConfig config) { * extension) *
  • json (JSON)
  • * - * + * * @return The language of the input files. */ protected abstract String getLanguage(); @@ -122,7 +122,7 @@ public FormatterStep newFormatterStep(FormatterStepConfig config) { * A new builder for configuring a Rome step that either downloads the Rome * executable with the given version from the network, or uses the executable * from the given path. - * + * * @param config Configuration from the Maven Mojo execution with details about * the currently executed project. * @return A builder for a Rome step. @@ -140,7 +140,7 @@ private Builder newBuilder(FormatterStepConfig config) { /** * 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. @@ -154,7 +154,7 @@ private String resolveConfigFile(FormatterStepConfig config) { * do not perform any resolution and interpret it as a command that must be on * the user's path. Otherwise resolve the executable path 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 Rome executable. @@ -173,7 +173,7 @@ private String resolveExePath(FormatterStepConfig config) { * {@link #downloadDir} is given, use that directory, resolved against the * current project's directory. Otherwise, use the {@code Rome} sub folder in * the shared data directory. - * + * * @param config Configuration for this step. * @return The download directory for the Rome executable. */ diff --git a/plugin-maven/src/main/java/com/diffplug/spotless/maven/typescript/RomeTs.java b/plugin-maven/src/main/java/com/diffplug/spotless/maven/typescript/RomeTs.java index 21f182079b..ccee83744a 100644 --- a/plugin-maven/src/main/java/com/diffplug/spotless/maven/typescript/RomeTs.java +++ b/plugin-maven/src/main/java/com/diffplug/spotless/maven/typescript/RomeTs.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2022 DiffPlug + * Copyright 2016-2023 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. From 0afd327dd543cc343d8228877cc738a7d854e26f Mon Sep 17 00:00:00 2001 From: Andre Wachsmuth Date: Sat, 8 Apr 2023 17:51:11 +0100 Subject: [PATCH 13/33] Add rome format step to json/javascript/typescript languages --- .../gradle/spotless/FormatExtension.java | 6 +-- .../gradle/spotless/JavascriptExtension.java | 42 +++++++++++++++++++ .../gradle/spotless/JsonExtension.java | 41 ++++++++++++++++++ .../gradle/spotless/TypescriptExtension.java | 41 ++++++++++++++++++ 4 files changed, 127 insertions(+), 3 deletions(-) diff --git a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/FormatExtension.java b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/FormatExtension.java index c3c366f3ea..dbf45e87f1 100644 --- a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/FormatExtension.java +++ b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/FormatExtension.java @@ -232,7 +232,7 @@ protected final FileCollection parseTarget(Object target) { private final FileCollection parseTargetIsExclude(Object target, boolean isExclude) { if (target instanceof Collection) { - return parseTargetsIsExclude(((Collection) target).toArray(), isExclude); + return parseTargetsIsExclude(((Collection) target).toArray(), isExclude); } else if (target instanceof FileCollection) { return (FileCollection) target; } else if (target instanceof String) { @@ -963,12 +963,12 @@ public PrettierConfig prettier(Map devDependencies) { * offline, you can specify the path to the Rome executable via * {@code rome().pathToExe(...)}. */ - public RomeGeneric rome() { + public RomeStepConfig rome() { return rome(null); } /** Downloads the given Rome version from the network. */ - public RomeGeneric rome(String version) { + public RomeStepConfig rome(String version) { var romeConfig = new RomeGeneric(version); addStep(romeConfig.createStep()); return romeConfig; diff --git a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/JavascriptExtension.java b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/JavascriptExtension.java index e8a76166bc..76ad5650ca 100644 --- a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/JavascriptExtension.java +++ b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/JavascriptExtension.java @@ -138,9 +138,51 @@ public PrettierConfig prettier(Map devDependencies) { return prettierConfig; } + /** + * Defaults to downloading the default Rome version from the network. To work + * offline, you can specify the path to the Rome executable via + * {@code rome().pathToExe(...)}. + */ + public RomeJs rome() { + return rome(null); + } + + /** Downloads the given Rome version from the network. */ + public RomeJs rome(String version) { + var romeConfig = new RomeJs(version); + addStep(romeConfig.createStep()); + return romeConfig; + } + private static final String DEFAULT_PRETTIER_JS_PARSER = "babel"; private static final ImmutableList PRETTIER_JS_PARSERS = ImmutableList.of(DEFAULT_PRETTIER_JS_PARSER, "babel-flow", "flow"); + /** + * Rome formatter step for JavaScript. + */ + public class RomeJs extends RomeStepConfig { + + /** + * Creates a new Rome formatter step config for formatting JavaScript files. Unless + * overwritten, the given Rome version is downloaded from the network. + * + * @param version Rome version to use. + */ + public RomeJs(String version) { + super(getProject(), JavascriptExtension.this::replaceStep, version); + } + + @Override + protected String getLanguage() { + return "js?"; + } + + @Override + protected RomeJs getThis() { + return this; + } + } + /** * Overrides the parser to be set to a js parser. */ diff --git a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/JsonExtension.java b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/JsonExtension.java index 39b158ce1e..510ac529e8 100644 --- a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/JsonExtension.java +++ b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/JsonExtension.java @@ -55,6 +55,22 @@ public JacksonJsonGradleConfig jackson() { return new JacksonJsonGradleConfig(this); } + /** + * Defaults to downloading the default Rome version from the network. To work + * offline, you can specify the path to the Rome executable via + * {@code rome().pathToExe(...)}. + */ + public RomeJson rome() { + return rome(null); + } + + /** Downloads the given Rome version from the network. */ + public RomeJson rome(String version) { + var romeConfig = new RomeJson(version); + addStep(romeConfig.createStep()); + return romeConfig; + } + public class SimpleConfig { private int indent; @@ -145,4 +161,29 @@ protected final FormatterStep createStep() { return JacksonJsonStep.create(jacksonConfig, version, formatExtension.provisioner()); } } + + /** + * Rome formatter step for JSON. + */ + public class RomeJson extends RomeStepConfig { + /** + * Creates a new Rome formatter step config for formatting JSON files. Unless + * overwritten, the given Rome version is downloaded from the network. + * + * @param version Rome version to use. + */ + public RomeJson(String version) { + super(getProject(), JsonExtension.this::replaceStep, version); + } + + @Override + protected String getLanguage() { + return "json"; + } + + @Override + protected RomeJson getThis() { + return this; + } + } } diff --git a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/TypescriptExtension.java b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/TypescriptExtension.java index 9f1d04abfd..ddb7fbfc10 100644 --- a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/TypescriptExtension.java +++ b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/TypescriptExtension.java @@ -229,6 +229,47 @@ protected EslintConfig eslintConfig() { } } + /** + * Defaults to downloading the default Rome version from the network. To work + * offline, you can specify the path to the Rome executable via + * {@code rome().pathToExe(...)}. + */ + public RomeTs rome() { + return rome(null); + } + + /** Downloads the given Rome version from the network. */ + public RomeTs rome(String version) { + var romeConfig = new RomeTs(version); + addStep(romeConfig.createStep()); + return romeConfig; + } + + /** + * Rome formatter step for TypeScript. + */ + public class RomeTs extends RomeStepConfig { + /** + * Creates a new Rome formatter step config for formatting TypeScript files. Unless + * overwritten, the given Rome version is downloaded from the network. + * + * @param version Rome version to use. + */ + public RomeTs(String version) { + super(getProject(), TypescriptExtension.this::replaceStep, version); + } + + @Override + protected String getLanguage() { + return "ts?"; + } + + @Override + protected RomeTs getThis() { + return this; + } + } + @Override protected void setupTask(SpotlessTask task) { if (target == null) { From 8c71500b00aa0658ff92ca8b66601ea64649d31d Mon Sep 17 00:00:00 2001 From: Andre Wachsmuth Date: Sat, 8 Apr 2023 19:06:27 +0100 Subject: [PATCH 14/33] Add docs for Maven plugin --- plugin-maven/README.md | 151 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 151 insertions(+) diff --git a/plugin-maven/README.md b/plugin-maven/README.md index b4d7393e21..b4c15f7fc2 100644 --- a/plugin-maven/README.md +++ b/plugin-maven/README.md @@ -57,6 +57,7 @@ user@machine repo % mvn spotless:check - Multiple languages - [Prettier](#prettier) ([plugins](#prettier-plugins), [npm detection](#npm-detection), [`.npmrc` detection](#npmrc-detection), [caching `npm install` results](#caching-results-of-npm-install)) - [eclipse web tools platform](#eclipse-web-tools-platform) + - [Rome](#rome) ([binary detection](#rome-binary), [config file](#rome-configuration-file), [input language](#rome-input-language)) - **Language independent** - [Generic steps](#generic-steps) - [License header](#license-header) ([slurp year from git](#retroactively-slurp-years-from-git-history)) @@ -704,6 +705,7 @@ Currently, none of the available options can be configured yet. It uses only the + /* (C)$YEAR */ @@ -814,6 +816,7 @@ For details, see the [npm detection](#npm-detection), [`.npmrc` detection](#npmr + /* (C)$YEAR */ @@ -890,6 +893,7 @@ For details, see the [npm detection](#npm-detection), [`.npmrc` detection](#npmr + ``` @@ -1220,6 +1224,153 @@ to true. +## Rome + +[homepage](https://rome.tools/). [changelog](https://github.com/rome/tools/blob/main/CHANGELOG.md). Rome is a formatter that for the Frontend written in Rust, which has a native binary, +does not require Node.js and as such, is pretty fast. It can currently format +JavaScript, TypeScript, JSX, and JSON, and may support +[more frontend languages](https://docs.rome.tools/internals/language_support/) +such as CSS in the future. + +You can use rome in any language-specific format for supported languages, but +usually you will be creating a generic format. + +```xml + + + + + src/**/typescript/**/*.ts + + + + + 12.0.0 + + + ${project.basedir}/path/to/config/dir + + + + ts + + + + + +``` + +**Limitations:** +- The auto-discovery of config files (up the file tree) will not work when using + Rome within spotless. + +To apply Rome to more kinds of files, just add more formats + +```xml + + + src/**/*.ts + src/**/*.js +``` + +### Rome binary + +To format with Rome, spotless needs to find the Rome binary. By default, +spotless downloads the binary for the given version from the network. This +should be fine in most cases, but may not work e.g. when there is not connection +to the internet. + +To download the Rome binary from the network, just specify a version: + +```xml + + 12.0.0 + +``` + +Spotless uses a default version when you do not specfiy a version, but this +may change at any time, so we recommend that you always set the Rome version +you want to use. Optionally, you can also specify a directory for the downloaded +Rome binaries (defaults to `~/.m2/repository/com/diffplug/spotless/spotless-data/rome`): + +```xml + + 12.0.0 + + ${user.home}/rome + +``` + +To use a fixed binary, omit the `version` and specify a `pathToExe`: + +```xml + + ${project.basedir}/bin/rome + +``` + +Absolute paths are used as-is. Relative paths are resolved against the project's +base directory. To use a pre-installed Rome binary on the user's path, specify +just a name without any slashes / backslashes: + + +```xml + + + rome + +``` + +### Rome configuration file + +Rome is a biased formatter and linter without many options, but there are a few +basic options. Rome uses a file named [rome.json](https://docs.rome.tools/configuration/) +for its configuration. When none is specified, the default configuration from +Rome is used. To use a custom configuration: + +```xml + + + + ${project.basedir} + +``` + +### Rome input Language + +By default, Rome detects the language / syntax of the files to format +automatically from the file extension. This may fail if your source code files +have unusual extensions for some reason. If you are using the generic format, +you can force a certain language like this: + +```xml + + + + + src/**/typescript/**/*.mjson + + + + 12.0.0 + json + + + + + +``` + +The following languages are currently recognized: + +* `js` -- JavaScript +* `jsx` -- JavaScript + JSX (React) +* `js?` -- JavaScript, with or without JSX, depending on the file extension +* `ts` -- TypeScript +* `tsx` -- TypeScript + JSX (React) +* `ts?` -- TypeScript, with or without JSX, depending on the file extension +* `json` -- JSON + ## Generic steps [Prettier](#prettier), [eclipse wtp](#eclipse-web-tools-platform), and [license header](#license-header) are available in every format, and they each have their own section. As mentioned in the [quickstart](#quickstart), there are a variety of simple generic steps which are also available in every format, here are examples of these: From 8fa9cdadfaf487ebba83d88fc522b6822433c2f0 Mon Sep 17 00:00:00 2001 From: Andre Wachsmuth Date: Sat, 8 Apr 2023 19:30:16 +0100 Subject: [PATCH 15/33] Add docs for Gradle plugin --- plugin-gradle/README.md | 173 +++++++++++++++++++++++++++++++++++++++- plugin-maven/README.md | 8 +- 2 files changed, 176 insertions(+), 5 deletions(-) diff --git a/plugin-gradle/README.md b/plugin-gradle/README.md index cea45a049f..f47c7a543f 100644 --- a/plugin-gradle/README.md +++ b/plugin-gradle/README.md @@ -75,6 +75,7 @@ Spotless supports all of Gradle's built-in performance features (incremental bui - c, c++, c#, objective-c, protobuf, javascript, java - [eclipse web tools platform](#eclipse-web-tools-platform) - css, html, js, json, xml + - [Rome](#rome) ([binary detection](#rome-binary), [config file](#rome-configuration-file), [input language](#rome-input-language)) - **Language independent** - [Generic steps](#generic-steps) - [License header](#license-header) ([slurp year from git](#retroactively-slurp-years-from-git-history)) @@ -613,7 +614,8 @@ spotless { tsfmt() // has its own section below prettier() // has its own section below - eslint() // has its own section below + eslint() // has its own section below + rome() // has its own section below licenseHeader '/* (C) $YEAR */', '(import|const|declare|export|var) ' // or licenseHeaderFile // note the '(import|const|...' argument - this is a regex which identifies the top @@ -705,7 +707,8 @@ spotless { target 'src/**/*.js' // you have to set the target manually prettier() // has its own section below - eslint() // has its own section below + eslint() // has its own section below + rome() // has its own section below licenseHeader '/* (C) $YEAR */', 'REGEX_TO_DEFINE_TOP_OF_FILE' // or licenseHeaderFile } @@ -772,6 +775,7 @@ spotless { eclipseWtp('json') // see Eclipse web tools platform section gson() // has its own section below jackson() // has its own section below + rome() // has its own section below } } ``` @@ -1064,6 +1068,171 @@ Unlike Eclipse, Spotless WTP ignores per default external URIs in schema locatio external entities. To allow the access of external URIs, set the property `resolveExternalURI` to true. +## Rome + +[homepage](https://rome.tools/). [changelog](https://github.com/rome/tools/blob/main/CHANGELOG.md). Rome is a formatter that for the Frontend written in Rust, which has a native binary, +does not require Node.js and as such, is pretty fast. It can currently format +JavaScript, TypeScript, JSX, and JSON, and may support +[more frontend languages](https://docs.rome.tools/internals/language_support/) +such as CSS in the future. + +You can use rome in any language-specific format for supported languages, but +usually you will be creating a generic format. + +```gradle +spotless { + format 'styling', { + // you have to set the target manually + target 'src/*/webapp/**/*.js' + + // Download Rome from the network if not already downloaded, see below for more info + rome('12.0.0') + + // (optional) Path to the directory with the rome.json conig file + rome('12.0.0').configPath("path/config/dir") + + // (optional) Rome will auto detect the language based on the file extension. + // See below for possible values. + rome('12.0.0').language("js") + } +} +``` + +**Limitations:** +- The auto-discovery of config files (up the file tree) will not work when using + Rome within spotless. + +To apply Rome to more kinds of files with a different configuration, just add +more formats: + +```gradle +spotless { + format 'rome-js', { + target '**/*.js' + rome('12.0.0') + } + format 'rome-ts', { + target '**/*.ts' + rome('12.0.0') + } + format 'rome-json', { + target '**/*.json' + rome('12.0.0') + } +} +``` + +### Rome binary + +To format with Rome, spotless needs to find the Rome binary. By default, +spotless downloads the binary for the given version from the network. This +should be fine in most cases, but may not work e.g. when there is not connection +to the internet. + +To download the Rome binary from the network, just specify a version: + +```gradle +spotless { + format 'rome', { + target '**/*.js','**/*.ts','**/*.json' + rome('12.0.0') + } +} +``` + +Spotless uses a default version when you do not specfiy a version, but this +may change at any time, so we recommend that you always set the Rome version +you want to use. Optionally, you can also specify a directory for the downloaded +Rome binaries (defaults to `~/.m2/repository/com/diffplug/spotless/spotless-data/rome`): + +```gradle +spotless { + format 'rome', { + target '**/*.js','**/*.ts','**/*.json' + // Relative paths are resolved against the project's base directory + rome('12.0.0').downloadDir("${project.gradle.gradleUserHomeDir}/rome") + } +} +``` + +To use a fixed binary, omit the `version` and specify a `pathToExe`: + +```gradle +spotless { + format 'rome', { + target '**/*.js','**/*.ts','**/*.json' + rome('12.0.0').pathToExe("${project.buildDir.absolutePath}/bin/rome") + } +} +``` + +Absolute paths are used as-is. Relative paths are resolved against the project's +base directory. To use a pre-installed Rome binary on the user's path, specify +just a name without any slashes / backslashes: + +```gradle +spotless { + format 'rome', { + target '**/*.js','**/*.ts','**/*.json' + // Uses the "rome" command, which must be on the user's path. --> + rome('12.0.0').pathToExe('rome') + } +} +``` + +### Rome configuration file + +Rome is a biased formatter and linter without many options, but there are a few +basic options. Rome uses a file named [rome.json](https://docs.rome.tools/configuration/) +for its configuration. When none is specified, the default configuration from +Rome is used. To use a custom configuration: + +```gradle +spotless { + format 'rome', { + target '**/*.js','**/*.ts','**/*.json' + // Must point to the directory with the "rome.json" config file --> + // Relative paths are resolved against the project's base directory --> + rome('12.0.0').configPath('./config') + } +} +``` + +### Rome input language + +By default, Rome detects the language / syntax of the files to format +automatically from the file extension. This may fail if your source code files +have unusual extensions for some reason. If you are using the generic format, +you can force a certain language like this: + +```xml + + + + + src/**/typescript/**/*.mjson + + + + 12.0.0 + json + + + + + +``` + +The following languages are currently recognized: + +* `js` -- JavaScript +* `jsx` -- JavaScript + JSX (React) +* `js?` -- JavaScript, with or without JSX, depending on the file extension +* `ts` -- TypeScript +* `tsx` -- TypeScript + JSX (React) +* `ts?` -- TypeScript, with or without JSX, depending on the file extension +* `json` -- JSON + ## Generic steps [Prettier](#prettier), [eclipse wtp](#eclipse-web-tools-platform), and [license header](#license-header) are available in every format, and they each have their own section. As mentioned in the [quickstart](#quickstart), there are a variety of simple generic steps which are also available in every format, here are examples of these: diff --git a/plugin-maven/README.md b/plugin-maven/README.md index b4c15f7fc2..03638278f5 100644 --- a/plugin-maven/README.md +++ b/plugin-maven/README.md @@ -1250,7 +1250,7 @@ usually you will be creating a generic format. ${project.basedir}/path/to/config/dir - + ts @@ -1264,13 +1264,15 @@ usually you will be creating a generic format. - The auto-discovery of config files (up the file tree) will not work when using Rome within spotless. -To apply Rome to more kinds of files, just add more formats +To apply Rome to more kinds of files with a different configuration, just add +more formats ```xml src/**/*.ts src/**/*.js + ``` ### Rome binary @@ -1336,7 +1338,7 @@ Rome is used. To use a custom configuration: ``` -### Rome input Language +### Rome input language By default, Rome detects the language / syntax of the files to format automatically from the file extension. This may fail if your source code files From 23308740d17e28c03fa1e5902ff25a3810e7831b Mon Sep 17 00:00:00 2001 From: Andre Wachsmuth Date: Sat, 8 Apr 2023 19:32:51 +0100 Subject: [PATCH 16/33] Readme: Mention Rome in TOC for JS/TS --- plugin-gradle/README.md | 4 ++-- plugin-maven/README.md | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/plugin-gradle/README.md b/plugin-gradle/README.md index f47c7a543f..de0f9e5f31 100644 --- a/plugin-gradle/README.md +++ b/plugin-gradle/README.md @@ -63,8 +63,8 @@ Spotless supports all of Gradle's built-in performance features (incremental bui - [FreshMark](#freshmark) aka markdown - [Antlr4](#antlr4) ([antlr4formatter](#antlr4formatter)) - [SQL](#sql) ([dbeaver](#dbeaver), [prettier](#prettier)) - - [Typescript](#typescript) ([tsfmt](#tsfmt), [prettier](#prettier), [ESLint](#eslint-typescript)) - - [Javascript](#javascript) ([prettier](#prettier), [ESLint](#eslint-javascript)) + - [Typescript](#typescript) ([tsfmt](#tsfmt), [prettier](#prettier), [ESLint](#eslint-typescript), [Rome](#rome)) + - [Javascript](#javascript) ([prettier](#prettier), [ESLint](#eslint-javascript), [Rome](#rome)) - [JSON](#json) - [YAML](#yaml) - [Gherkin](#gherkin) diff --git a/plugin-maven/README.md b/plugin-maven/README.md index 03638278f5..0d7ddeaf4f 100644 --- a/plugin-maven/README.md +++ b/plugin-maven/README.md @@ -49,8 +49,8 @@ user@machine repo % mvn spotless:check - [Sql](#sql) ([dbeaver](#dbeaver)) - [Maven Pom](#maven-pom) ([sortPom](#sortpom)) - [Markdown](#markdown) ([flexmark](#flexmark)) - - [Typescript](#typescript) ([tsfmt](#tsfmt), [prettier](#prettier), [ESLint](#eslint-typescript)) - - [Javascript](#javascript) ([prettier](#prettier), [ESLint](#eslint-javascript)) + - [Typescript](#typescript) ([tsfmt](#tsfmt), [prettier](#prettier), [ESLint](#eslint-typescript), [Rome](#rome)) + - [Javascript](#javascript) ([prettier](#prettier), [ESLint](#eslint-javascript), [Rome](#rome)) - [JSON](#json) - [YAML](#yaml) - [Gherkin](#gherkin) From 850b2877342930354035ce1f4cbb5d103ceb274d Mon Sep 17 00:00:00 2001 From: Andre Wachsmuth Date: Sat, 8 Apr 2023 19:35:59 +0100 Subject: [PATCH 17/33] Fix readme for Gradle plugin with custom binary --- plugin-gradle/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugin-gradle/README.md b/plugin-gradle/README.md index de0f9e5f31..3e409677a2 100644 --- a/plugin-gradle/README.md +++ b/plugin-gradle/README.md @@ -1161,7 +1161,7 @@ To use a fixed binary, omit the `version` and specify a `pathToExe`: spotless { format 'rome', { target '**/*.js','**/*.ts','**/*.json' - rome('12.0.0').pathToExe("${project.buildDir.absolutePath}/bin/rome") + rome().pathToExe("${project.buildDir.absolutePath}/bin/rome") } } ``` @@ -1175,7 +1175,7 @@ spotless { format 'rome', { target '**/*.js','**/*.ts','**/*.json' // Uses the "rome" command, which must be on the user's path. --> - rome('12.0.0').pathToExe('rome') + rome().pathToExe('rome') } } ``` From 7afe75b999002efed6e245ced92c02cc7326a130 Mon Sep 17 00:00:00 2001 From: Andre Wachsmuth Date: Sat, 8 Apr 2023 19:58:17 +0100 Subject: [PATCH 18/33] Minor refactor, remove useless extra builder class --- .../com/diffplug/spotless/rome/RomeStep.java | 142 ++++++------------ .../gradle/spotless/FormatExtension.java | 5 +- .../spotless/maven/rome/AbstractRome.java | 6 +- 3 files changed, 52 insertions(+), 101 deletions(-) diff --git a/lib/src/main/java/com/diffplug/spotless/rome/RomeStep.java b/lib/src/main/java/com/diffplug/spotless/rome/RomeStep.java index e48a78c2ba..b0f17fafd6 100644 --- a/lib/src/main/java/com/diffplug/spotless/rome/RomeStep.java +++ b/lib/src/main/java/com/diffplug/spotless/rome/RomeStep.java @@ -50,7 +50,7 @@ public class RomeStep { * Path to the directory with the {@code rome.json} config file, can be * null, in which case the defaults are used. */ - private final String configPath; + private String configPath; /** * The language (syntax) of the input files to format. When null or @@ -68,7 +68,7 @@ public class RomeStep { *
  • json (JSON)
  • * */ - private final String language; + private String language; /** * Path to the Rome executable. Can be null, but either a path to @@ -106,8 +106,8 @@ public static String name() { * @param downloadDir Directory where to place the downloaded executable. * @return A new Rome step that download the executable from the network. */ - public static RomeStep.Builder withExeDownload(String version, String downloadDir) { - return new RomeStep.Builder(version, null, downloadDir); + public static RomeStep withExeDownload(String version, String downloadDir) { + return new RomeStep(version, null, downloadDir); } /** @@ -117,8 +117,8 @@ public static RomeStep.Builder withExeDownload(String version, String downloadDi * @param pathToExe Path to the Rome executable to use. * @return A new Rome step that format with the given executable. */ - public static RomeStep.Builder withExePath(String pathToExe) { - return new RomeStep.Builder(null, pathToExe, null); + public static RomeStep withExePath(String pathToExe) { + return new RomeStep(null, pathToExe, null); } /** @@ -217,12 +217,10 @@ private static void validateRomeExecutable(String resolvedPathToExe) { * * @param builder Builder with the configuration to use. */ - private RomeStep(RomeStep.Builder builder) { - this.version = builder.version != null && !builder.version.isBlank() ? builder.version : defaultVersion(); - this.pathToExe = builder.pathToExe; - this.downloadDir = builder.downloadDir; - this.configPath = builder.configPath; - this.language = builder.language; + private RomeStep(String version, String pathToExe, String downloadDir) { + this.version = version != null && !version.isBlank() ? version : defaultVersion(); + this.pathToExe = pathToExe; + this.downloadDir = downloadDir; } /** @@ -235,6 +233,44 @@ public FormatterStep create() { return FormatterStep.createLazy(name(), this::createState, State::toFunc); } + /** + * Sets the path to the directory with the {@code rome.json} config file. When + * no config path is set, the default configuration is used. + * + * @param configPath Config path to use. Must point to a directory which contain + * a file named {@code rome.json}. + * @return This builder instance for chaining method calls. + */ + public RomeStep withConfigPath(String configPath) { + this.configPath = configPath; + return this; + } + + /** + * Sets the language of the files to format When no language is set, it is + * determined automatically from the file name. The following languages are + * currently supported by Rome. + * + *
      + *
    • js (JavaScript)
    • + *
    • jsx (JavaScript + JSX)
    • + *
    • js? (JavaScript or JavaScript + JSX, depending on the file + * extension)
    • + *
    • ts (TypeScript)
    • + *
    • tsx (TypeScript + JSX)
    • + *
    • ts? (TypeScript or TypeScript + JSX, depending on the file + * extension)
    • + *
    • json (JSON)
    • + *
    + * + * @param language The language of the files to format. + * @return This builder instance for chaining method calls. + */ + public RomeStep withLanguage(String language) { + this.language = language; + return this; + } + /** * Resolves the Rome executable, possibly downloading it from the network, and * creates a new state instance with the resolved executable that can format @@ -290,88 +326,6 @@ private String resolveExe() throws IOException, InterruptedException { } } - public final static class Builder { - /** See {@link RomeStep#configPath} */ - private String configPath; - - /** See {@link RomeStep#downloadDir} */ - private final String downloadDir; - - /** See {@link RomeStep#language} */ - private String language; - - /** See {@link RomeStep#pathToExe} */ - private final String pathToExe; - - /** See {@link RomeStep#version} */ - private final String version; - - /** - * Creates a new builder for configuring a Rome step that can format code via - * Rome. Either a version and and downloadDir, or a pathToExe must be given. - * - * @param version The version of Rome to download, see - * {@link RomeStep#version}. - * @param pathToExe The path to the Rome executable to use, see - * {@link RomeStep#pathToExe}. - * @param downloadDir The path to the download directory when downloading Rome - * from the network, {@link RomeStep#downloadDir}. - */ - private Builder(String version, String pathToExe, String downloadDir) { - this.version = version; - this.pathToExe = pathToExe; - this.downloadDir = downloadDir; - } - - /** - * Creates a new Rome step for formatting code with Rome from the current - * configuration of this builder. - * - * @return A new Rome step with the current configuration. - */ - public RomeStep build() { - return new RomeStep(this); - } - - /** - * Sets the path to the directory with the {@code rome.json} config file. When - * no config path is set, the default configuration is used. - * - * @param configPath Config path to use. Must point to a directory which contain - * a file named {@code rome.json}. - * @return This builder instance for chaining method calls. - */ - public Builder withConfigPath(String configPath) { - this.configPath = configPath; - return this; - } - - /** - * Sets the language of the files to format When no language is set, it is - * determined automatically from the file name. The following languages are - * currently supported by Rome. - * - *
      - *
    • js (JavaScript)
    • - *
    • jsx (JavaScript + JSX)
    • - *
    • js? (JavaScript or JavaScript + JSX, depending on the file - * extension)
    • - *
    • ts (TypeScript)
    • - *
    • tsx (TypeScript + JSX)
    • - *
    • ts? (TypeScript or TypeScript + JSX, depending on the file - * extension)
    • - *
    • json (JSON)
    • - *
    - * - * @param language The language of the files to format. - * @return This builder instance for chaining method calls. - */ - public Builder withLanguage(String language) { - this.language = language; - return this; - } - } - /** * The internal state used by the Rome formatter. A state instance is created * when the spotless plugin for Maven or Gradle is executed, and reused for all diff --git a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/FormatExtension.java b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/FormatExtension.java index dbf45e87f1..ae7e792e9b 100644 --- a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/FormatExtension.java +++ b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/FormatExtension.java @@ -770,8 +770,7 @@ protected FormatterStep createStep() { builder.withConfigPath(resolvedConfigPath.toString()); } builder.withLanguage(getLanguage()); - var rome = builder.build(); - return rome.create(); + return builder.create(); } /** @@ -842,7 +841,7 @@ private File findDataDir() { * * @return A builder for a Rome step. */ - private RomeStep.Builder newBuilder() { + private RomeStep newBuilder() { if (pathToExe != null) { var resolvedPathToExe = resolvePathToExe(); return RomeStep.withExePath(resolvedPathToExe); diff --git a/plugin-maven/src/main/java/com/diffplug/spotless/maven/rome/AbstractRome.java b/plugin-maven/src/main/java/com/diffplug/spotless/maven/rome/AbstractRome.java index 8af56c5a00..33567b0a20 100644 --- a/plugin-maven/src/main/java/com/diffplug/spotless/maven/rome/AbstractRome.java +++ b/plugin-maven/src/main/java/com/diffplug/spotless/maven/rome/AbstractRome.java @@ -23,7 +23,6 @@ import com.diffplug.spotless.maven.FormatterStepConfig; import com.diffplug.spotless.maven.FormatterStepFactory; import com.diffplug.spotless.rome.RomeStep; -import com.diffplug.spotless.rome.RomeStep.Builder; /** * Factory for creating the Rome formatter step that can format format code in @@ -94,8 +93,7 @@ public FormatterStep newFormatterStep(FormatterStepConfig config) { if (getLanguage() != null) { builder.withLanguage(getLanguage()); } - var step = builder.build(); - return step.create(); + return builder.create(); } /** @@ -127,7 +125,7 @@ public FormatterStep newFormatterStep(FormatterStepConfig config) { * the currently executed project. * @return A builder for a Rome step. */ - private Builder newBuilder(FormatterStepConfig config) { + private RomeStep newBuilder(FormatterStepConfig config) { if (pathToExe != null) { var resolvedExePath = resolveExePath(config); return RomeStep.withExePath(resolvedExePath); From 57d36b4a4d7a2dce664b1f7c518c2d53d3e1697e Mon Sep 17 00:00:00 2001 From: Andre Wachsmuth Date: Sat, 8 Apr 2023 19:58:39 +0100 Subject: [PATCH 19/33] Fix default download dir path for Gradle plugin --- .../main/java/com/diffplug/gradle/spotless/FormatExtension.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/FormatExtension.java b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/FormatExtension.java index ae7e792e9b..c429c469d2 100644 --- a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/FormatExtension.java +++ b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/FormatExtension.java @@ -824,7 +824,7 @@ private File findDataDir() { try { // e.g. ~/.m2/repository/ var repoPath = Paths.get(localRepo.getUrl().getPath()); - var dataPath = repoPath.resolve("com").resolve("diffplus").resolve("spotless").resolve("spotless-data"); + var dataPath = repoPath.resolve("com").resolve("diffplug").resolve("spotless").resolve("spotless-data"); return dataPath.toAbsolutePath().toFile(); } finally { // Remove mavenLocal() repository again if it was not part of the project From c483ed37e2e8bfdfe26c084a79801570600595cd Mon Sep 17 00:00:00 2001 From: Andre Wachsmuth Date: Sat, 8 Apr 2023 21:24:29 +0100 Subject: [PATCH 20/33] Add tests for RomeStep --- .../resources/rome/config/line-width-120.json | 11 + .../resources/rome/config/line-width-80.json | 11 + .../src/main/resources/rome/js/fileAfter.cjs | 3 + .../src/main/resources/rome/js/fileAfter.js | 3 + .../src/main/resources/rome/js/fileAfter.jsx | 3 + .../src/main/resources/rome/js/fileAfter.mjs | 3 + .../src/main/resources/rome/js/fileBefore.cjs | 3 + .../src/main/resources/rome/js/fileBefore.js | 3 + .../src/main/resources/rome/js/fileBefore.jsx | 4 + .../src/main/resources/rome/js/fileBefore.mjs | 3 + .../resources/rome/js/longLineAfter120.js | 1 + .../main/resources/rome/js/longLineAfter80.js | 13 + .../main/resources/rome/js/longLineBefore.js | 1 + .../main/resources/rome/json/fileAfter.json | 5 + .../main/resources/rome/json/fileBefore.json | 7 + .../src/main/resources/rome/ts/fileAfter.cts | 4 + .../src/main/resources/rome/ts/fileAfter.mts | 4 + .../src/main/resources/rome/ts/fileAfter.ts | 4 + .../src/main/resources/rome/ts/fileAfter.tsx | 7 + .../src/main/resources/rome/ts/fileBefore.cts | 4 + .../src/main/resources/rome/ts/fileBefore.mts | 5 + .../src/main/resources/rome/ts/fileBefore.ts | 5 + .../src/main/resources/rome/ts/fileBefore.tsx | 8 + .../diffplug/spotless/rome/RomeStepTest.java | 284 ++++++++++++++++++ 24 files changed, 399 insertions(+) create mode 100644 testlib/src/main/resources/rome/config/line-width-120.json create mode 100644 testlib/src/main/resources/rome/config/line-width-80.json create mode 100644 testlib/src/main/resources/rome/js/fileAfter.cjs create mode 100644 testlib/src/main/resources/rome/js/fileAfter.js create mode 100644 testlib/src/main/resources/rome/js/fileAfter.jsx create mode 100644 testlib/src/main/resources/rome/js/fileAfter.mjs create mode 100644 testlib/src/main/resources/rome/js/fileBefore.cjs create mode 100644 testlib/src/main/resources/rome/js/fileBefore.js create mode 100644 testlib/src/main/resources/rome/js/fileBefore.jsx create mode 100644 testlib/src/main/resources/rome/js/fileBefore.mjs create mode 100644 testlib/src/main/resources/rome/js/longLineAfter120.js create mode 100644 testlib/src/main/resources/rome/js/longLineAfter80.js create mode 100644 testlib/src/main/resources/rome/js/longLineBefore.js create mode 100644 testlib/src/main/resources/rome/json/fileAfter.json create mode 100644 testlib/src/main/resources/rome/json/fileBefore.json create mode 100644 testlib/src/main/resources/rome/ts/fileAfter.cts create mode 100644 testlib/src/main/resources/rome/ts/fileAfter.mts create mode 100644 testlib/src/main/resources/rome/ts/fileAfter.ts create mode 100644 testlib/src/main/resources/rome/ts/fileAfter.tsx create mode 100644 testlib/src/main/resources/rome/ts/fileBefore.cts create mode 100644 testlib/src/main/resources/rome/ts/fileBefore.mts create mode 100644 testlib/src/main/resources/rome/ts/fileBefore.ts create mode 100644 testlib/src/main/resources/rome/ts/fileBefore.tsx create mode 100644 testlib/src/test/java/com/diffplug/spotless/rome/RomeStepTest.java diff --git a/testlib/src/main/resources/rome/config/line-width-120.json b/testlib/src/main/resources/rome/config/line-width-120.json new file mode 100644 index 0000000000..8f14afa3f8 --- /dev/null +++ b/testlib/src/main/resources/rome/config/line-width-120.json @@ -0,0 +1,11 @@ +{ + "formatter": { + "enabled": true, + "indentStyle": "tab", + "lineWidth": 120, + "formatWithErrors": false + }, + "linter": { + "enabled": false + } + } \ No newline at end of file diff --git a/testlib/src/main/resources/rome/config/line-width-80.json b/testlib/src/main/resources/rome/config/line-width-80.json new file mode 100644 index 0000000000..5ec998bd97 --- /dev/null +++ b/testlib/src/main/resources/rome/config/line-width-80.json @@ -0,0 +1,11 @@ +{ + "formatter": { + "enabled": true, + "indentStyle": "tab", + "lineWidth": 80, + "formatWithErrors": false + }, + "linter": { + "enabled": false + } + } \ No newline at end of file diff --git a/testlib/src/main/resources/rome/js/fileAfter.cjs b/testlib/src/main/resources/rome/js/fileAfter.cjs new file mode 100644 index 0000000000..defc9c85eb --- /dev/null +++ b/testlib/src/main/resources/rome/js/fileAfter.cjs @@ -0,0 +1,3 @@ +function foo(name = "World") { + return "Hello " + name; +} diff --git a/testlib/src/main/resources/rome/js/fileAfter.js b/testlib/src/main/resources/rome/js/fileAfter.js new file mode 100644 index 0000000000..defc9c85eb --- /dev/null +++ b/testlib/src/main/resources/rome/js/fileAfter.js @@ -0,0 +1,3 @@ +function foo(name = "World") { + return "Hello " + name; +} diff --git a/testlib/src/main/resources/rome/js/fileAfter.jsx b/testlib/src/main/resources/rome/js/fileAfter.jsx new file mode 100644 index 0000000000..313aceb6ed --- /dev/null +++ b/testlib/src/main/resources/rome/js/fileAfter.jsx @@ -0,0 +1,3 @@ +export function Panel(cfg = {}) { + return
    {1 + 2}
    ; +} diff --git a/testlib/src/main/resources/rome/js/fileAfter.mjs b/testlib/src/main/resources/rome/js/fileAfter.mjs new file mode 100644 index 0000000000..defc9c85eb --- /dev/null +++ b/testlib/src/main/resources/rome/js/fileAfter.mjs @@ -0,0 +1,3 @@ +function foo(name = "World") { + return "Hello " + name; +} diff --git a/testlib/src/main/resources/rome/js/fileBefore.cjs b/testlib/src/main/resources/rome/js/fileBefore.cjs new file mode 100644 index 0000000000..92539ba751 --- /dev/null +++ b/testlib/src/main/resources/rome/js/fileBefore.cjs @@ -0,0 +1,3 @@ +function foo ( name="World"){ + return "Hello "+name ; + } \ No newline at end of file diff --git a/testlib/src/main/resources/rome/js/fileBefore.js b/testlib/src/main/resources/rome/js/fileBefore.js new file mode 100644 index 0000000000..92539ba751 --- /dev/null +++ b/testlib/src/main/resources/rome/js/fileBefore.js @@ -0,0 +1,3 @@ +function foo ( name="World"){ + return "Hello "+name ; + } \ No newline at end of file diff --git a/testlib/src/main/resources/rome/js/fileBefore.jsx b/testlib/src/main/resources/rome/js/fileBefore.jsx new file mode 100644 index 0000000000..8e5d9834bc --- /dev/null +++ b/testlib/src/main/resources/rome/js/fileBefore.jsx @@ -0,0 +1,4 @@ +export function Panel ( cfg={}){ + return (
    {1+2}
    + ) ; + } \ No newline at end of file diff --git a/testlib/src/main/resources/rome/js/fileBefore.mjs b/testlib/src/main/resources/rome/js/fileBefore.mjs new file mode 100644 index 0000000000..92539ba751 --- /dev/null +++ b/testlib/src/main/resources/rome/js/fileBefore.mjs @@ -0,0 +1,3 @@ +function foo ( name="World"){ + return "Hello "+name ; + } \ No newline at end of file diff --git a/testlib/src/main/resources/rome/js/longLineAfter120.js b/testlib/src/main/resources/rome/js/longLineAfter120.js new file mode 100644 index 0000000000..68addc4b65 --- /dev/null +++ b/testlib/src/main/resources/rome/js/longLineAfter120.js @@ -0,0 +1 @@ +const x = ["Hello", "World", "How", "Are", "You", "Doing", "Today", "Such", "A", "Wondrous", "Sunshine"]; diff --git a/testlib/src/main/resources/rome/js/longLineAfter80.js b/testlib/src/main/resources/rome/js/longLineAfter80.js new file mode 100644 index 0000000000..dbdbd157e9 --- /dev/null +++ b/testlib/src/main/resources/rome/js/longLineAfter80.js @@ -0,0 +1,13 @@ +const x = [ + "Hello", + "World", + "How", + "Are", + "You", + "Doing", + "Today", + "Such", + "A", + "Wondrous", + "Sunshine", +]; diff --git a/testlib/src/main/resources/rome/js/longLineBefore.js b/testlib/src/main/resources/rome/js/longLineBefore.js new file mode 100644 index 0000000000..fd59e429c2 --- /dev/null +++ b/testlib/src/main/resources/rome/js/longLineBefore.js @@ -0,0 +1 @@ +const x = ["Hello", "World", "How", "Are", "You", "Doing", "Today", "Such", "A", "Wondrous", "Sunshine"]; \ No newline at end of file diff --git a/testlib/src/main/resources/rome/json/fileAfter.json b/testlib/src/main/resources/rome/json/fileAfter.json new file mode 100644 index 0000000000..468dac3297 --- /dev/null +++ b/testlib/src/main/resources/rome/json/fileAfter.json @@ -0,0 +1,5 @@ +{ + "a": [1, 2, 3], + "b": 9, + "c": null +} diff --git a/testlib/src/main/resources/rome/json/fileBefore.json b/testlib/src/main/resources/rome/json/fileBefore.json new file mode 100644 index 0000000000..77182284c7 --- /dev/null +++ b/testlib/src/main/resources/rome/json/fileBefore.json @@ -0,0 +1,7 @@ + { + "a":[1,2,3 + +], + "b":9, + "c" : null + } \ No newline at end of file diff --git a/testlib/src/main/resources/rome/ts/fileAfter.cts b/testlib/src/main/resources/rome/ts/fileAfter.cts new file mode 100644 index 0000000000..f854953234 --- /dev/null +++ b/testlib/src/main/resources/rome/ts/fileAfter.cts @@ -0,0 +1,4 @@ +type Name = "World" | "Maven" | "Gradle"; +const foo = (name: Name = "World", v: T): string => { + return "Hello " + name; +}; diff --git a/testlib/src/main/resources/rome/ts/fileAfter.mts b/testlib/src/main/resources/rome/ts/fileAfter.mts new file mode 100644 index 0000000000..e6563e3030 --- /dev/null +++ b/testlib/src/main/resources/rome/ts/fileAfter.mts @@ -0,0 +1,4 @@ +export type Name = "World" | "Maven" | "Gradle"; +export const foo = (name: Name = "World", v: T): string => { + return "Hello " + name; +}; diff --git a/testlib/src/main/resources/rome/ts/fileAfter.ts b/testlib/src/main/resources/rome/ts/fileAfter.ts new file mode 100644 index 0000000000..e6563e3030 --- /dev/null +++ b/testlib/src/main/resources/rome/ts/fileAfter.ts @@ -0,0 +1,4 @@ +export type Name = "World" | "Maven" | "Gradle"; +export const foo = (name: Name = "World", v: T): string => { + return "Hello " + name; +}; diff --git a/testlib/src/main/resources/rome/ts/fileAfter.tsx b/testlib/src/main/resources/rome/ts/fileAfter.tsx new file mode 100644 index 0000000000..15ef316142 --- /dev/null +++ b/testlib/src/main/resources/rome/ts/fileAfter.tsx @@ -0,0 +1,7 @@ +export interface Cfg { + classname: string; + message: T; +} +const Panel = (cfg: Cfg): JSX.Element => { + return
    {String(cfg.message)}
    ; +}; diff --git a/testlib/src/main/resources/rome/ts/fileBefore.cts b/testlib/src/main/resources/rome/ts/fileBefore.cts new file mode 100644 index 0000000000..d4304287c0 --- /dev/null +++ b/testlib/src/main/resources/rome/ts/fileBefore.cts @@ -0,0 +1,4 @@ +type Name = "World" | "Maven"|"Gradle"; +const foo = ( name: Name="World", v: T): string => { + return "Hello " + name; + } \ No newline at end of file diff --git a/testlib/src/main/resources/rome/ts/fileBefore.mts b/testlib/src/main/resources/rome/ts/fileBefore.mts new file mode 100644 index 0000000000..96837762a3 --- /dev/null +++ b/testlib/src/main/resources/rome/ts/fileBefore.mts @@ -0,0 +1,5 @@ +export + type Name = "World" | "Maven"|"Gradle"; +export const foo = ( name: Name="World", v: T): string => { + return "Hello " + name; + } \ No newline at end of file diff --git a/testlib/src/main/resources/rome/ts/fileBefore.ts b/testlib/src/main/resources/rome/ts/fileBefore.ts new file mode 100644 index 0000000000..96837762a3 --- /dev/null +++ b/testlib/src/main/resources/rome/ts/fileBefore.ts @@ -0,0 +1,5 @@ +export + type Name = "World" | "Maven"|"Gradle"; +export const foo = ( name: Name="World", v: T): string => { + return "Hello " + name; + } \ No newline at end of file diff --git a/testlib/src/main/resources/rome/ts/fileBefore.tsx b/testlib/src/main/resources/rome/ts/fileBefore.tsx new file mode 100644 index 0000000000..38f24f8440 --- /dev/null +++ b/testlib/src/main/resources/rome/ts/fileBefore.tsx @@ -0,0 +1,8 @@ +export interface Cfg{ +classname:string, +message:T, +} +const Panel = ( cfg:Cfg):JSX.Element =>{ + return (
    {String(cfg.message)}
    + ) ; + } \ No newline at end of file diff --git a/testlib/src/test/java/com/diffplug/spotless/rome/RomeStepTest.java b/testlib/src/test/java/com/diffplug/spotless/rome/RomeStepTest.java new file mode 100644 index 0000000000..5c4801d50a --- /dev/null +++ b/testlib/src/test/java/com/diffplug/spotless/rome/RomeStepTest.java @@ -0,0 +1,284 @@ +/* + * Copyright 2023 DiffPlug + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.diffplug.spotless.rome; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +import com.diffplug.common.base.StandardSystemProperty; +import com.diffplug.spotless.ResourceHarness; +import com.diffplug.spotless.StepHarnessWithFile; +import com.diffplug.spotless.ThrowingEx; + +class RomeStepTest extends ResourceHarness { + private static String downloadDir; + + @BeforeAll + static void createDownloadDir() throws IOException { + // We do not want to download Rome each time we execute a test + var userHome = Paths.get(StandardSystemProperty.USER_HOME.value()); + downloadDir = userHome.resolve(".gradle").resolve("rome-dl-test").toAbsolutePath().normalize().toString(); + } + + /** + * Tests that files can be formatted without setting the input language + * explicitly. + */ + @Nested + class AutoDetectLanguage { + /** + * Tests that a *.cjs file can be formatted without setting the input language + * explicitly. + */ + @Test + void testAutoDetectCjs() { + var step = RomeStep.withExeDownload("12.0.0", downloadDir.toString()).create(); + var stepHarness = StepHarnessWithFile.forStep(RomeStepTest.this, step); + stepHarness.testResource("rome/js/fileBefore.cjs", "rome/js/fileAfter.cjs"); + } + + /** + * Tests that a *.cts file can be formatted without setting the input language + * explicitly. + */ + @Test + void testAutoDetectCts() { + var step = RomeStep.withExeDownload("12.0.0", downloadDir.toString()).create(); + var stepHarness = StepHarnessWithFile.forStep(RomeStepTest.this, step); + stepHarness.testResource("rome/ts/fileBefore.cts", "rome/ts/fileAfter.cts"); + } + + /** + * Tests that a *.js file can be formatted without setting the input language + * explicitly. + */ + @Test + void testAutoDetectJs() { + var step = RomeStep.withExeDownload("12.0.0", downloadDir.toString()).create(); + var stepHarness = StepHarnessWithFile.forStep(RomeStepTest.this, step); + stepHarness.testResource("rome/js/fileBefore.js", "rome/js/fileAfter.js"); + } + + /** + * Tests that a *.js file can be formatted without setting the input language + * explicitly. + */ + @Test + void testAutoDetectJson() { + var step = RomeStep.withExeDownload("12.0.0", downloadDir.toString()).create(); + var stepHarness = StepHarnessWithFile.forStep(RomeStepTest.this, step); + stepHarness.testResource("rome/json/fileBefore.json", "rome/json/fileAfter.json"); + } + + /** + * Tests that a *.jsx file can be formatted without setting the input language + * explicitly. + */ + @Test + void testAutoDetectJsx() { + var step = RomeStep.withExeDownload("12.0.0", downloadDir.toString()).create(); + var stepHarness = StepHarnessWithFile.forStep(RomeStepTest.this, step); + stepHarness.testResource("rome/js/fileBefore.jsx", "rome/js/fileAfter.jsx"); + } + + /** + * Tests that a *.mjs file can be formatted without setting the input language + * explicitly. + */ + @Test + void testAutoDetectMjs() { + var step = RomeStep.withExeDownload("12.0.0", downloadDir.toString()).create(); + var stepHarness = StepHarnessWithFile.forStep(RomeStepTest.this, step); + stepHarness.testResource("rome/js/fileBefore.mjs", "rome/js/fileAfter.mjs"); + } + + /** + * Tests that a *.mts file can be formatted without setting the input language + * explicitly. + */ + @Test + void testAutoDetectMts() { + var step = RomeStep.withExeDownload("12.0.0", downloadDir.toString()).create(); + var stepHarness = StepHarnessWithFile.forStep(RomeStepTest.this, step); + stepHarness.testResource("rome/ts/fileBefore.mts", "rome/ts/fileAfter.mts"); + } + + /** + * Tests that a *.ts file can be formatted without setting the input language + * explicitly. + */ + @Test + void testAutoDetectTs() { + var step = RomeStep.withExeDownload("12.0.0", downloadDir.toString()).create(); + var stepHarness = StepHarnessWithFile.forStep(RomeStepTest.this, step); + stepHarness.testResource("rome/ts/fileBefore.ts", "rome/ts/fileAfter.ts"); + } + + /** + * Tests that a *.tsx file can be formatted without setting the input language + * explicitly. + */ + @Test + void testAutoDetectTsx() { + var step = RomeStep.withExeDownload("12.0.0", downloadDir.toString()).create(); + var stepHarness = StepHarnessWithFile.forStep(RomeStepTest.this, step); + stepHarness.testResource("rome/ts/fileBefore.tsx", "rome/ts/fileAfter.tsx"); + } + } + + @Nested + class ConfigFile { + /** + * Test formatting with the line width in the config file set to 120. + */ + @Test + void testLineWidth120() { + var path = createRomeConfig("rome/config/line-width-120.json"); + var step = RomeStep.withExeDownload("12.0.0", downloadDir.toString()).withConfigPath(path).create(); + var stepHarness = StepHarnessWithFile.forStep(RomeStepTest.this, step); + stepHarness.testResource("rome/js/longLineBefore.js", "rome/js/longLineAfter120.js"); + } + + /** + * Test formatting with the line width in the config file set to 120. + */ + @Test + void testLineWidth80() { + var path = createRomeConfig("rome/config/line-width-80.json"); + var step = RomeStep.withExeDownload("12.0.0", downloadDir.toString()).withConfigPath(path).create(); + var stepHarness = StepHarnessWithFile.forStep(RomeStepTest.this, step); + stepHarness.testResource("rome/js/longLineBefore.js", "rome/js/longLineAfter80.js"); + } + + private String createRomeConfig(String name) { + var config = createTestFile(name).toPath(); + var dir = config.getParent(); + var rome = dir.resolve("rome.json"); + ThrowingEx.run(() -> Files.copy(config, rome)); + return dir.toString(); + } + } + + /** + * Tests that files can be formatted when setting the input language explicitly. + */ + @Nested + class ExplicitLanguage { + /** + * Tests that a *.cjs file can be formatted when setting the input language + * explicitly. + */ + @Test + void testAutoDetectCjs() { + var step = RomeStep.withExeDownload("12.0.0", downloadDir.toString()).withLanguage("js").create(); + var stepHarness = StepHarnessWithFile.forStep(RomeStepTest.this, step); + stepHarness.testResource("rome/js/fileBefore.cjs", "rome/js/fileAfter.cjs"); + } + + /** + * Tests that a *.cts file can be formatted when setting the input language + * explicitly. + */ + @Test + void testAutoDetectCts() { + var step = RomeStep.withExeDownload("12.0.0", downloadDir.toString()).withLanguage("ts").create(); + var stepHarness = StepHarnessWithFile.forStep(RomeStepTest.this, step); + stepHarness.testResource("rome/ts/fileBefore.cts", "rome/ts/fileAfter.cts"); + } + + /** + * Tests that a *.js file can be formatted when setting the input language + * explicitly. + */ + @Test + void testAutoDetectJs() { + var step = RomeStep.withExeDownload("12.0.0", downloadDir.toString()).withLanguage("js").create(); + var stepHarness = StepHarnessWithFile.forStep(RomeStepTest.this, step); + stepHarness.testResource("rome/js/fileBefore.js", "rome/js/fileAfter.js"); + } + + /** + * Tests that a *.json file can be formatted when setting the input language + * explicitly. + */ + @Test + void testAutoDetectJson() { + var step = RomeStep.withExeDownload("12.0.0", downloadDir.toString()).withLanguage("json").create(); + var stepHarness = StepHarnessWithFile.forStep(RomeStepTest.this, step); + stepHarness.testResource("rome/json/fileBefore.json", "rome/json/fileAfter.json"); + } + + /** + * Tests that a *.jsx file can be formatted when setting the input language + * explicitly. + */ + @Test + void testAutoDetectJsx() { + var step = RomeStep.withExeDownload("12.0.0", downloadDir.toString()).withLanguage("jsx").create(); + var stepHarness = StepHarnessWithFile.forStep(RomeStepTest.this, step); + stepHarness.testResource("rome/js/fileBefore.jsx", "rome/js/fileAfter.jsx"); + } + + /** + * Tests that a *.mjs file can be formatted without setting the input language + * explicitly. + */ + @Test + void testAutoDetectMjs() { + var step = RomeStep.withExeDownload("12.0.0", downloadDir.toString()).withLanguage("js").create(); + var stepHarness = StepHarnessWithFile.forStep(RomeStepTest.this, step); + stepHarness.testResource("rome/js/fileBefore.mjs", "rome/js/fileAfter.mjs"); + } + + /** + * Tests that a *.mts file can be formatted when setting the input language + * explicitly. + */ + @Test + void testAutoDetectMts() { + var step = RomeStep.withExeDownload("12.0.0", downloadDir.toString()).withLanguage("ts").create(); + var stepHarness = StepHarnessWithFile.forStep(RomeStepTest.this, step); + stepHarness.testResource("rome/ts/fileBefore.mts", "rome/ts/fileAfter.mts"); + } + + /** + * Tests that a *.ts file can be formatted when setting the input language + * explicitly. + */ + @Test + void testAutoDetectTs() { + var step = RomeStep.withExeDownload("12.0.0", downloadDir.toString()).withLanguage("ts").create(); + var stepHarness = StepHarnessWithFile.forStep(RomeStepTest.this, step); + stepHarness.testResource("rome/ts/fileBefore.ts", "rome/ts/fileAfter.ts"); + } + + /** + * Tests that a *.tsx file can be formatted when setting the input language + * explicitly. + */ + @Test + void testAutoDetectTsx() { + var step = RomeStep.withExeDownload("12.0.0", downloadDir.toString()).withLanguage("tsx").create(); + var stepHarness = StepHarnessWithFile.forStep(RomeStepTest.this, step); + stepHarness.testResource("rome/ts/fileBefore.tsx", "rome/ts/fileAfter.tsx"); + } + } +} From 97198cb239589e8bc3688146ba7efd44170ac085 Mon Sep 17 00:00:00 2001 From: Andre Wachsmuth Date: Sat, 8 Apr 2023 22:18:24 +0100 Subject: [PATCH 21/33] Add tests for Maven plugin --- plugin-maven/build.gradle | 1 + .../maven/MavenIntegrationHarness.java | 4 + .../spotless/maven/rome/RomeMavenTest.java | 187 ++++++++++++++++++ 3 files changed, 192 insertions(+) create mode 100644 plugin-maven/src/test/java/com/diffplug/spotless/maven/rome/RomeMavenTest.java diff --git a/plugin-maven/build.gradle b/plugin-maven/build.gradle index a8604c6ed9..dda98ad79a 100644 --- a/plugin-maven/build.gradle +++ b/plugin-maven/build.gradle @@ -50,6 +50,7 @@ dependencies { testImplementation "org.mockito:mockito-core:${VER_MOCKITO}" testImplementation "com.diffplug.durian:durian-io:${VER_DURIAN}" testImplementation 'com.github.spullara.mustache.java:compiler:0.9.10' + testImplementation 'org.owasp.encoder:encoder:1.2.3' testImplementation "org.apache.maven:maven-plugin-api:${VER_MAVEN_API}" testImplementation "org.eclipse.aether:aether-api:${VER_ECLIPSE_AETHER}" testImplementation "org.codehaus.plexus:plexus-resources:${VER_PLEXUS_RESOURCES}" diff --git a/plugin-maven/src/test/java/com/diffplug/spotless/maven/MavenIntegrationHarness.java b/plugin-maven/src/test/java/com/diffplug/spotless/maven/MavenIntegrationHarness.java index 216502bc07..398932dd19 100644 --- a/plugin-maven/src/test/java/com/diffplug/spotless/maven/MavenIntegrationHarness.java +++ b/plugin-maven/src/test/java/com/diffplug/spotless/maven/MavenIntegrationHarness.java @@ -154,6 +154,10 @@ protected void writePomWithPrettierSteps(String includes, String... steps) throw writePom(formats(groupWithSteps("format", including(includes), steps))); } + protected void writePomWithRomeSteps(String includes, String... steps) throws IOException { + writePom(formats(groupWithSteps("format", including(includes), steps))); + } + protected void writePomWithPrettierSteps(String[] plugins, String includes, String... steps) throws IOException { writePom(null, formats(groupWithSteps("format", including(includes), steps)), null, plugins); } diff --git a/plugin-maven/src/test/java/com/diffplug/spotless/maven/rome/RomeMavenTest.java b/plugin-maven/src/test/java/com/diffplug/spotless/maven/rome/RomeMavenTest.java new file mode 100644 index 0000000000..76e297e704 --- /dev/null +++ b/plugin-maven/src/test/java/com/diffplug/spotless/maven/rome/RomeMavenTest.java @@ -0,0 +1,187 @@ +/* + * Copyright 2023 DiffPlug + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.diffplug.spotless.maven.rome; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.owasp.encoder.Encode.forXml; + +import org.junit.jupiter.api.Test; + +import com.diffplug.spotless.maven.MavenIntegrationHarness; + +class RomeMavenTest extends MavenIntegrationHarness { + /** + * Tests that rome can be used as a generic formatting step. + * + * @throws Exception When a test failure occurs. + */ + @Test + public void testAsGenericStep() throws Exception { + writePomWithRomeSteps("**/*.js", "12.0.0"); + setFile("rome_test.js").toResource("rome/js/fileBefore.js"); + mavenRunner().withArguments("spotless:apply").runNoError(); + assertFile("rome_test.js").sameAsResource("rome/js/fileAfter.js"); + } + + /** + * Tests that rome can be used as a JavaScript formatting step. + * + * @throws Exception When a test failure occurs. + */ + @Test + public void testAsJavaScriptStep() throws Exception { + writePomWithJavascriptSteps("**/*.js", "12.0.0"); + setFile("rome_test.js").toResource("rome/js/fileBefore.js"); + mavenRunner().withArguments("spotless:apply").runNoError(); + assertFile("rome_test.js").sameAsResource("rome/js/fileAfter.js"); + } + + /** + * Tests that rome can be used as a JSON formatting step. + * + * @throws Exception When a test failure occurs. + */ + @Test + public void testAsJsonStep() throws Exception { + writePomWithJsonSteps("**/*.json", "12.0.0"); + setFile("rome_test.json").toResource("rome/json/fileBefore.json"); + mavenRunner().withArguments("spotless:apply").runNoError(); + assertFile("rome_test.json").sameAsResource("rome/json/fileAfter.json"); + } + + /** + * Tests that rome can be used as a TypeScript formatting step. + * + * @throws Exception When a test failure occurs. + */ + @Test + public void testAsTypeScriptStep() throws Exception { + writePomWithTypescriptSteps("**/*.ts", "12.0.0"); + setFile("rome_test.ts").toResource("rome/ts/fileBefore.ts"); + mavenRunner().withArguments("spotless:apply").runNoError(); + assertFile("rome_test.ts").sameAsResource("rome/ts/fileAfter.ts"); + } + + /** + * Tests that the language can be specified for the generic format step. + * + * @throws Exception When a test failure occurs. + */ + @Test + public void testCanSetLanguageForGenericStep() throws Exception { + writePomWithRomeSteps("**/*.nosj", "12.0.0json"); + setFile("rome_test.nosj").toResource("rome/json/fileBefore.json"); + mavenRunner().withArguments("spotless:apply").runNoError(); + assertFile("rome_test.nosj").sameAsResource("rome/json/fileAfter.json"); + } + + /** + * Tests that an absolute config path can be specified. + * + * @throws Exception When a test failure occurs. + */ + @Test + public void testConfigPathAbsolute() throws Exception { + var path = newFile("configs").getAbsolutePath(); + writePomWithRomeSteps("**/*.js", + "12.0.0" + forXml(path) + ""); + setFile("rome_test.js").toResource("rome/js/longLineBefore.js"); + setFile("configs/rome.json").toResource("rome/config/line-width-120.json"); + mavenRunner().withArguments("spotless:apply").runNoError(); + assertFile("rome_test.js").sameAsResource("rome/js/longLineAfter120.js"); + } + + /** + * Tests that a path to the directory with the rome.json config file can be + * specified. Uses a config file with a line width of 120. + * + * @throws Exception When a test failure occurs. + */ + @Test + public void testConfigPathLineWidth120() throws Exception { + writePomWithRomeSteps("**/*.js", "12.0.0configs"); + setFile("rome_test.js").toResource("rome/js/longLineBefore.js"); + setFile("configs/rome.json").toResource("rome/config/line-width-120.json"); + mavenRunner().withArguments("spotless:apply").runNoError(); + assertFile("rome_test.js").sameAsResource("rome/js/longLineAfter120.js"); + } + + /** + * Tests that a path to the directory with the rome.json config file can be + * specified. Uses a config file with a line width of 80. + * + * @throws Exception When a test failure occurs. + */ + @Test + public void testConfigPathLineWidth80() throws Exception { + writePomWithRomeSteps("**/*.js", "12.0.0configs"); + setFile("rome_test.js").toResource("rome/js/longLineBefore.js"); + setFile("configs/rome.json").toResource("rome/config/line-width-80.json"); + mavenRunner().withArguments("spotless:apply").runNoError(); + assertFile("rome_test.js").sameAsResource("rome/js/longLineAfter80.js"); + } + + /** + * Tests that the download directory can be an absolute path. + * + * @throws Exception When a test failure occurs. + */ + @Test + public void testDownloadDirAbsolute() throws Exception { + var path = newFile("target/bin/rome").getAbsoluteFile().toString(); + writePomWithRomeSteps("**/*.js", + "12.0.0" + forXml(path) + ""); + setFile("rome_test.js").toResource("rome/js/fileBefore.js"); + assertTrue(!newFile("target/bin/rome").exists() || newFile("target/bin/rome").list().length == 0); + mavenRunner().withArguments("spotless:apply").runNoError(); + assertFile("rome_test.js").sameAsResource("rome/js/fileAfter.js"); + assertEquals(2, newFile("target/bin/rome").list().length); + } + + /** + * Tests that the download directory can be changed to a path relative to the + * project's base directory. + * + * @throws Exception When a test failure occurs. + */ + @Test + public void testDownloadDirRelative() throws Exception { + writePomWithRomeSteps("**/*.js", + "12.0.0target/bin/rome"); + setFile("rome_test.js").toResource("rome/js/fileBefore.js"); + assertTrue(!newFile("target/bin/rome").exists() || newFile("target/bin/rome").list().length == 0); + mavenRunner().withArguments("spotless:apply").runNoError(); + assertFile("rome_test.js").sameAsResource("rome/js/fileAfter.js"); + assertEquals(2, newFile("target/bin/rome").list().length); + } + + /** + * Tests that the build fails when the input file could not be parsed. + * + * @throws Exception When a test failure occurs. + */ + @Test + public void testFailureWhenNotParseable() throws Exception { + writePomWithRomeSteps("**/*.js", "12.0.0json"); + setFile("rome_test.js").toResource("rome/js/fileBefore.js"); + var result = mavenRunner().withArguments("spotless:apply").runHasError(); + assertFile("rome_test.js").sameAsResource("rome/js/fileBefore.js"); + assertTrue(result.stdOutUtf8().contains("Format with errors is disabled.")); + assertTrue(result.stdOutUtf8().contains("Unable to format file")); + assertTrue(result.stdOutUtf8().contains("Step 'rome' found problem in 'rome_test.js'")); + } +} From 77d302e60106f90dec7cf2252e4b3bedf21c8580 Mon Sep 17 00:00:00 2001 From: Andre Wachsmuth Date: Sat, 8 Apr 2023 22:56:57 +0100 Subject: [PATCH 22/33] Add tests for Gradle plugin --- plugin-gradle/build.gradle | 1 + .../gradle/spotless/RomeIntegrationTest.java | 343 ++++++++++++++++++ .../spotless/maven/rome/RomeMavenTest.java | 43 ++- 3 files changed, 373 insertions(+), 14 deletions(-) create mode 100644 plugin-gradle/src/test/java/com/diffplug/gradle/spotless/RomeIntegrationTest.java diff --git a/plugin-gradle/build.gradle b/plugin-gradle/build.gradle index 9a725dbf5d..021155abfe 100644 --- a/plugin-gradle/build.gradle +++ b/plugin-gradle/build.gradle @@ -25,6 +25,7 @@ dependencies { testImplementation "org.junit.jupiter:junit-jupiter:${VER_JUNIT}" testImplementation "org.assertj:assertj-core:${VER_ASSERTJ}" testImplementation "com.diffplug.durian:durian-testlib:${VER_DURIAN}" + testImplementation 'org.owasp.encoder:encoder:1.2.3' } apply from: rootProject.file('gradle/special-tests.gradle') diff --git a/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/RomeIntegrationTest.java b/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/RomeIntegrationTest.java new file mode 100644 index 0000000000..4464b1cec7 --- /dev/null +++ b/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/RomeIntegrationTest.java @@ -0,0 +1,343 @@ +/* + * Copyright 2023 DiffPlug + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.diffplug.gradle.spotless; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.IOException; + +import org.junit.jupiter.api.Test; +import org.owasp.encoder.Encode; + +class RomeIntegrationTest extends GradleIntegrationHarness { + /** + * Tests that rome can be used as a generic formatting step. + * + * @throws Exception When a test failure occurs. + */ + @Test + void asGenericStep() throws IOException { + setFile("build.gradle").toLines( + "plugins {", + " id 'com.diffplug.spotless'", + "}", + "repositories { mavenCentral() }", + "spotless {", + " format 'myrome', {", + " target '**/*.js'", + " rome('12.0.0')", + " }", + "}"); + setFile("rome_test.js").toResource("rome/js/fileBefore.js"); + + var spotlessApply = gradleRunner().withArguments("--stacktrace", "spotlessApply").build(); + assertThat(spotlessApply.getOutput()).contains("BUILD SUCCESSFUL"); + assertFile("rome_test.js").sameAsResource("rome/js/fileAfter.js"); + } + + /** + * Tests that rome can be used as a JavaScript formatting step. + * + * @throws Exception When a test failure occurs. + */ + @Test + void asJavaScriptStep() throws Exception { + setFile("build.gradle").toLines( + "plugins {", + " id 'com.diffplug.spotless'", + "}", + "repositories { mavenCentral() }", + "spotless {", + " javascript {", + " target '**/*.js'", + " rome('12.0.0')", + " }", + "}"); + setFile("rome_test.js").toResource("rome/js/fileBefore.js"); + + var spotlessApply = gradleRunner().withArguments("--stacktrace", "spotlessApply").build(); + assertThat(spotlessApply.getOutput()).contains("BUILD SUCCESSFUL"); + assertFile("rome_test.js").sameAsResource("rome/js/fileAfter.js"); + } + + /** + * Tests that rome can be used as a JSON formatting step. + * + * @throws Exception When a test failure occurs. + */ + @Test + void asJsonStep() throws Exception { + setFile("build.gradle").toLines( + "plugins {", + " id 'com.diffplug.spotless'", + "}", + "repositories { mavenCentral() }", + "spotless {", + " json {", + " target '**/*.json'", + " rome('12.0.0')", + " }", + "}"); + setFile("rome_test.json").toResource("rome/json/fileBefore.json"); + + var spotlessApply = gradleRunner().withArguments("--stacktrace", "spotlessApply").build(); + assertThat(spotlessApply.getOutput()).contains("BUILD SUCCESSFUL"); + assertFile("rome_test.json").sameAsResource("rome/json/fileAfter.json"); + } + + /** + * Tests that rome can be used as a TypeScript formatting step. + * + * @throws Exception When a test failure occurs. + */ + @Test + void asTypeScriptStep() throws Exception { + setFile("build.gradle").toLines( + "plugins {", + " id 'com.diffplug.spotless'", + "}", + "repositories { mavenCentral() }", + "spotless {", + " typescript {", + " target '**/*.ts'", + " rome('12.0.0')", + " }", + "}"); + setFile("rome_test.ts").toResource("rome/ts/fileBefore.ts"); + + var spotlessApply = gradleRunner().withArguments("--stacktrace", "spotlessApply").build(); + assertThat(spotlessApply.getOutput()).contains("BUILD SUCCESSFUL"); + assertFile("rome_test.ts").sameAsResource("rome/ts/fileAfter.ts"); + } + + /** + * Tests that the language can be specified for the generic format step. + * + * @throws Exception When a test failure occurs. + */ + @Test + void canSetLanguageForGenericStep() throws Exception { + setFile("build.gradle").toLines( + "plugins {", + " id 'com.diffplug.spotless'", + "}", + "repositories { mavenCentral() }", + "spotless {", + " format 'myrome', {", + " target '**/*.nosj'", + " rome('12.0.0').language('json')", + " }", + "}"); + setFile("rome_test.nosj").toResource("rome/json/fileBefore.json"); + + var spotlessApply = gradleRunner().withArguments("--stacktrace", "spotlessApply").build(); + assertThat(spotlessApply.getOutput()).contains("BUILD SUCCESSFUL"); + assertFile("rome_test.nosj").sameAsResource("rome/json/fileAfter.json"); + } + + /** + * Tests that an absolute config path can be specified. + * + * @throws Exception When a test failure occurs. + */ + @Test + void configPathAbsolute() throws Exception { + var path = newFile("configs").getAbsolutePath(); + setFile("build.gradle").toLines( + "plugins {", + " id 'com.diffplug.spotless'", + "}", + "repositories { mavenCentral() }", + "spotless {", + " format 'myrome', {", + " target '**/*.js'", + " rome('12.0.0').configPath('" + Encode.forJava(path) + "')", + " }", + "}"); + setFile("rome_test.js").toResource("rome/js/longLineBefore.js"); + setFile("configs/rome.json").toResource("rome/config/line-width-120.json"); + + var spotlessApply = gradleRunner().withArguments("--stacktrace", "spotlessApply").build(); + assertThat(spotlessApply.getOutput()).contains("BUILD SUCCESSFUL"); + assertFile("rome_test.js").sameAsResource("rome/js/longLineAfter120.js"); + } + + /** + * Tests that a path to the directory with the rome.json config file can be + * specified. Uses a config file with a line width of 120. + * + * @throws Exception When a test failure occurs. + */ + @Test + void configPathLineWidth120() throws Exception { + setFile("build.gradle").toLines( + "plugins {", + " id 'com.diffplug.spotless'", + "}", + "repositories { mavenCentral() }", + "spotless {", + " format 'myrome', {", + " target '**/*.js'", + " rome('12.0.0').configPath('configs')", + " }", + "}"); + setFile("rome_test.js").toResource("rome/js/longLineBefore.js"); + setFile("configs/rome.json").toResource("rome/config/line-width-120.json"); + + var spotlessApply = gradleRunner().withArguments("--stacktrace", "spotlessApply").build(); + assertThat(spotlessApply.getOutput()).contains("BUILD SUCCESSFUL"); + assertFile("rome_test.js").sameAsResource("rome/js/longLineAfter120.js"); + } + + /** + * Tests that a path to the directory with the rome.json config file can be + * specified. Uses a config file with a line width of 80. + * + * @throws Exception When a test failure occurs. + */ + @Test + void configPathLineWidth80() throws Exception { + setFile("build.gradle").toLines( + "plugins {", + " id 'com.diffplug.spotless'", + "}", + "repositories { mavenCentral() }", + "spotless {", + " format 'myrome', {", + " target '**/*.js'", + " rome('12.0.0').configPath('configs')", + " }", + "}"); + setFile("rome_test.js").toResource("rome/js/longLineBefore.js"); + setFile("configs/rome.json").toResource("rome/config/line-width-80.json"); + + var spotlessApply = gradleRunner().withArguments("--stacktrace", "spotlessApply").build(); + assertThat(spotlessApply.getOutput()).contains("BUILD SUCCESSFUL"); + assertFile("rome_test.js").sameAsResource("rome/js/longLineAfter80.js"); + } + + /** + * Tests that the download directory can be an absolute path. + * + * @throws Exception When a test failure occurs. + */ + @Test + void downloadDirAbsolute() throws Exception { + var path = newFile("target/bin/rome").getAbsoluteFile().toString(); + setFile("build.gradle").toLines( + "plugins {", + " id 'com.diffplug.spotless'", + "}", + "repositories { mavenCentral() }", + "spotless {", + " format 'myrome', {", + " target '**/*.js'", + " rome('12.0.0').downloadDir('" + Encode.forJava(path) + "')", + " }", + "}"); + setFile("rome_test.js").toResource("rome/js/fileBefore.js"); + assertTrue(!newFile("target/bin/rome").exists() || newFile("target/bin/rome").list().length == 0); + + var spotlessApply = gradleRunner().withArguments("--stacktrace", "spotlessApply").build(); + assertThat(spotlessApply.getOutput()).contains("BUILD SUCCESSFUL"); + assertFile("rome_test.js").sameAsResource("rome/js/fileAfter.js"); + assertEquals(2, newFile("target/bin/rome").list().length); + } + + /** + * Tests that the download directory can be changed to a path relative to the + * project's base directory. + * + * @throws Exception When a test failure occurs. + */ + @Test + void downloadDirRelative() throws Exception { + setFile("build.gradle").toLines( + "plugins {", + " id 'com.diffplug.spotless'", + "}", + "repositories { mavenCentral() }", + "spotless {", + " format 'myrome', {", + " target '**/*.js'", + " rome('12.0.0').downloadDir('target/bin/rome')", + " }", + "}"); + setFile("rome_test.js").toResource("rome/js/fileBefore.js"); + assertTrue(!newFile("target/bin/rome").exists() || newFile("target/bin/rome").list().length == 0); + + var spotlessApply = gradleRunner().withArguments("--stacktrace", "spotlessApply").build(); + assertThat(spotlessApply.getOutput()).contains("BUILD SUCCESSFUL"); + assertFile("rome_test.js").sameAsResource("rome/js/fileAfter.js"); + assertEquals(2, newFile("target/bin/rome").list().length); + } + + /** + * Tests that the build fails when given Rome executable does not exist. + * + * @throws Exception When a test failure occurs. + */ + @Test + void failureWhenExeNotFound() throws Exception { + setFile("build.gradle").toLines( + "plugins {", + " id 'com.diffplug.spotless'", + "}", + "repositories { mavenCentral() }", + "spotless {", + " format 'myrome', {", + " target '**/*.js'", + " rome('12.0.0').pathToExe('rome/is/missing')", + " }", + "}"); + setFile("rome_test.js").toResource("rome/js/fileBefore.js"); + + var spotlessApply = gradleRunner().withArguments("--stacktrace", "spotlessApply").buildAndFail(); + assertThat(spotlessApply.getOutput()).contains("Build failed with an exception"); + assertFile("rome_test.js").sameAsResource("rome/js/fileBefore.js"); + assertThat(spotlessApply.getOutput()).contains("Could not create task ':spotlessMyromeApply'"); + assertThat(spotlessApply.getOutput()).contains("Rome executable does not exist"); + } + + /** + * Tests that the build fails when the input file could not be parsed. + * + * @throws Exception When a test failure occurs. + */ + @Test + void failureWhenNotParseable() throws Exception { + setFile("build.gradle").toLines( + "plugins {", + " id 'com.diffplug.spotless'", + "}", + "repositories { mavenCentral() }", + "spotless {", + " format 'myrome', {", + " target '**/*.js'", + " rome('12.0.0').language('json')", + " }", + "}"); + setFile("rome_test.js").toResource("rome/js/fileBefore.js"); + + var spotlessApply = gradleRunner().withArguments("--stacktrace", "spotlessApply").buildAndFail(); + assertThat(spotlessApply.getOutput()).contains("spotlessMyrome FAILED"); + assertFile("rome_test.js").sameAsResource("rome/js/fileBefore.js"); + assertThat(spotlessApply.getOutput()).contains("Format with errors is disabled."); + assertThat(spotlessApply.getOutput()).contains("Step 'rome' found problem in 'rome_test.js'"); + } +} diff --git a/plugin-maven/src/test/java/com/diffplug/spotless/maven/rome/RomeMavenTest.java b/plugin-maven/src/test/java/com/diffplug/spotless/maven/rome/RomeMavenTest.java index 76e297e704..7ca3f7d981 100644 --- a/plugin-maven/src/test/java/com/diffplug/spotless/maven/rome/RomeMavenTest.java +++ b/plugin-maven/src/test/java/com/diffplug/spotless/maven/rome/RomeMavenTest.java @@ -15,6 +15,7 @@ */ package com.diffplug.spotless.maven.rome; +import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.owasp.encoder.Encode.forXml; @@ -30,7 +31,7 @@ class RomeMavenTest extends MavenIntegrationHarness { * @throws Exception When a test failure occurs. */ @Test - public void testAsGenericStep() throws Exception { + void asGenericStep() throws Exception { writePomWithRomeSteps("**/*.js", "12.0.0"); setFile("rome_test.js").toResource("rome/js/fileBefore.js"); mavenRunner().withArguments("spotless:apply").runNoError(); @@ -43,7 +44,7 @@ public void testAsGenericStep() throws Exception { * @throws Exception When a test failure occurs. */ @Test - public void testAsJavaScriptStep() throws Exception { + void asJavaScriptStep() throws Exception { writePomWithJavascriptSteps("**/*.js", "12.0.0"); setFile("rome_test.js").toResource("rome/js/fileBefore.js"); mavenRunner().withArguments("spotless:apply").runNoError(); @@ -56,7 +57,7 @@ public void testAsJavaScriptStep() throws Exception { * @throws Exception When a test failure occurs. */ @Test - public void testAsJsonStep() throws Exception { + void asJsonStep() throws Exception { writePomWithJsonSteps("**/*.json", "12.0.0"); setFile("rome_test.json").toResource("rome/json/fileBefore.json"); mavenRunner().withArguments("spotless:apply").runNoError(); @@ -69,7 +70,7 @@ public void testAsJsonStep() throws Exception { * @throws Exception When a test failure occurs. */ @Test - public void testAsTypeScriptStep() throws Exception { + void asTypeScriptStep() throws Exception { writePomWithTypescriptSteps("**/*.ts", "12.0.0"); setFile("rome_test.ts").toResource("rome/ts/fileBefore.ts"); mavenRunner().withArguments("spotless:apply").runNoError(); @@ -82,7 +83,7 @@ public void testAsTypeScriptStep() throws Exception { * @throws Exception When a test failure occurs. */ @Test - public void testCanSetLanguageForGenericStep() throws Exception { + void canSetLanguageForGenericStep() throws Exception { writePomWithRomeSteps("**/*.nosj", "12.0.0json"); setFile("rome_test.nosj").toResource("rome/json/fileBefore.json"); mavenRunner().withArguments("spotless:apply").runNoError(); @@ -95,7 +96,7 @@ public void testCanSetLanguageForGenericStep() throws Exception { * @throws Exception When a test failure occurs. */ @Test - public void testConfigPathAbsolute() throws Exception { + void configPathAbsolute() throws Exception { var path = newFile("configs").getAbsolutePath(); writePomWithRomeSteps("**/*.js", "12.0.0" + forXml(path) + ""); @@ -112,7 +113,7 @@ public void testConfigPathAbsolute() throws Exception { * @throws Exception When a test failure occurs. */ @Test - public void testConfigPathLineWidth120() throws Exception { + void configPathLineWidth120() throws Exception { writePomWithRomeSteps("**/*.js", "12.0.0configs"); setFile("rome_test.js").toResource("rome/js/longLineBefore.js"); setFile("configs/rome.json").toResource("rome/config/line-width-120.json"); @@ -127,7 +128,7 @@ public void testConfigPathLineWidth120() throws Exception { * @throws Exception When a test failure occurs. */ @Test - public void testConfigPathLineWidth80() throws Exception { + void configPathLineWidth80() throws Exception { writePomWithRomeSteps("**/*.js", "12.0.0configs"); setFile("rome_test.js").toResource("rome/js/longLineBefore.js"); setFile("configs/rome.json").toResource("rome/config/line-width-80.json"); @@ -141,7 +142,7 @@ public void testConfigPathLineWidth80() throws Exception { * @throws Exception When a test failure occurs. */ @Test - public void testDownloadDirAbsolute() throws Exception { + void downloadDirAbsolute() throws Exception { var path = newFile("target/bin/rome").getAbsoluteFile().toString(); writePomWithRomeSteps("**/*.js", "12.0.0" + forXml(path) + ""); @@ -159,7 +160,7 @@ public void testDownloadDirAbsolute() throws Exception { * @throws Exception When a test failure occurs. */ @Test - public void testDownloadDirRelative() throws Exception { + void downloadDirRelative() throws Exception { writePomWithRomeSteps("**/*.js", "12.0.0target/bin/rome"); setFile("rome_test.js").toResource("rome/js/fileBefore.js"); @@ -175,13 +176,27 @@ public void testDownloadDirRelative() throws Exception { * @throws Exception When a test failure occurs. */ @Test - public void testFailureWhenNotParseable() throws Exception { + void failureWhenExeNotFound() throws Exception { + writePomWithRomeSteps("**/*.js", "12.0.0rome/is/missing"); + setFile("rome_test.js").toResource("rome/js/fileBefore.js"); + var result = mavenRunner().withArguments("spotless:apply").runHasError(); + assertFile("rome_test.js").sameAsResource("rome/js/fileBefore.js"); + assertThat(result.stdOutUtf8()).contains("Rome executable does not exist"); + } + + /** + * Tests that the build fails when the input file could not be parsed. + * + * @throws Exception When a test failure occurs. + */ + @Test + void failureWhenNotParseable() throws Exception { writePomWithRomeSteps("**/*.js", "12.0.0json"); setFile("rome_test.js").toResource("rome/js/fileBefore.js"); var result = mavenRunner().withArguments("spotless:apply").runHasError(); assertFile("rome_test.js").sameAsResource("rome/js/fileBefore.js"); - assertTrue(result.stdOutUtf8().contains("Format with errors is disabled.")); - assertTrue(result.stdOutUtf8().contains("Unable to format file")); - assertTrue(result.stdOutUtf8().contains("Step 'rome' found problem in 'rome_test.js'")); + assertThat(result.stdOutUtf8()).contains("Format with errors is disabled."); + assertThat(result.stdOutUtf8()).contains("Unable to format file"); + assertThat(result.stdOutUtf8()).contains("Step 'rome' found problem in 'rome_test.js'"); } } From 971824d7677254bff75bcdf00a198da4b50040c3 Mon Sep 17 00:00:00 2001 From: Andre Wachsmuth Date: Thu, 13 Apr 2023 07:09:50 +0100 Subject: [PATCH 23/33] Fix spotbugs issues --- .../rome/RomeExecutableDownloader.java | 11 +++++++-- .../com/diffplug/spotless/rome/RomeStep.java | 6 +++-- .../diffplug/spotless/maven/FileLocator.java | 23 +++++++++++++++---- 3 files changed, 31 insertions(+), 9 deletions(-) diff --git a/lib/src/main/java/com/diffplug/spotless/rome/RomeExecutableDownloader.java b/lib/src/main/java/com/diffplug/spotless/rome/RomeExecutableDownloader.java index c55a18f754..5f389f0342 100644 --- a/lib/src/main/java/com/diffplug/spotless/rome/RomeExecutableDownloader.java +++ b/lib/src/main/java/com/diffplug/spotless/rome/RomeExecutableDownloader.java @@ -117,7 +117,10 @@ public Path download(String version) throws IOException, InterruptedException { var url = getDownloadUrl(version, platform); var executablePath = getExecutablePath(version, platform); var checksumPath = getChecksumPath(executablePath); - Files.createDirectories(executablePath.getParent()); + var executableDir = executablePath.getParent(); + if (executableDir != null) { + Files.createDirectories(executableDir); + } logger.info("Attempting to download Rome from '{}' to '{}'", url, executablePath); var request = HttpRequest.newBuilder(URI.create(url)).GET().build(); var handler = BodyHandlers.ofFile(executablePath, WRITE_OPTIONS); @@ -270,7 +273,11 @@ private String getArchitectureCodeName(Architecture architecture) throws IOExcep * @return The path with the checksum for the given file. */ private Path getChecksumPath(Path file) { - return file.getParent().resolve(file.getFileName().toString() + ".md5"); + var parent = file.getParent(); + var base = parent != null ? parent : file; + var fileName = file.getFileName(); + var checksumName = fileName != null ? fileName.toString() + ".md5" : "checksum.md5"; + return base.resolve(checksumName); } /** diff --git a/lib/src/main/java/com/diffplug/spotless/rome/RomeStep.java b/lib/src/main/java/com/diffplug/spotless/rome/RomeStep.java index b0f17fafd6..d6a3e62669 100644 --- a/lib/src/main/java/com/diffplug/spotless/rome/RomeStep.java +++ b/lib/src/main/java/com/diffplug/spotless/rome/RomeStep.java @@ -134,7 +134,9 @@ private static void attemptToAddPosixPermission(Path file, PosixFilePermission p var newPermissions = new HashSet<>(Files.getPosixFilePermissions(file)); newPermissions.add(permission); Files.setPosixFilePermissions(file, newPermissions); - } catch (final Exception ignore) {} + } catch (final Exception ignore) { + logger.debug("Unable to add POSIX permission '{}' to file '{}'", permission, file); + } } /** @@ -440,7 +442,7 @@ private String resolveFileName(File file) { return "jsx".equals(ext) || "js".equals(ext) || "mjs".equals(ext) || "cjs".equals(ext) ? name : "file.js"; case "ts?": - return "tsx".equals(ext) || "ts".equals(ext) || "tjs".equals(ext) || "tjs".equals(ext) ? name + return "tsx".equals(ext) || "ts".equals(ext) || "mts".equals(ext) || "cts".equals(ext) ? name : "file.js"; case "js": return "js".equals(ext) || "mjs".equals(ext) || "cjs".equals(ext) ? name : "file.js"; diff --git a/plugin-maven/src/main/java/com/diffplug/spotless/maven/FileLocator.java b/plugin-maven/src/main/java/com/diffplug/spotless/maven/FileLocator.java index a8c3973dd3..0c66fe29cb 100644 --- a/plugin-maven/src/main/java/com/diffplug/spotless/maven/FileLocator.java +++ b/plugin-maven/src/main/java/com/diffplug/spotless/maven/FileLocator.java @@ -24,6 +24,7 @@ import java.security.NoSuchAlgorithmException; import java.util.Base64; import java.util.Objects; +import java.util.Optional; import org.codehaus.plexus.resource.ResourceManager; import org.codehaus.plexus.resource.loader.FileResourceCreationException; @@ -121,10 +122,22 @@ private static byte[] hash(String value) { } private static File findDataDir() { - // E.g. ~/.m2/repository/com/diffplug/spotless/spotless-plugin-maven/1.2.3/spotless-plugin-maven-1.2.3.jar - final var jarPath = Paths.get(AbstractRome.class.getProtectionDomain().getCodeSource().getLocation().getPath()); - final var base = jarPath.getParent().getParent().getParent(); - final var sub = base.resolve("spotless-data"); - return sub.toAbsolutePath().toFile(); + // JAR path is e.g. + // ~/.m2/repository/com/diffplug/spotless/spotless-plugin-maven/1.2.3/spotless-plugin-maven-1.2.3.jar + var codeSource = FileLocator.class.getProtectionDomain().getCodeSource(); + var location = codeSource != null ? codeSource.getLocation() : null; + var path = location != null ? location.getPath() : null; + var jarPath = path != null && !path.isBlank() ? Paths.get(path) : null; + var parent1 = jarPath != null ? jarPath.getParent() : null; + var parent2 = parent1 != null ? parent1.getParent() : null; + var base = parent2 != null ? parent2.getParent() : null; + var sub = base != null ? base.resolve("spotless-data") : null; + if (sub != null) { + return sub.toAbsolutePath().toFile(); + } + else { + var home = Paths.get(System.getenv("user.home")); + return home.resolve(".rome").toAbsolutePath().toFile(); + } } } From 071df538360ea93fdaf3898de86f8d6d5008b63e Mon Sep 17 00:00:00 2001 From: Andre Wachsmuth Date: Thu, 13 Apr 2023 07:16:43 +0100 Subject: [PATCH 24/33] Run spotlessApply again --- .../main/java/com/diffplug/spotless/maven/FileLocator.java | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/plugin-maven/src/main/java/com/diffplug/spotless/maven/FileLocator.java b/plugin-maven/src/main/java/com/diffplug/spotless/maven/FileLocator.java index 0c66fe29cb..fdf91ef68f 100644 --- a/plugin-maven/src/main/java/com/diffplug/spotless/maven/FileLocator.java +++ b/plugin-maven/src/main/java/com/diffplug/spotless/maven/FileLocator.java @@ -24,15 +24,12 @@ import java.security.NoSuchAlgorithmException; import java.util.Base64; import java.util.Objects; -import java.util.Optional; import org.codehaus.plexus.resource.ResourceManager; import org.codehaus.plexus.resource.loader.FileResourceCreationException; import org.codehaus.plexus.resource.loader.ResourceNotFoundException; import org.codehaus.plexus.util.FileUtils; -import com.diffplug.spotless.maven.rome.AbstractRome; - public class FileLocator { static final String TMP_RESOURCE_FILE_PREFIX = "spotless-resource-"; @@ -134,8 +131,7 @@ private static File findDataDir() { var sub = base != null ? base.resolve("spotless-data") : null; if (sub != null) { return sub.toAbsolutePath().toFile(); - } - else { + } else { var home = Paths.get(System.getenv("user.home")); return home.resolve(".rome").toAbsolutePath().toFile(); } From 20729b8272c211f21f241037c307090356044135 Mon Sep 17 00:00:00 2001 From: Andre Wachsmuth Date: Sat, 22 Apr 2023 10:59:18 +0100 Subject: [PATCH 25/33] Fix conversion of file URL to Path, improve exception handling --- .../diffplug/spotless/maven/FileLocator.java | 43 ++++++++++++------- 1 file changed, 28 insertions(+), 15 deletions(-) diff --git a/plugin-maven/src/main/java/com/diffplug/spotless/maven/FileLocator.java b/plugin-maven/src/main/java/com/diffplug/spotless/maven/FileLocator.java index fdf91ef68f..91aae3a055 100644 --- a/plugin-maven/src/main/java/com/diffplug/spotless/maven/FileLocator.java +++ b/plugin-maven/src/main/java/com/diffplug/spotless/maven/FileLocator.java @@ -19,6 +19,9 @@ import static java.nio.charset.StandardCharsets.UTF_8; import java.io.File; +import java.net.URISyntaxException; +import java.nio.file.FileSystemNotFoundException; +import java.nio.file.Path; import java.nio.file.Paths; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; @@ -119,21 +122,31 @@ private static byte[] hash(String value) { } private static File findDataDir() { - // JAR path is e.g. - // ~/.m2/repository/com/diffplug/spotless/spotless-plugin-maven/1.2.3/spotless-plugin-maven-1.2.3.jar - var codeSource = FileLocator.class.getProtectionDomain().getCodeSource(); - var location = codeSource != null ? codeSource.getLocation() : null; - var path = location != null ? location.getPath() : null; - var jarPath = path != null && !path.isBlank() ? Paths.get(path) : null; - var parent1 = jarPath != null ? jarPath.getParent() : null; - var parent2 = parent1 != null ? parent1.getParent() : null; - var base = parent2 != null ? parent2.getParent() : null; - var sub = base != null ? base.resolve("spotless-data") : null; - if (sub != null) { - return sub.toAbsolutePath().toFile(); - } else { - var home = Paths.get(System.getenv("user.home")); - return home.resolve(".rome").toAbsolutePath().toFile(); + try { + // JAR path is e.g. + // ~/.m2/repository/com/diffplug/spotless/spotless-plugin-maven/1.2.3/spotless-plugin-maven-1.2.3.jar + var codeSource = FileLocator.class.getProtectionDomain().getCodeSource(); + var location = codeSource != null ? codeSource.getLocation() : null; + var locationUri = location != null ? location.toURI() : null; + var jarPath = locationUri != null && "file".equals(locationUri.getScheme()) ? Path.of(locationUri) : null; + var parent1 = jarPath != null ? jarPath.getParent() : null; + var parent2 = parent1 != null ? parent1.getParent() : null; + var base = parent2 != null ? parent2.getParent() : null; + var sub = base != null ? base.resolve("spotless-data") : null; + if (sub != null) { + return sub.toAbsolutePath().toFile(); + } else { + return findUserHome(); + } + } catch (final SecurityException e) { + return findUserHome(); + } catch (final URISyntaxException | FileSystemNotFoundException | IllegalArgumentException e) { + throw new RuntimeException("Unable to determine data directory in local Maven repository", e); } } + + private static File findUserHome() { + var home = Paths.get(System.getenv("user.home")); + return home.resolve(".rome").toAbsolutePath().toFile(); + } } From bc3261ba30de82db7f81adc79da684837fdc8f3a Mon Sep 17 00:00:00 2001 From: Andre Wachsmuth Date: Sat, 22 Apr 2023 11:13:22 +0100 Subject: [PATCH 26/33] use sha-256 for downloaded rome binary checksum --- .../diffplug/spotless/rome/RomeExecutableDownloader.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/src/main/java/com/diffplug/spotless/rome/RomeExecutableDownloader.java b/lib/src/main/java/com/diffplug/spotless/rome/RomeExecutableDownloader.java index 5f389f0342..1307655e63 100644 --- a/lib/src/main/java/com/diffplug/spotless/rome/RomeExecutableDownloader.java +++ b/lib/src/main/java/com/diffplug/spotless/rome/RomeExecutableDownloader.java @@ -47,7 +47,7 @@ final class RomeExecutableDownloader { /** * The checksum algorithm to use for checking the integrity of downloaded files. */ - private static final String CHECKSUM_ALGORITHM = "MD5"; + private static final String CHECKSUM_ALGORITHM = "SHA256"; /** * The pattern for {@link String#format(String, Object...) String.format()} for @@ -229,7 +229,7 @@ private boolean checkFileWithChecksum(Path filePath) { * * @param file File to process. * @param algorithm The checksum algorithm to use. - * @return The MD5 checksum of the given file. + * @return The checksum of the given file. * @throws IOException When the file does not exist or could not be read. */ private String computeChecksum(Path file, String algorithm) throws IOException { @@ -276,7 +276,7 @@ private Path getChecksumPath(Path file) { var parent = file.getParent(); var base = parent != null ? parent : file; var fileName = file.getFileName(); - var checksumName = fileName != null ? fileName.toString() + ".md5" : "checksum.md5"; + var checksumName = fileName != null ? fileName.toString() + ".sha256" : "checksum.sha256"; return base.resolve(checksumName); } From b516c65d267afa387a53d06c48f11a1d8603c70f Mon Sep 17 00:00:00 2001 From: Andre Wachsmuth Date: Tue, 16 May 2023 06:32:17 +0100 Subject: [PATCH 27/33] Remove whitespace on empty line --- .../src/main/java/com/diffplug/spotless/maven/FileLocator.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugin-maven/src/main/java/com/diffplug/spotless/maven/FileLocator.java b/plugin-maven/src/main/java/com/diffplug/spotless/maven/FileLocator.java index 91aae3a055..088c35a3d0 100644 --- a/plugin-maven/src/main/java/com/diffplug/spotless/maven/FileLocator.java +++ b/plugin-maven/src/main/java/com/diffplug/spotless/maven/FileLocator.java @@ -144,7 +144,7 @@ private static File findDataDir() { throw new RuntimeException("Unable to determine data directory in local Maven repository", e); } } - + private static File findUserHome() { var home = Paths.get(System.getenv("user.home")); return home.resolve(".rome").toAbsolutePath().toFile(); From 39d61bc1bee17d937ea390880aa2a048684f8858 Mon Sep 17 00:00:00 2001 From: Andre Wachsmuth Date: Tue, 16 May 2023 06:56:40 +0100 Subject: [PATCH 28/33] SHA256 -> SHA-256 --- .../com/diffplug/spotless/rome/RomeExecutableDownloader.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/main/java/com/diffplug/spotless/rome/RomeExecutableDownloader.java b/lib/src/main/java/com/diffplug/spotless/rome/RomeExecutableDownloader.java index 1307655e63..578916a54b 100644 --- a/lib/src/main/java/com/diffplug/spotless/rome/RomeExecutableDownloader.java +++ b/lib/src/main/java/com/diffplug/spotless/rome/RomeExecutableDownloader.java @@ -47,7 +47,7 @@ final class RomeExecutableDownloader { /** * The checksum algorithm to use for checking the integrity of downloaded files. */ - private static final String CHECKSUM_ALGORITHM = "SHA256"; + private static final String CHECKSUM_ALGORITHM = "SHA-256"; /** * The pattern for {@link String#format(String, Object...) String.format()} for From 6c8bff185e91e3115eda160b20255b1d25f23ac9 Mon Sep 17 00:00:00 2001 From: Ned Twigg Date: Tue, 16 May 2023 23:10:56 -0700 Subject: [PATCH 29/33] Update changelogs. --- CHANGES.md | 3 +-- plugin-gradle/CHANGES.md | 7 ++----- plugin-maven/CHANGES.md | 4 +--- 3 files changed, 4 insertions(+), 10 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 9096e9735f..faf42c0cee 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -12,7 +12,7 @@ We adhere to the [keepachangelog](https://keepachangelog.com/en/1.0.0/) format ( ## [Unreleased] ### Added * `Jvm.Support` now accepts `-SNAPSHOT` versions, treated as the non`-SNAPSHOT`. ([#1583](https://github.com/diffplug/spotless/issues/1583)) -* Support Rome as a formatter for JavaScript and TypeScript code. Adds a new `rome` step to `javascript` and `typescript` formatter configurations. https://github.com/diffplug/spotless/pull/1663 +* Support Rome as a formatter for JavaScript and TypeScript code. Adds a new `rome` step to `javascript` and `typescript` formatter configurations. ([#1663](https://github.com/diffplug/spotless/pull/1663)) ### Fixed * When P2 download fails, indicate the responsible formatter. ([#1698](https://github.com/diffplug/spotless/issues/1698)) ### Changes @@ -29,7 +29,6 @@ We adhere to the [keepachangelog](https://keepachangelog.com/en/1.0.0/) format ( * Bump default `diktat` version `1.2.4.2` -> `1.2.5`. ([#1631](https://github.com/diffplug/spotless/pull/1631)) * Bump default `flexmark` version `0.62.2` -> `0.64.0`. ([#1302](https://github.com/diffplug/spotless/pull/1302)) * Bump default `googleJavaFormat` version `1.15.0` -> `1.16.0`. ([#1630](https://github.com/diffplug/spotless/pull/1630)) - * Bump default `scalafmt` version `3.7.1` -> `3.7.3`. ([#1584](https://github.com/diffplug/spotless/pull/1584)) * Bump default Eclipse formatters for the 2023-03 release. ([#1662](https://github.com/diffplug/spotless/pull/1662)) * JDT and GrEclipse `4.26` -> `4.27` diff --git a/plugin-gradle/CHANGES.md b/plugin-gradle/CHANGES.md index e10542f364..5d68193da9 100644 --- a/plugin-gradle/CHANGES.md +++ b/plugin-gradle/CHANGES.md @@ -4,10 +4,9 @@ We adhere to the [keepachangelog](https://keepachangelog.com/en/1.0.0/) format ( ## [Unreleased] ### Added -* Support Rome as a formatter for JavaScript and TypeScript code. Adds a new `rome` step to `javascript` and `typescript` formatter configurations. https://github.com/diffplug/spotless/pull/1663 - +* Support Rome as a formatter for JavaScript and TypeScript code. Adds a new `rome` step to `javascript` and `typescript` formatter configurations. ([#1663](https://github.com/diffplug/spotless/pull/1663)) ### Fixed -* Added `@DisableCachingByDefault` to `RegisterDependenciesTask`. +* Added `@DisableCachingByDefault` to `RegisterDependenciesTask`. ([#1666](https://github.com/diffplug/spotless/pull/1666)) * When P2 download fails, indicate the responsible formatter. ([#1698](https://github.com/diffplug/spotless/issues/1698)) ### Changes * Bump default sortpom version to latest `3.0.0` -> `3.2.1`. ([#1675](https://github.com/diffplug/spotless/pull/1675)) @@ -27,7 +26,6 @@ We adhere to the [keepachangelog](https://keepachangelog.com/en/1.0.0/) format ( Mirrors are selected by prefix match, for example `https://download.eclipse.org/eclipse/updates/4.26/` will be redirected to `https://some.internal.mirror/eclipse/eclipse/updates/4.26/`. The same configuration exists for `greclipse` and `eclipseCdt`. * The `style` option in Palantir Java Format ([#1654](https://github.com/diffplug/spotless/pull/1654)). - ### Fixed * Stop using deprecated conventions when used in Gradle >= `7.1`. ([#1618](https://github.com/diffplug/spotless/pull/1618)) ### Changes @@ -36,7 +34,6 @@ We adhere to the [keepachangelog](https://keepachangelog.com/en/1.0.0/) format ( * Bump default `diktat` version `1.2.4.2` -> `1.2.5`. ([#1631](https://github.com/diffplug/spotless/pull/1631)) * Bump default `flexmark` version `0.62.2` -> `0.64.0`. ([#1302](https://github.com/diffplug/spotless/pull/1302)) * Bump default `googleJavaFormat` version `1.15.0` -> `1.16.0`. ([#1630](https://github.com/diffplug/spotless/pull/1630)) - * Bump default `scalafmt` version `3.7.1` -> `3.7.3`. ([#1584](https://github.com/diffplug/spotless/pull/1584)) * Bump default Eclipse formatters for the 2023-03 release. ([#1662](https://github.com/diffplug/spotless/pull/1662)) * JDT and GrEclipse `4.26` -> `4.27` diff --git a/plugin-maven/CHANGES.md b/plugin-maven/CHANGES.md index fca3c4263e..d5744acb77 100644 --- a/plugin-maven/CHANGES.md +++ b/plugin-maven/CHANGES.md @@ -4,7 +4,7 @@ We adhere to the [keepachangelog](https://keepachangelog.com/en/1.0.0/) format ( ## [Unreleased] ### Added -* Support Rome as a formatter for JavaScript and TypeScript code. Adds a new `rome` step to `javascript` and `typescript` formatter configurations. https://github.com/diffplug/spotless/pull/1663### Fixed +* Support Rome as a formatter for JavaScript and TypeScript code. Adds a new `rome` step to `javascript` and `typescript` formatter configurations. ([#1663](https://github.com/diffplug/spotless/pull/1663)) ### Fixed * `palantir` step now accepts a `style` parameter, which is documentation had already claimed to do. ([#1694](https://github.com/diffplug/spotless/pull/1694)) * When P2 download fails, indicate the responsible formatter. ([#1698](https://github.com/diffplug/spotless/issues/1698)) @@ -16,7 +16,6 @@ We adhere to the [keepachangelog](https://keepachangelog.com/en/1.0.0/) format ( * `removeUnusedImport` can be configured to rely on `cleanthat-javaparser-unnecessaryimport`. Default remains `google-java-format`. ([#1589](https://github.com/diffplug/spotless/pull/1589)) * The `style` option in Palantir Java Format ([#1654](https://github.com/diffplug/spotless/pull/1654)). * Added formatter for Gherkin feature files ([#1649](https://github.com/diffplug/spotless/issues/1649)). - * Fix non deterministic computation of cache fingerprint when using multiple formatters. ([#1643](https://github.com/diffplug/spotless/pull/1643) fixes [#1642](https://github.com/diffplug/spotless/pull/1642)) ### Changes * **POTENTIALLY BREAKING** Drop support for `googleJavaFormat` versions < `1.8`. ([#1630](https://github.com/diffplug/spotless/pull/1630)) @@ -24,7 +23,6 @@ We adhere to the [keepachangelog](https://keepachangelog.com/en/1.0.0/) format ( * Bump default `diktat` version `1.2.4.2` -> `1.2.5`. ([#1631](https://github.com/diffplug/spotless/pull/1631)) * Bump default `flexmark` version `0.62.2` -> `0.64.0`. ([#1302](https://github.com/diffplug/spotless/pull/1302)) * Bump default `googleJavaFormat` version `1.15.0` -> `1.16.0`. ([#1630](https://github.com/diffplug/spotless/pull/1630)) - * Bump default `scalafmt` version `3.7.1` -> `3.7.3`. ([#1584](https://github.com/diffplug/spotless/pull/1584)) * Bump default Eclipse formatters for the 2023-03 release. ([#1662](https://github.com/diffplug/spotless/pull/1662)) * JDT and GrEclipse `4.26` -> `4.27` From ed4e5c237507665dc0ea918206ff3dced3d8c181 Mon Sep 17 00:00:00 2001 From: Ned Twigg Date: Tue, 16 May 2023 23:14:13 -0700 Subject: [PATCH 30/33] Move RomeStepConfig into its own file since it's so big. --- .../gradle/spotless/FormatExtension.java | 239 --------------- .../gradle/spotless/RomeStepConfig.java | 273 ++++++++++++++++++ 2 files changed, 273 insertions(+), 239 deletions(-) create mode 100644 plugin-gradle/src/main/java/com/diffplug/gradle/spotless/RomeStepConfig.java diff --git a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/FormatExtension.java b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/FormatExtension.java index c429c469d2..4fa74bc2ea 100644 --- a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/FormatExtension.java +++ b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/FormatExtension.java @@ -22,7 +22,6 @@ import java.io.Serializable; import java.nio.charset.Charset; import java.nio.file.Files; -import java.nio.file.Paths; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -38,7 +37,6 @@ import org.gradle.api.Action; import org.gradle.api.GradleException; import org.gradle.api.Project; -import org.gradle.api.artifacts.repositories.MavenArtifactRepository; import org.gradle.api.file.ConfigurableFileTree; import org.gradle.api.file.FileCollection; import org.gradle.api.plugins.BasePlugin; @@ -65,7 +63,6 @@ import com.diffplug.spotless.generic.TrimTrailingWhitespaceStep; import com.diffplug.spotless.npm.NpmPathResolver; import com.diffplug.spotless.npm.PrettierFormatterStep; -import com.diffplug.spotless.rome.RomeStep; import groovy.lang.Closure; @@ -652,242 +649,6 @@ protected FormatterStep createStep() { } } - public abstract static class RomeStepConfig> { - /** - * 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. - */ - @Nullable - private Object 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. - * Defaults to - * ~/.m2/repository/com/diffplug/spotless/spotless-data/rome. - */ - @Nullable - private Object downloadDir; - - /** - * Optional path to the Rome executable. Either a version or a - * pathToExe should be specified. When not given, an attempt is - * made to download the executable for the given version from the network. When - * given, the executable is used and the version parameter is - * ignored. - *

    - * When an absolute path is given, that path is used as-is. When a relative path - * is given, it is resolved against the project's base directory. When only a - * file name (i.e. without any slashes or back slash path separators such as - * {@code rome}) is given, this is interpreted as the name of a command with - * executable that is in your {@code path} environment variable. Use - * {@code ./executable-name} if you want to use an executable in the project's - * base directory. - */ - @Nullable - private Object pathToExe; - - /** A reference to the Gradle project for which spotless is executed. */ - private final Project project; - - /** Replaces the current Rome formatter step with the given step. */ - private final Consumer replaceStep; - - /** - * Rome version to download, applies only when no pathToExe is - * specified explicitly. Either a version or a - * pathToExe should be specified. When not given, a default known - * version is used. For stable builds, it is recommended that you always set the - * version explicitly. This parameter is ignored when you specify a - * pathToExe explicitly. - */ - @Nullable - private String version; - - protected RomeStepConfig(Project project, Consumer replaceStep, String version) { - this.project = requireNonNull(project); - this.replaceStep = requireNonNull(replaceStep); - this.version = version; - } - - /** - * 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. - * @return This step for further configuration. - */ - public Self configPath(Object configPath) { - this.configPath = configPath; - replaceStep(); - return getThis(); - } - - /** - * Optional directory where the downloaded Rome executable is placed. If this is - * a relative path, it is resolved against the project's base directory. - * Defaults to - * ~/.m2/repository/com/diffplug/spotless/spotless-data/rome. - * @return This step for further configuration. - */ - public Self downloadDir(Object downloadDir) { - this.downloadDir = downloadDir; - replaceStep(); - return getThis(); - } - - /** - * Optional path to the Rome executable. Overwrites the configured version. No - * attempt is made to download the Rome executable from the network. - *

    - * When an absolute path is given, that path is used as-is. When a relative path - * is given, it is resolved against the project's base directory. When only a - * file name (i.e. without any slashes or back slash path separators such as - * {@code rome}) is given, this is interpreted as the name of a command with - * executable that is in your {@code path} environment variable. Use - * {@code ./executable-name} if you want to use an executable in the project's - * base directory. - * @return This step for further configuration. - */ - public Self pathToExe(Object pathToExe) { - this.pathToExe = pathToExe; - replaceStep(); - return getThis(); - } - - /** - * Creates a new formatter step that formats code by calling the Rome - * executable, using the current configuration. - * - * @return A new formatter step for the Rome formatter. - */ - protected FormatterStep createStep() { - var builder = newBuilder(); - if (configPath != null) { - var resolvedConfigPath = project.file(configPath); - builder.withConfigPath(resolvedConfigPath.toString()); - } - builder.withLanguage(getLanguage()); - return builder.create(); - } - - /** - * Gets the language (syntax) of the input files to format. When - * null or the empty string, the language is detected automatically - * from the file name. Currently the following languages are supported by Rome: - *

      - *
    • js (JavaScript)
    • - *
    • jsx (JavaScript + JSX)
    • - *
    • js? (JavaScript or JavaScript + JSX, depending on the file - * extension)
    • - *
    • ts (TypeScript)
    • - *
    • tsx (TypeScript + JSX)
    • - *
    • ts? (TypeScript or TypeScript + JSX, depending on the file - * extension)
    • - *
    • json (JSON)
    • - *
    - * - * @return The language of the input files. - */ - protected abstract String getLanguage(); - - /** - * @return This Rome config instance. - */ - protected abstract Self getThis(); - - /** - * Creates a new Rome step and replaces the existing Rome step in the list of - * format steps. - */ - protected void replaceStep() { - replaceStep.accept(createStep()); - } - - /** - * Finds the data directory that can be used for storing shared data such as - * Rome executable globally. This is a directory in the local repository, e.g. - * ~/.m2/repository/com/diffplus/spotless/spotless-data. - * - * @return The directory for storing shared data. - */ - private File findDataDir() { - var currentRepo = project.getRepositories().stream() - .filter(r -> r instanceof MavenArtifactRepository) - .map(r -> (MavenArtifactRepository) r) - .filter(r -> "file".equals(r.getUrl().getScheme())) - .findAny().orElse(null); - // Temporarily add mavenLocal() repository to get its file URL - var localRepo = currentRepo != null ? (MavenArtifactRepository) currentRepo : project.getRepositories().mavenLocal(); - try { - // e.g. ~/.m2/repository/ - var repoPath = Paths.get(localRepo.getUrl().getPath()); - var dataPath = repoPath.resolve("com").resolve("diffplug").resolve("spotless").resolve("spotless-data"); - return dataPath.toAbsolutePath().toFile(); - } finally { - // Remove mavenLocal() repository again if it was not part of the project - if (currentRepo == null) { - project.getRepositories().remove(localRepo); - } - } - } - - /** - * A new builder for configuring a Rome step that either downloads the Rome - * executable with the given version from the network, or uses the executable - * from the given path. - * - * @return A builder for a Rome step. - */ - private RomeStep newBuilder() { - if (pathToExe != null) { - var resolvedPathToExe = resolvePathToExe(); - return RomeStep.withExePath(resolvedPathToExe); - } else { - var downloadDir = resolveDownloadDir(); - return RomeStep.withExeDownload(version, downloadDir); - } - } - - /** - * 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 - * the user's path. Otherwise resolve the executable path 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 Rome executable. - */ - private String resolvePathToExe() { - var fileNameOnly = pathToExe instanceof String && Paths.get(pathToExe.toString()).getNameCount() == 1; - if (fileNameOnly) { - return pathToExe.toString(); - } else { - return project.file(pathToExe).toString(); - } - } - - /** - * Resolves the directory to use for storing downloaded Rome executable. When a - * {@link #downloadDir} is given, use that directory, resolved against the - * current project's directory. Otherwise, use the {@code Rome} sub folder in - * the shared data directory. - * - * @param config Configuration for this step. - * @return The download directory for the Rome executable. - */ - private String resolveDownloadDir() { - if (downloadDir != null) { - return project.file(downloadDir).toString(); - } else { - return findDataDir().toPath().resolve("rome").toString(); - } - } - } - /** * Generic Rome formatter step that detects the language of the input file from * the file name. It should be specified as a formatter step for a generic diff --git a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/RomeStepConfig.java b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/RomeStepConfig.java new file mode 100644 index 0000000000..99519f1766 --- /dev/null +++ b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/RomeStepConfig.java @@ -0,0 +1,273 @@ +/* + * Copyright 2023 DiffPlug + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.diffplug.gradle.spotless; + +import static java.util.Objects.requireNonNull; + +import java.io.File; +import java.nio.file.Paths; +import java.util.function.Consumer; + +import javax.annotation.Nullable; + +import org.gradle.api.Project; +import org.gradle.api.artifacts.repositories.MavenArtifactRepository; + +import com.diffplug.spotless.FormatterStep; +import com.diffplug.spotless.rome.RomeStep; + +public abstract class RomeStepConfig> { + /** + * 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. + */ + @Nullable + private Object 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. + * Defaults to + * ~/.m2/repository/com/diffplug/spotless/spotless-data/rome. + */ + @Nullable + private Object downloadDir; + + /** + * Optional path to the Rome executable. Either a version or a + * pathToExe should be specified. When not given, an attempt is + * made to download the executable for the given version from the network. When + * given, the executable is used and the version parameter is + * ignored. + *

    + * When an absolute path is given, that path is used as-is. When a relative path + * is given, it is resolved against the project's base directory. When only a + * file name (i.e. without any slashes or back slash path separators such as + * {@code rome}) is given, this is interpreted as the name of a command with + * executable that is in your {@code path} environment variable. Use + * {@code ./executable-name} if you want to use an executable in the project's + * base directory. + */ + @Nullable + private Object pathToExe; + + /** + * A reference to the Gradle project for which spotless is executed. + */ + private final Project project; + + /** + * Replaces the current Rome formatter step with the given step. + */ + private final Consumer replaceStep; + + /** + * Rome version to download, applies only when no pathToExe is + * specified explicitly. Either a version or a + * pathToExe should be specified. When not given, a default known + * version is used. For stable builds, it is recommended that you always set the + * version explicitly. This parameter is ignored when you specify a + * pathToExe explicitly. + */ + @Nullable + private String version; + + protected RomeStepConfig(Project project, Consumer replaceStep, String version) { + this.project = requireNonNull(project); + this.replaceStep = requireNonNull(replaceStep); + this.version = version; + } + + /** + * 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. + * + * @return This step for further configuration. + */ + public Self configPath(Object configPath) { + this.configPath = configPath; + replaceStep(); + return getThis(); + } + + /** + * Optional directory where the downloaded Rome executable is placed. If this is + * a relative path, it is resolved against the project's base directory. + * Defaults to + * ~/.m2/repository/com/diffplug/spotless/spotless-data/rome. + * + * @return This step for further configuration. + */ + public Self downloadDir(Object downloadDir) { + this.downloadDir = downloadDir; + replaceStep(); + return getThis(); + } + + /** + * Optional path to the Rome executable. Overwrites the configured version. No + * attempt is made to download the Rome executable from the network. + *

    + * When an absolute path is given, that path is used as-is. When a relative path + * is given, it is resolved against the project's base directory. When only a + * file name (i.e. without any slashes or back slash path separators such as + * {@code rome}) is given, this is interpreted as the name of a command with + * executable that is in your {@code path} environment variable. Use + * {@code ./executable-name} if you want to use an executable in the project's + * base directory. + * + * @return This step for further configuration. + */ + public Self pathToExe(Object pathToExe) { + this.pathToExe = pathToExe; + replaceStep(); + return getThis(); + } + + /** + * Creates a new formatter step that formats code by calling the Rome + * executable, using the current configuration. + * + * @return A new formatter step for the Rome formatter. + */ + protected FormatterStep createStep() { + var builder = newBuilder(); + if (configPath != null) { + var resolvedConfigPath = project.file(configPath); + builder.withConfigPath(resolvedConfigPath.toString()); + } + builder.withLanguage(getLanguage()); + return builder.create(); + } + + /** + * Gets the language (syntax) of the input files to format. When + * null or the empty string, the language is detected automatically + * from the file name. Currently the following languages are supported by Rome: + *

      + *
    • js (JavaScript)
    • + *
    • jsx (JavaScript + JSX)
    • + *
    • js? (JavaScript or JavaScript + JSX, depending on the file + * extension)
    • + *
    • ts (TypeScript)
    • + *
    • tsx (TypeScript + JSX)
    • + *
    • ts? (TypeScript or TypeScript + JSX, depending on the file + * extension)
    • + *
    • json (JSON)
    • + *
    + * + * @return The language of the input files. + */ + protected abstract String getLanguage(); + + /** + * @return This Rome config instance. + */ + protected abstract Self getThis(); + + /** + * Creates a new Rome step and replaces the existing Rome step in the list of + * format steps. + */ + protected void replaceStep() { + replaceStep.accept(createStep()); + } + + /** + * Finds the data directory that can be used for storing shared data such as + * Rome executable globally. This is a directory in the local repository, e.g. + * ~/.m2/repository/com/diffplus/spotless/spotless-data. + * + * @return The directory for storing shared data. + */ + private File findDataDir() { + var currentRepo = project.getRepositories().stream() + .filter(r -> r instanceof MavenArtifactRepository) + .map(r -> (MavenArtifactRepository) r) + .filter(r -> "file".equals(r.getUrl().getScheme())) + .findAny().orElse(null); + // Temporarily add mavenLocal() repository to get its file URL + var localRepo = currentRepo != null ? (MavenArtifactRepository) currentRepo : project.getRepositories().mavenLocal(); + try { + // e.g. ~/.m2/repository/ + var repoPath = Paths.get(localRepo.getUrl().getPath()); + var dataPath = repoPath.resolve("com").resolve("diffplug").resolve("spotless").resolve("spotless-data"); + return dataPath.toAbsolutePath().toFile(); + } finally { + // Remove mavenLocal() repository again if it was not part of the project + if (currentRepo == null) { + project.getRepositories().remove(localRepo); + } + } + } + + /** + * A new builder for configuring a Rome step that either downloads the Rome + * executable with the given version from the network, or uses the executable + * from the given path. + * + * @return A builder for a Rome step. + */ + private RomeStep newBuilder() { + if (pathToExe != null) { + var resolvedPathToExe = resolvePathToExe(); + return RomeStep.withExePath(resolvedPathToExe); + } else { + var downloadDir = resolveDownloadDir(); + return RomeStep.withExeDownload(version, downloadDir); + } + } + + /** + * 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 + * the user's path. Otherwise resolve the executable path 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 Rome executable. + */ + private String resolvePathToExe() { + var fileNameOnly = pathToExe instanceof String && Paths.get(pathToExe.toString()).getNameCount() == 1; + if (fileNameOnly) { + return pathToExe.toString(); + } else { + return project.file(pathToExe).toString(); + } + } + + /** + * Resolves the directory to use for storing downloaded Rome executable. When a + * {@link #downloadDir} is given, use that directory, resolved against the + * current project's directory. Otherwise, use the {@code Rome} sub folder in + * the shared data directory. + * + * @param config Configuration for this step. + * @return The download directory for the Rome executable. + */ + private String resolveDownloadDir() { + if (downloadDir != null) { + return project.file(downloadDir).toString(); + } else { + return findDataDir().toPath().resolve("rome").toString(); + } + } +} From 09d8498fc3763934018af35c659cf6831f1cc1bc Mon Sep 17 00:00:00 2001 From: Ned Twigg Date: Tue, 16 May 2023 23:18:41 -0700 Subject: [PATCH 31/33] Fix root path issue in the Rome formatter. --- .../java/com/diffplug/gradle/spotless/RomeStepConfig.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/RomeStepConfig.java b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/RomeStepConfig.java index 99519f1766..ab55152015 100644 --- a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/RomeStepConfig.java +++ b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/RomeStepConfig.java @@ -26,6 +26,7 @@ import org.gradle.api.Project; import org.gradle.api.artifacts.repositories.MavenArtifactRepository; +import com.diffplug.spotless.FileSignature; import com.diffplug.spotless.FormatterStep; import com.diffplug.spotless.rome.RomeStep; @@ -207,7 +208,11 @@ private File findDataDir() { var localRepo = currentRepo != null ? (MavenArtifactRepository) currentRepo : project.getRepositories().mavenLocal(); try { // e.g. ~/.m2/repository/ - var repoPath = Paths.get(localRepo.getUrl().getPath()); + var path = localRepo.getUrl().getPath(); + if (FileSignature.machineIsWin() && path.startsWith("/")) { + path = path.substring(1); + } + var repoPath = Paths.get(path); var dataPath = repoPath.resolve("com").resolve("diffplug").resolve("spotless").resolve("spotless-data"); return dataPath.toAbsolutePath().toFile(); } finally { From d8913c4fc92e58c3a738c58729df20488e0aff5e Mon Sep 17 00:00:00 2001 From: Ned Twigg Date: Tue, 16 May 2023 23:20:24 -0700 Subject: [PATCH 32/33] Another changelog fix. --- plugin-maven/CHANGES.md | 1 + 1 file changed, 1 insertion(+) diff --git a/plugin-maven/CHANGES.md b/plugin-maven/CHANGES.md index d5744acb77..993404f1fe 100644 --- a/plugin-maven/CHANGES.md +++ b/plugin-maven/CHANGES.md @@ -16,6 +16,7 @@ We adhere to the [keepachangelog](https://keepachangelog.com/en/1.0.0/) format ( * `removeUnusedImport` can be configured to rely on `cleanthat-javaparser-unnecessaryimport`. Default remains `google-java-format`. ([#1589](https://github.com/diffplug/spotless/pull/1589)) * The `style` option in Palantir Java Format ([#1654](https://github.com/diffplug/spotless/pull/1654)). * Added formatter for Gherkin feature files ([#1649](https://github.com/diffplug/spotless/issues/1649)). +### Fixed * Fix non deterministic computation of cache fingerprint when using multiple formatters. ([#1643](https://github.com/diffplug/spotless/pull/1643) fixes [#1642](https://github.com/diffplug/spotless/pull/1642)) ### Changes * **POTENTIALLY BREAKING** Drop support for `googleJavaFormat` versions < `1.8`. ([#1630](https://github.com/diffplug/spotless/pull/1630)) From a56e7e603aeccf123c5d8a15fd3ad33129c640f6 Mon Sep 17 00:00:00 2001 From: Ned Twigg Date: Wed, 17 May 2023 00:18:12 -0700 Subject: [PATCH 33/33] Use Path.of(URI) instead of manual string wrangling. --- .../com/diffplug/gradle/spotless/RomeStepConfig.java | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/RomeStepConfig.java b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/RomeStepConfig.java index ab55152015..f739e922d2 100644 --- a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/RomeStepConfig.java +++ b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/RomeStepConfig.java @@ -18,6 +18,7 @@ import static java.util.Objects.requireNonNull; import java.io.File; +import java.nio.file.Path; import java.nio.file.Paths; import java.util.function.Consumer; @@ -26,7 +27,6 @@ import org.gradle.api.Project; import org.gradle.api.artifacts.repositories.MavenArtifactRepository; -import com.diffplug.spotless.FileSignature; import com.diffplug.spotless.FormatterStep; import com.diffplug.spotless.rome.RomeStep; @@ -208,11 +208,7 @@ private File findDataDir() { var localRepo = currentRepo != null ? (MavenArtifactRepository) currentRepo : project.getRepositories().mavenLocal(); try { // e.g. ~/.m2/repository/ - var path = localRepo.getUrl().getPath(); - if (FileSignature.machineIsWin() && path.startsWith("/")) { - path = path.substring(1); - } - var repoPath = Paths.get(path); + var repoPath = Path.of(localRepo.getUrl()); var dataPath = repoPath.resolve("com").resolve("diffplug").resolve("spotless").resolve("spotless-data"); return dataPath.toAbsolutePath().toFile(); } finally { @@ -246,8 +242,6 @@ private RomeStep newBuilder() { * the user's path. Otherwise resolve the executable path 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 Rome executable. */ private String resolvePathToExe() { @@ -265,7 +259,6 @@ private String resolvePathToExe() { * current project's directory. Otherwise, use the {@code Rome} sub folder in * the shared data directory. * - * @param config Configuration for this step. * @return The download directory for the Rome executable. */ private String resolveDownloadDir() {