Skip to content

Commit 17b05d7

Browse files
committed
Add rome() step to generic format for Gradle plugin
1 parent 7461c04 commit 17b05d7

File tree

2 files changed

+315
-0
lines changed
  • plugin-gradle/src/main/java/com/diffplug/gradle/spotless
  • plugin-maven/src/main/java/com/diffplug/spotless/maven/generic

2 files changed

+315
-0
lines changed

plugin-gradle/src/main/java/com/diffplug/gradle/spotless/FormatExtension.java

Lines changed: 309 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import java.io.Serializable;
2323
import java.nio.charset.Charset;
2424
import java.nio.file.Files;
25+
import java.nio.file.Paths;
2526
import java.util.ArrayList;
2627
import java.util.Arrays;
2728
import java.util.Collection;
@@ -37,6 +38,7 @@
3738
import org.gradle.api.Action;
3839
import org.gradle.api.GradleException;
3940
import org.gradle.api.Project;
41+
import org.gradle.api.artifacts.repositories.MavenArtifactRepository;
4042
import org.gradle.api.file.ConfigurableFileTree;
4143
import org.gradle.api.file.FileCollection;
4244
import org.gradle.api.plugins.BasePlugin;
@@ -63,6 +65,7 @@
6365
import com.diffplug.spotless.generic.TrimTrailingWhitespaceStep;
6466
import com.diffplug.spotless.npm.NpmPathResolver;
6567
import com.diffplug.spotless.npm.PrettierFormatterStep;
68+
import com.diffplug.spotless.rome.RomeStep;
6669

6770
import groovy.lang.Closure;
6871

