Skip to content

Commit 4f5e4d3

Browse files
committed
eslint: add support for xo standard typescript ruleset
1 parent c20d3bb commit 4f5e4d3

File tree

18 files changed

+216
-30
lines changed

18 files changed

+216
-30
lines changed

lib/src/main/java/com/diffplug/spotless/npm/EslintConfig.java

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,12 @@
1818
import java.io.File;
1919
import java.io.IOException;
2020
import java.io.Serializable;
21+
import java.util.Collections;
22+
import java.util.Map;
23+
import java.util.Optional;
24+
import java.util.TreeMap;
2125

26+
import javax.annotation.Nonnull;
2227
import javax.annotation.Nullable;
2328

2429
import com.diffplug.spotless.FileSignature;
@@ -39,11 +44,18 @@ public class EslintConfig implements Serializable {
3944

4045
private final String eslintConfigJs;
4146

42-
public EslintConfig(@Nullable File eslintConfigPath, @Nullable String eslintConfigJs) {
47+
@SuppressFBWarnings("SE_TRANSIENT_FIELD_NOT_RESTORED")
48+
private final transient Map<File, Optional<String>> additionalConfigFiles; // key: source-file, value: target-remapping path relative to package.json (if needed)
49+
50+
private final FileSignature additionalConfigFilesSignature;
51+
52+
public EslintConfig(@Nullable File eslintConfigPath, @Nullable String eslintConfigJs, Map<File, Optional<String>> additionalConfigFiles) {
4353
try {
4454
this.eslintConfigPath = eslintConfigPath;
4555
this.eslintConfigPathSignature = eslintConfigPath != null ? FileSignature.signAsList(this.eslintConfigPath) : FileSignature.signAsList();
4656
this.eslintConfigJs = eslintConfigJs;
57+
this.additionalConfigFiles = additionalConfigFiles != null ? new TreeMap<>(additionalConfigFiles) : Collections.emptyMap();
58+
this.additionalConfigFilesSignature = FileSignature.signAsList(this.additionalConfigFiles.keySet().toArray(new File[0]));
4759
} catch (IOException e) {
4860
throw ThrowingEx.asRuntime(e);
4961
}
@@ -58,4 +70,9 @@ public File getEslintConfigPath() {
5870
public String getEslintConfigJs() {
5971
return eslintConfigJs;
6072
}
73+
74+
@Nonnull
75+
public Map<File, Optional<String>> getAdditionalConfigFiles() {
76+
return additionalConfigFiles;
77+
}
6178
}

lib/src/main/java/com/diffplug/spotless/npm/EslintFormatterStep.java

Lines changed: 28 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
import java.util.LinkedHashMap;
2626
import java.util.Map;
2727
import java.util.Objects;
28+
import java.util.Optional;
2829
import java.util.TreeMap;
2930

3031
import javax.annotation.Nonnull;
@@ -113,12 +114,13 @@ public static Map<String, String> defaultDevDependenciesWithEslint(String versio
113114
return Collections.singletonMap("eslint", version);
114115
}
115116

116-
public static FormatterStep create(Map<String, String> devDependencies, Provisioner provisioner, File buildDir, NpmPathResolver npmPathResolver, EslintConfig eslintConfig) {
117+
public static FormatterStep create(Map<String, String> devDependencies, Provisioner provisioner, File projectDir, File buildDir, NpmPathResolver npmPathResolver, EslintConfig eslintConfig) {
117118
requireNonNull(devDependencies);
118119
requireNonNull(provisioner);
120+
requireNonNull(projectDir);
119121
requireNonNull(buildDir);
120122
return FormatterStep.createLazy(NAME,
121-
() -> new State(NAME, devDependencies, buildDir, npmPathResolver, eslintConfig),
123+
() -> new State(NAME, devDependencies, projectDir, buildDir, npmPathResolver, eslintConfig),
122124
State::createFormatterFunc);
123125
}
124126

@@ -127,7 +129,7 @@ private static class State extends NpmFormatterStepStateBase implements Serializ
127129
private static final long serialVersionUID = -539537027004745812L;
128130
private final EslintConfig eslintConfig;
129131

130-
State(String stepName, Map<String, String> devDependencies, File buildDir, NpmPathResolver npmPathResolver, EslintConfig eslintConfig) throws IOException {
132+
State(String stepName, Map<String, String> devDependencies, File projectDir, File buildDir, NpmPathResolver npmPathResolver, EslintConfig eslintConfig) throws IOException {
131133
super(stepName,
132134
new NpmConfig(
133135
replaceDevDependencies(
@@ -138,20 +140,29 @@ private static class State extends NpmFormatterStepStateBase implements Serializ
138140
"/com/diffplug/spotless/npm/common-serve.js",
139141
"/com/diffplug/spotless/npm/eslint-serve.js"),
140142
npmPathResolver.resolveNpmrcContent()),
143+
projectDir,
141144
buildDir,
142145
npmPathResolver.resolveNpmExecutable());
143146
this.eslintConfig = localCopyFiles(requireNonNull(eslintConfig));
144147
}
145148

146149
private EslintConfig localCopyFiles(EslintConfig orig) {
147-
if (orig.getEslintConfigPath() == null) {
148-
return orig;
149-
}
150-
// If a config file is provided, we need to make sure it is at the same location as the node modules
151-
// as eslint will try to resolve plugin/config names relatively to the config file location
150+
// If any config files are provided, we need to make sure they are at the same location as the node modules
151+
// as eslint will try to resolve plugin/config names relatively to the config file location and some
152+
// eslint configs contain relative paths to additional config files (such as tsconfig.json e.g.)
152153
FormattedPrinter.SYSOUT.print("Copying config file <%s> to <%s> and using the copy", orig.getEslintConfigPath(), nodeModulesDir);
153154
File configFileCopy = NpmResourceHelper.copyFileToDir(orig.getEslintConfigPath(), nodeModulesDir);
154-
return new EslintConfig(configFileCopy, orig.getEslintConfigJs());
155+
156+
for (Map.Entry<File, Optional<String>> additionalConfigFile : orig.getAdditionalConfigFiles().entrySet()) {
157+
FormattedPrinter.SYSOUT.print("Copying additional config file <%s> to <%s> at subpath <%s> and using the copy", additionalConfigFile.getKey(), nodeModulesDir, additionalConfigFile.getValue());
158+
159+
if (additionalConfigFile.getValue().isPresent()) {
160+
NpmResourceHelper.copyFileToDirAtSubpath(additionalConfigFile.getKey(), nodeModulesDir, additionalConfigFile.getValue().get());
161+
} else {
162+
NpmResourceHelper.copyFileToDir(additionalConfigFile.getKey(), nodeModulesDir);
163+
}
164+
}
165+
return new EslintConfig(configFileCopy, orig.getEslintConfigJs(), orig.getAdditionalConfigFiles());
155166
}
156167

157168
@Override
@@ -161,9 +172,7 @@ public FormatterFunc createFormatterFunc() {
161172
FormattedPrinter.SYSOUT.print("creating formatter function (starting server)");
162173
ServerProcessInfo eslintRestServer = npmRunServer();
163174
EslintRestService restService = new EslintRestService(eslintRestServer.getBaseUrl());
164-
165-
// String prettierConfigOptions = restService.resolveConfig(this.prettierConfig.getPrettierConfigPath(), this.prettierConfig.getOptions());
166-
return Closeable.ofDangerous(() -> endServer(restService, eslintRestServer), new EslintFilePathPassingFormatterFunc(nodeModulesDir, eslintConfig, restService));
175+
return Closeable.ofDangerous(() -> endServer(restService, eslintRestServer), new EslintFilePathPassingFormatterFunc(projectDir, nodeModulesDir, eslintConfig, restService));
167176
} catch (IOException e) {
168177
throw ThrowingEx.asRuntime(e);
169178
}
@@ -182,12 +191,14 @@ private void endServer(BaseNpmRestService restService, ServerProcessInfo restSer
182191
}
183192

184193
private static class EslintFilePathPassingFormatterFunc implements FormatterFunc.NeedsFile {
194+
private final File projectDir;
185195
private final File nodeModulesDir;
186196
private final EslintConfig eslintConfig;
187197
private final EslintRestService restService;
188198

189-
public EslintFilePathPassingFormatterFunc(File nodeModulesDir, EslintConfig eslintConfig, EslintRestService restService) {
190-
this.nodeModulesDir = nodeModulesDir;
199+
public EslintFilePathPassingFormatterFunc(File projectDir, File nodeModulesDir, EslintConfig eslintConfig, EslintRestService restService) {
200+
this.projectDir = requireNonNull(projectDir);
201+
this.nodeModulesDir = requireNonNull(nodeModulesDir);
191202
this.eslintConfig = requireNonNull(eslintConfig);
192203
this.restService = requireNonNull(restService);
193204
}
@@ -214,6 +225,9 @@ private void setConfigToCallOptions(Map<FormatOption, Object> eslintCallOptions)
214225
eslintCallOptions.put(FormatOption.ESLINT_OVERRIDE_CONFIG, eslintConfig.getEslintConfigJs());
215226
}
216227
eslintCallOptions.put(FormatOption.NODE_MODULES_DIR, nodeModulesDir.getAbsolutePath());
228+
229+
// TODO (simschla, 09.12.22): maybe only add this if there is a typescript config active? (TBD: how to detect)
230+
eslintCallOptions.put(FormatOption.TS_CONFIG_ROOT_DIR, nodeModulesDir.toPath().relativize(projectDir.toPath()).toString());
217231
}
218232
}
219233
}

lib/src/main/java/com/diffplug/spotless/npm/EslintRestService.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ public String format(String fileContent, Map<FormatOption, Object> formatOptions
3535
}
3636

3737
enum FormatOption {
38-
ESLINT_OVERRIDE_CONFIG("eslint_override_config"), ESLINT_OVERRIDE_CONFIG_FILE("eslint_override_config_file"), FILE_PATH("file_path"), NODE_MODULES_DIR("node_modules_dir");
38+
ESLINT_OVERRIDE_CONFIG("eslint_override_config"), ESLINT_OVERRIDE_CONFIG_FILE("eslint_override_config_file"), FILE_PATH("file_path"), NODE_MODULES_DIR("node_modules_dir"), TS_CONFIG_ROOT_DIR("ts_config_root_dir");
3939

4040
private final String backendName;
4141

lib/src/main/java/com/diffplug/spotless/npm/NpmFormatterStepStateBase.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,13 +51,17 @@ abstract class NpmFormatterStepStateBase implements Serializable {
5151
@SuppressFBWarnings("SE_TRANSIENT_FIELD_NOT_RESTORED")
5252
private final transient File npmExecutable;
5353

54+
@SuppressFBWarnings("SE_TRANSIENT_FIELD_NOT_RESTORED")
55+
public final transient File projectDir;
56+
5457
private final NpmConfig npmConfig;
5558

5659
private final String stepName;
5760

58-
protected NpmFormatterStepStateBase(String stepName, NpmConfig npmConfig, File buildDir, File npm) throws IOException {
61+
protected NpmFormatterStepStateBase(String stepName, NpmConfig npmConfig, File projectDir, File buildDir, File npm) throws IOException {
5962
this.stepName = requireNonNull(stepName);
6063
this.npmConfig = requireNonNull(npmConfig);
64+
this.projectDir = requireNonNull(projectDir);
6165
this.npmExecutable = npm;
6266

6367
NodeServerLayout layout = prepareNodeServer(buildDir);

lib/src/main/java/com/diffplug/spotless/npm/NpmResourceHelper.java

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,12 @@
1818
import java.io.*;
1919
import java.nio.charset.StandardCharsets;
2020
import java.nio.file.Files;
21+
import java.nio.file.Path;
22+
import java.nio.file.Paths;
2123
import java.nio.file.StandardCopyOption;
2224
import java.time.Duration;
2325
import java.util.Arrays;
26+
import java.util.Objects;
2427
import java.util.concurrent.TimeoutException;
2528
import java.util.stream.Collectors;
2629

@@ -103,10 +106,18 @@ static void awaitReadableFile(File file, Duration maxWaitTime) throws TimeoutExc
103106
}
104107

105108
static File copyFileToDir(File file, File targetDir) {
109+
return copyFileToDirAtSubpath(file, targetDir, file.getName());
110+
}
111+
112+
static File copyFileToDirAtSubpath(File file, File targetDir, String relativePath) {
113+
Objects.requireNonNull(relativePath);
106114
try {
107-
File copiedFile = new File(targetDir, file.getName());
108-
Files.copy(file.toPath(), copiedFile.toPath(), StandardCopyOption.REPLACE_EXISTING);
109-
return copiedFile;
115+
// create file pointing to relativePath in targetDir
116+
final Path relativeTargetFile = Paths.get(targetDir.getAbsolutePath(), relativePath);
117+
assertDirectoryExists(relativeTargetFile.getParent().toFile());
118+
119+
Files.copy(file.toPath(), relativeTargetFile, StandardCopyOption.REPLACE_EXISTING);
120+
return relativeTargetFile.toFile();
110121
} catch (IOException e) {
111122
throw ThrowingEx.asRuntime(e);
112123
}

lib/src/main/java/com/diffplug/spotless/npm/PrettierFormatterStep.java

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -49,12 +49,12 @@ public static final Map<String, String> defaultDevDependenciesWithPrettier(Strin
4949
return Collections.singletonMap("prettier", version);
5050
}
5151

52-
public static FormatterStep create(Map<String, String> devDependencies, Provisioner provisioner, File buildDir, NpmPathResolver npmPathResolver, PrettierConfig prettierConfig) {
52+
public static FormatterStep create(Map<String, String> devDependencies, Provisioner provisioner, File projectDir, File buildDir, NpmPathResolver npmPathResolver, PrettierConfig prettierConfig) {
5353
requireNonNull(devDependencies);
5454
requireNonNull(provisioner);
5555
requireNonNull(buildDir);
5656
return FormatterStep.createLazy(NAME,
57-
() -> new State(NAME, devDependencies, buildDir, npmPathResolver, prettierConfig),
57+
() -> new State(NAME, devDependencies, projectDir, buildDir, npmPathResolver, prettierConfig),
5858
State::createFormatterFunc);
5959
}
6060

@@ -63,7 +63,7 @@ private static class State extends NpmFormatterStepStateBase implements Serializ
6363
private static final long serialVersionUID = -539537027004745812L;
6464
private final PrettierConfig prettierConfig;
6565

66-
State(String stepName, Map<String, String> devDependencies, File buildDir, NpmPathResolver npmPathResolver, PrettierConfig prettierConfig) throws IOException {
66+
State(String stepName, Map<String, String> devDependencies, File projectDir, File buildDir, NpmPathResolver npmPathResolver, PrettierConfig prettierConfig) throws IOException {
6767
super(stepName,
6868
new NpmConfig(
6969
replaceDevDependencies(
@@ -74,6 +74,7 @@ private static class State extends NpmFormatterStepStateBase implements Serializ
7474
"/com/diffplug/spotless/npm/common-serve.js",
7575
"/com/diffplug/spotless/npm/prettier-serve.js"),
7676
npmPathResolver.resolveNpmrcContent()),
77+
projectDir,
7778
buildDir,
7879
npmPathResolver.resolveNpmExecutable());
7980
this.prettierConfig = requireNonNull(prettierConfig);

lib/src/main/java/com/diffplug/spotless/npm/TsFmtFormatterStep.java

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,11 +40,11 @@ public class TsFmtFormatterStep {
4040

4141
public static final String NAME = "tsfmt-format";
4242

43-
public static FormatterStep create(Map<String, String> versions, Provisioner provisioner, File buildDir, NpmPathResolver npmPathResolver, @Nullable TypedTsFmtConfigFile configFile, @Nullable Map<String, Object> inlineTsFmtSettings) {
43+
public static FormatterStep create(Map<String, String> versions, Provisioner provisioner, File projectDir, File buildDir, NpmPathResolver npmPathResolver, @Nullable TypedTsFmtConfigFile configFile, @Nullable Map<String, Object> inlineTsFmtSettings) {
4444
requireNonNull(provisioner);
4545
requireNonNull(buildDir);
4646
return FormatterStep.createLazy(NAME,
47-
() -> new State(NAME, versions, buildDir, npmPathResolver, configFile, inlineTsFmtSettings),
47+
() -> new State(NAME, versions, projectDir, buildDir, npmPathResolver, configFile, inlineTsFmtSettings),
4848
State::createFormatterFunc);
4949
}
5050

@@ -71,7 +71,7 @@ public static class State extends NpmFormatterStepStateBase implements Serializa
7171
@Nullable
7272
private final TypedTsFmtConfigFile configFile;
7373

74-
public State(String stepName, Map<String, String> versions, File buildDir, NpmPathResolver npmPathResolver, @Nullable TypedTsFmtConfigFile configFile, @Nullable Map<String, Object> inlineTsFmtSettings) throws IOException {
74+
public State(String stepName, Map<String, String> versions, File projectDir, File buildDir, NpmPathResolver npmPathResolver, @Nullable TypedTsFmtConfigFile configFile, @Nullable Map<String, Object> inlineTsFmtSettings) throws IOException {
7575
super(stepName,
7676
new NpmConfig(
7777
replaceDevDependencies(NpmResourceHelper.readUtf8StringFromClasspath(TsFmtFormatterStep.class, "/com/diffplug/spotless/npm/tsfmt-package.json"), new TreeMap<>(versions)),
@@ -80,6 +80,7 @@ public State(String stepName, Map<String, String> versions, File buildDir, NpmPa
8080
"/com/diffplug/spotless/npm/common-serve.js",
8181
"/com/diffplug/spotless/npm/tsfmt-serve.js"),
8282
npmPathResolver.resolveNpmrcContent()),
83+
projectDir,
8384
buildDir,
8485
npmPathResolver.resolveNpmExecutable());
8586
this.buildDir = requireNonNull(buildDir);

lib/src/main/resources/com/diffplug/spotless/npm/eslint-serve.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,22 @@ app.post("/eslint/format", async (req, res) => {
2424
fix: true,
2525
useEslintrc: false, // would result in (gradle) cache issues
2626
resolvePluginsRelativeTo: format_data.node_modules_dir
27+
// baseConfig: {
28+
// parserOptions: {
29+
// tsconfigRootDir: '../../',
30+
// }
31+
// }
2732
};
2833

34+
if (format_data.ts_config_root_dir) {
35+
ESLintOptions.baseConfig = {
36+
parserOptions: {
37+
tsconfigRootDir: format_data.ts_config_root_dir
38+
}
39+
};
40+
// res.status(501).send("Resolved ts config root dir: " + format_data.ts_config_root_dir);
41+
}
42+
2943

3044
if (ESLintOverrideConfigFile) {
3145
ESLintOptions.overrideConfigFile = ESLintOverrideConfigFile;

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

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,10 @@
2727
import java.util.LinkedHashMap;
2828
import java.util.List;
2929
import java.util.Map;
30+
import java.util.Optional;
3031
import java.util.Random;
3132
import java.util.TreeMap;
33+
import java.util.stream.Collectors;
3234

3335
import javax.annotation.Nullable;
3436
import javax.inject.Inject;
@@ -583,6 +585,7 @@ FormatterStep createStep() {
583585
return PrettierFormatterStep.create(
584586
devDependencies,
585587
provisioner(),
588+
project.getProjectDir(),
586589
project.getBuildDir(),
587590
new NpmPathResolver(npmFileOrNull(), npmrcFileOrNull(), project.getProjectDir(), project.getRootDir()),
588591
new com.diffplug.spotless.npm.PrettierConfig(
@@ -618,6 +621,8 @@ public class EslintFormatExtension extends NpmStepConfig<EslintFormatExtension>
618621
@Nullable
619622
String configJs = null;
620623

624+
List<AdditionalEslintConfig> additionalConfigs = new ArrayList<>();
625+
621626
public EslintFormatExtension(Map<String, String> devDependencies) {
622627
this.devDependencies.putAll(requireNonNull(devDependencies));
623628
}
@@ -640,19 +645,44 @@ public EslintFormatExtension configFile(Object configFilePath) {
640645
return this;
641646
}
642647

648+
protected void additionalConfigFilePath(Object sourceFile, String relativeTargetPath) {
649+
this.additionalConfigs.add(new AdditionalEslintConfig(sourceFile, relativeTargetPath));
650+
}
651+
643652
public FormatterStep createStep() {
644653
final Project project = getProject();
645654

646655
return EslintFormatterStep.create(
647656
devDependencies,
648657
provisioner(),
658+
project.getProjectDir(),
649659
project.getBuildDir(),
650660
new NpmPathResolver(npmFileOrNull(), npmrcFileOrNull(), project.getProjectDir(), project.getRootDir()),
651661
eslintConfig());
652662
}
653663

654664
private EslintConfig eslintConfig() {
655-
return new EslintConfig(configFilePath != null ? getProject().file(configFilePath) : null, configJs);
665+
return new EslintConfig(configFilePath != null ? getProject().file(configFilePath) : null, configJs, additionalConfigs());
666+
}
667+
668+
private Map<File, Optional<String>> additionalConfigs() {
669+
// convert additionalConfigs to a map explicitly allowing null values
670+
671+
return additionalConfigs.stream()
672+
.collect(Collectors.toMap(
673+
config -> getProject().file(config.configFilePath),
674+
config -> Optional.ofNullable(config.relativeTargetPath)));
675+
}
676+
}
677+
678+
private static class AdditionalEslintConfig {
679+
final Object configFilePath;
680+
681+
final String relativeTargetPath;
682+
683+
AdditionalEslintConfig(Object configFilePath, String relativeTargetPath) {
684+
this.configFilePath = configFilePath;
685+
this.relativeTargetPath = relativeTargetPath;
656686
}
657687
}
658688

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

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,7 @@ public FormatterStep createStep() {
113113
return TsFmtFormatterStep.create(
114114
devDependencies,
115115
provisioner(),
116+
project.getProjectDir(),
116117
project.getBuildDir(),
117118
new NpmPathResolver(npmFileOrNull(), npmrcFileOrNull(), project.getProjectDir(), project.getRootDir()),
118119
typedConfigFile(),
@@ -221,6 +222,16 @@ public TypescriptEslintFormatExtension styleGuide(String styleGuide) {
221222
replaceStep(createStep());
222223
return this;
223224
}
225+
226+
public TypescriptEslintFormatExtension tsconfigFile(Object path) {
227+
return tsconfigFile(path, null);
228+
}
229+
230+
public TypescriptEslintFormatExtension tsconfigFile(Object path, String remapping) {
231+
additionalConfigFilePath(path, remapping);
232+
replaceStep(createStep());
233+
return this;
234+
}
224235
}
225236

226237
@Override

0 commit comments

Comments
 (0)