@@ -648,6 +651,296 @@ protected FormatterStep createStep() {
648651
this.prettierConfig));
649652
}
650653
}
654+
655+
public abstract static class RomeStepConfig<Self extends RomeStepConfig<Self>> {
656+
/**
657+
* Optional path to the directory with configuration file for Rome. The file
658+
* must be named {@code rome.json}. When none is given, the default
659+
* configuration is used. If this is a relative path, it is resolved against the
660+
* project's base directory.
661+
*/
662+
@Nullable
663+
private Object configPath;
664+
665+
/**
666+
* Optional directory where the downloaded Rome executable is placed. If this is
667+
* a relative path, it is resolved against the project's base directory.
668+
* Defaults to
669+
* <code>~/.m2/repository/com/diffplug/spotless/spotless-data/rome</code>.
670+
*/
671+
@Nullable
672+
private Object downloadDir;
673+
674+
/**
675+
* Optional path to the Rome executable. Either a <code>version</code> or a
676+
* <code>pathToExe</code> should be specified. When not given, an attempt is
677+
* made to download the executable for the given version from the network. When
678+
* given, the executable is used and the <code>version</code> parameter is
679+
* ignored.
680+
* <p>
681+
* When an absolute path is given, that path is used as-is. When a relative path
682+
* is given, it is resolved against the project's base directory. When only a
683+
* file name (i.e. without any slashes or back slash path separators such as
684+
* {@code rome}) is given, this is interpreted as the name of a command with
685+
* executable that is in your {@code path} environment variable. Use
686+
* {@code ./executable-name} if you want to use an executable in the project's
687+
* base directory.
688+
*/
689+
@Nullable
690+
private Object pathToExe;
691+
692+
/** A reference to the Gradle project for which spotless is executed. */
693+
private final Project project;
694+
695+
/** Replaces the current Rome formatter step with the given step. */
696+
private final Consumer<FormatterStep> replaceStep;
697+
698+
/**
699+
* Rome version to download, applies only when no <code>pathToExe</code> is
700+
* specified explicitly. Either a <code>version</code> or a
701+
* <code>pathToExe</code> should be specified. When not given, a default known
702+
* version is used. For stable builds, it is recommended that you always set the
703+
* version explicitly. This parameter is ignored when you specify a
704+
* <code>pathToExe</code> explicitly.
705+
*/
706+
@Nullable
707+
private String version;
708+
709+
protected RomeStepConfig(Project project, Consumer<FormatterStep> replaceStep, String version) {
710+
this.project = requireNonNull(project);
711+
this.replaceStep = requireNonNull(replaceStep);
712+
this.version = version;
713+
}
714+
715+
/**
716+
* Optional path to the directory with configuration file for Rome. The file
717+
* must be named {@code rome.json}. When none is given, the default
718+
* configuration is used. If this is a relative path, it is resolved against the
719+
* project's base directory.
720+
* @return This step for further configuration.
721+
*/
722+
public Self configPath(Object configPath) {
723+
this.configPath = configPath;
724+
replaceStep();
725+
return getThis();
726+
}
727+
728+
/**
729+
* Optional directory where the downloaded Rome executable is placed. If this is
730+
* a relative path, it is resolved against the project's base directory.
731+
* Defaults to
732+
* <code>~/.m2/repository/com/diffplug/spotless/spotless-data/rome</code>.
733+
* @return This step for further configuration.
734+
*/
735+
public Self downloadDir(Object downloadDir) {
736+
this.downloadDir = downloadDir;
737+
replaceStep();
738+
return getThis();
739+
}
740+
741+
/**
742+
* Optional path to the Rome executable. Overwrites the configured version. No
743+
* attempt is made to download the Rome executable from the network.
744+
* <p>
745+
* When an absolute path is given, that path is used as-is. When a relative path
746+
* is given, it is resolved against the project's base directory. When only a
747+
* file name (i.e. without any slashes or back slash path separators such as
748+
* {@code rome}) is given, this is interpreted as the name of a command with
749+
* executable that is in your {@code path} environment variable. Use
750+
* {@code ./executable-name} if you want to use an executable in the project's
751+
* base directory.
752+
* @return This step for further configuration.
753+
*/
754+
public Self pathToExe(Object pathToExe) {
755+
this.pathToExe = pathToExe;
756+
replaceStep();
757+
return getThis();
758+
}
759+
760+
/**
761+
* Creates a new formatter step that formats code by calling the Rome
762+
* executable, using the current configuration.
763+
*
764+
* @return A new formatter step for the Rome formatter.
765+
*/
766+
protected FormatterStep createStep() {
767+
var builder = newBuilder();
768+
if (configPath != null) {
769+
var resolvedConfigPath = project.file(configPath);
770+
builder.withConfigPath(resolvedConfigPath.toString());
771+
}
772+
builder.withLanguage(getLanguage());
773+
var rome = builder.build();
774+
return rome.create();
775+
}
776+
777+
/**
778+
* Gets the language (syntax) of the input files to format. When
779+
* <code>null</code> or the empty string, the language is detected automatically
780+
* from the file name. Currently the following languages are supported by Rome:
781+
* <ul>
782+
* <li>js (JavaScript)</li>
783+
* <li>jsx (JavaScript + JSX)</li>
784+
* <li>js? (JavaScript or JavaScript + JSX, depending on the file
785+
* extension)</li>
786+
* <li>ts (TypeScript)</li>
787+
* <li>tsx (TypeScript + JSX)</li>
788+
* <li>ts? (TypeScript or TypeScript + JSX, depending on the file
789+
* extension)</li>
790+
* <li>json (JSON)</li>
791+
* </ul>
792+
*
793+
* @return The language of the input files.
794+
*/
795+
protected abstract String getLanguage();
796+
797+
/**
798+
* @return This Rome config instance.
799+
*/
800+
protected abstract Self getThis();
801+
802+
/**
803+
* Creates a new Rome step and replaces the existing Rome step in the list of
804+
* format steps.
805+
*/
806+
protected void replaceStep() {
807+
replaceStep.accept(createStep());
808+
}
809+
810+
/**
811+
* Finds the data directory that can be used for storing shared data such as
812+
* Rome executable globally. This is a directory in the local repository, e.g.
813+
* <code>~/.m2/repository/com/diffplus/spotless/spotless-data<code>.
814+
*
815+
* @return The directory for storing shared data.
816+
*/
817+
private File findDataDir() {
818+
var currentRepo = project.getRepositories().stream()
819+
.filter(r -> r instanceof MavenArtifactRepository)
820+
.map(r -> (MavenArtifactRepository) r)
821+
.filter(r -> "file".equals(r.getUrl().getScheme()))
822+
.findAny().orElse(null);
823+
// Temporarily add mavenLocal() repository to get its file URL
824+
var localRepo = currentRepo != null ? (MavenArtifactRepository)currentRepo : project.getRepositories().mavenLocal();
825+
try {
826+
// e.g. ~/.m2/repository/
827+
var repoPath = Paths.get(localRepo.getUrl().getPath());
828+
var dataPath = repoPath.resolve("com").resolve("diffplus").resolve("spotless").resolve("spotless-data");
829+
return dataPath.toAbsolutePath().toFile();
830+
}
831+
finally {
832+
// Remove mavenLocal() repository again if it was not part of the project
833+
if (currentRepo == null) {
834+
project.getRepositories().remove(localRepo);
835+
}
836+
}
837+
}
838+
839+
/**
840+
* A new builder for configuring a Rome step that either downloads the Rome
841+
* executable with the given version from the network, or uses the executable
842+
* from the given path.
843+
*
844+
* @return A builder for a Rome step.
845+
*/
846+
private RomeStep.Builder newBuilder() {
847+
if (pathToExe != null) {
848+
var resolvedPathToExe = resolvePathToExe();
849+
return RomeStep.withExePath(resolvedPathToExe);
850+
} else {
851+
var downloadDir = resolveDownloadDir();
852+
return RomeStep.withExeDownload(version, downloadDir);
853+
}
854+
}
855+
856+
/**
857+
* Resolves the path to the Rome executable. When the path is only a file name,
858+
* do not perform any resolution and interpret it as a command that must be on
859+
* the user's path. Otherwise resolve the executable path against the project's
860+
* base directory.
861+
*
862+
* @param config Configuration from the Maven Mojo execution with details about
863+
* the currently executed project.
864+
* @return The resolved path to the Rome executable.
865+
*/
866+
private String resolvePathToExe() {
867+
var fileNameOnly = pathToExe instanceof String && Paths.get(pathToExe.toString()).getNameCount() == 1;
868+
if (fileNameOnly) {
869+
return pathToExe.toString();
870+
} else {
871+
return project.file(pathToExe).toString();
872+
}
873+
}
874+
875+
/**
876+
* Resolves the directory to use for storing downloaded Rome executable. When a
877+
* {@link #downloadDir} is given, use that directory, resolved against the
878+
* current project's directory. Otherwise, use the {@code Rome} sub folder in
879+
* the shared data directory.
880+
*
881+
* @param config Configuration for this step.
882+
* @return The download directory for the Rome executable.
883+
*/
884+
private String resolveDownloadDir() {
885+
if (downloadDir != null) {
886+
return project.file(downloadDir).toString();
887+
} else {
888+
return findDataDir().toPath().resolve("rome").toString();
889+
}
890+
}
891+
}
892+
893+
/**
894+
* Generic Rome formatter step that detects the language of the input file from
895+
* the file name. It should be specified as a formatter step for a generic
896+
* <code>format{ ... }</code>.
897+
*/
898+
public class RomeGeneric extends RomeStepConfig<RomeGeneric> {
899+
@Nullable
900+
String language;
901+
902+
/**
903+
* Creates a new Rome config that downloads the Rome executable for the given version from the network.
904+
* @param version Rome version to use. The default version is used when <code>null</code>.
905+
*/
906+
public RomeGeneric(String version) {
907+
super(getProject(), FormatExtension.this::replaceStep, version);
908+
}
909+
910+
/**
911+
* Sets the language (syntax) of the input files to format. When
912+
* <code>null</code> or the empty string, the language is detected automatically
913+
* from the file name. Currently the following languages are supported by Rome:
914+
* <ul>
915+
* <li>js (JavaScript)</li>
916+
* <li>jsx (JavaScript + JSX)</li>
917+
* <li>js? (JavaScript or JavaScript + JSX, depending on the file
918+
* extension)</li>
919+
* <li>ts (TypeScript)</li>
920+
* <li>tsx (TypeScript + JSX)</li>
921+
* <li>ts? (TypeScript or TypeScript + JSX, depending on the file
922+
* extension)</li>
923+
* <li>json (JSON)</li>
924+
* </ul>
925+
* @param language The language of the files to format.
926+
* @return This step for further configuration.
927+
*/
928+
public RomeGeneric language(String language) {
929+
this.language = language;
930+
replaceStep();
931+
return this;
932+
}
933+
934+
@Override
935+
protected String getLanguage() {
936+
return language;
937+
}
938+
939+
@Override
940+
protected RomeGeneric getThis() {
941+
return this;
942+
}
943+
}
651944

652945
/** Uses the default version of prettier. */
653946
public PrettierConfig prettier() {
@@ -665,6 +958,22 @@ public PrettierConfig prettier(Map<String, String> devDependencies) {
665958
addStep(prettierConfig.createStep());
666959
return prettierConfig;
667960
}
961+
962+
/**
963+
* Defaults to downloading the default Rome version from the network. To work
964+
* offline, you can specify the path to the Rome executable via
965+
* {@code rome().pathToExe(...)}.
966+
*/
967+
public RomeGeneric rome() {
968+
return rome(null);
969+
}
970+
971+
/** Downloads the given Rome version from the network. */
972+
public RomeGeneric rome(String version) {
973+
var romeConfig = new RomeGeneric(version);
974+
addStep(romeConfig.createStep());
975+
return romeConfig;
976+
}
668977

669978
/** Uses the default version of clang-format. */
670979
public ClangFormatConfig clangFormat() {

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,18 @@ public class Rome extends AbstractRome {
1515
* <code>null</code> or the empty string, the language is detected automatically
1616
* from the file name. Currently the following languages are supported by Rome:
1717
* <ul>
18+
* <ul>
1819
* <li>js (JavaScript)</li>
1920
* <li>jsx (JavaScript + JSX)</li>
21+
* <li>js? (JavaScript or JavaScript + JSX, depending on the file
22+
* extension)</li>
2023
* <li>ts (TypeScript)</li>
2124
* <li>tsx (TypeScript + JSX)</li>
25+
* <li>ts? (TypeScript or TypeScript + JSX, depending on the file
26+
* extension)</li>
2227
* <li>json (JSON)</li>
2328
* </ul>
29+
* </ul>
2430
*
2531
* @return The language of the input files.
2632
*/

0 commit comments

Comments
 (0)