Skip to content

Commit c20d3bb

Browse files
committed
add "eslint --fix" as formatter
1 parent 3884e14 commit c20d3bb

File tree

17 files changed

+737
-13
lines changed

17 files changed

+737
-13
lines changed
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
/*
2+
* Copyright 2016-2022 DiffPlug
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package com.diffplug.spotless.npm;
17+
18+
import java.io.File;
19+
import java.io.IOException;
20+
import java.io.Serializable;
21+
22+
import javax.annotation.Nullable;
23+
24+
import com.diffplug.spotless.FileSignature;
25+
import com.diffplug.spotless.ThrowingEx;
26+
27+
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
28+
29+
public class EslintConfig implements Serializable {
30+
31+
private static final long serialVersionUID = -6196834313082791248L;
32+
33+
@SuppressFBWarnings("SE_TRANSIENT_FIELD_NOT_RESTORED")
34+
@Nullable
35+
private final transient File eslintConfigPath;
36+
37+
@SuppressWarnings("unused")
38+
private final FileSignature eslintConfigPathSignature;
39+
40+
private final String eslintConfigJs;
41+
42+
public EslintConfig(@Nullable File eslintConfigPath, @Nullable String eslintConfigJs) {
43+
try {
44+
this.eslintConfigPath = eslintConfigPath;
45+
this.eslintConfigPathSignature = eslintConfigPath != null ? FileSignature.signAsList(this.eslintConfigPath) : FileSignature.signAsList();
46+
this.eslintConfigJs = eslintConfigJs;
47+
} catch (IOException e) {
48+
throw ThrowingEx.asRuntime(e);
49+
}
50+
}
51+
52+
@Nullable
53+
public File getEslintConfigPath() {
54+
return eslintConfigPath;
55+
}
56+
57+
@Nullable
58+
public String getEslintConfigJs() {
59+
return eslintConfigJs;
60+
}
61+
}
Lines changed: 219 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,219 @@
1+
/*
2+
* Copyright 2016-2022 DiffPlug
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package com.diffplug.spotless.npm;
17+
18+
import static java.util.Objects.requireNonNull;
19+
20+
import java.io.File;
21+
import java.io.IOException;
22+
import java.io.Serializable;
23+
import java.util.Collections;
24+
import java.util.HashMap;
25+
import java.util.LinkedHashMap;
26+
import java.util.Map;
27+
import java.util.Objects;
28+
import java.util.TreeMap;
29+
30+
import javax.annotation.Nonnull;
31+
32+
import org.slf4j.Logger;
33+
import org.slf4j.LoggerFactory;
34+
35+
import com.diffplug.spotless.FormatterFunc;
36+
import com.diffplug.spotless.FormatterFunc.Closeable;
37+
import com.diffplug.spotless.FormatterStep;
38+
import com.diffplug.spotless.Provisioner;
39+
import com.diffplug.spotless.ThrowingEx;
40+
import com.diffplug.spotless.npm.EslintRestService.FormatOption;
41+
42+
public class EslintFormatterStep {
43+
44+
private static final Logger logger = LoggerFactory.getLogger(EslintFormatterStep.class);
45+
46+
public static final String NAME = "eslint-format";
47+
48+
public static final String DEFAULT_ESLINT_VERSION = "8.28.0";
49+
50+
public enum PopularStyleGuide {
51+
STANDARD_WITH_TYPESCRIPT("standard-with-typescript") {
52+
@Override
53+
public Map<String, String> devDependencies() {
54+
Map<String, String> dependencies = new LinkedHashMap<>();
55+
dependencies.put("eslint-config-standard-with-typescript", "23.0.0");
56+
dependencies.put("eslint-plugin-import", "2.26.0");
57+
dependencies.put("eslint-plugin-n", "15.5.1");
58+
dependencies.put("eslint-plugin-promise", "6.1.1");
59+
dependencies.put("typescript", "4.9.3");
60+
return dependencies;
61+
}
62+
},
63+
XO_TYPESCRIPT("xo-typescript") {
64+
@Override
65+
public Map<String, String> devDependencies() {
66+
Map<String, String> dependencies = new LinkedHashMap<>();
67+
dependencies.put("eslint-config-xo", "0.43.1");
68+
dependencies.put("eslint-config-xo-typescript", "0.55.1");
69+
dependencies.put("typescript", "4.9.3");
70+
return dependencies;
71+
}
72+
};
73+
74+
private final String popularStyleGuideName;
75+
76+
PopularStyleGuide(String popularStyleGuideName) {
77+
this.popularStyleGuideName = popularStyleGuideName;
78+
}
79+
80+
public String getPopularStyleGuideName() {
81+
return popularStyleGuideName;
82+
}
83+
84+
public abstract Map<String, String> devDependencies();
85+
86+
public static PopularStyleGuide fromNameOrNull(String popularStyleGuideName) {
87+
for (PopularStyleGuide popularStyleGuide : PopularStyleGuide.values()) {
88+
if (popularStyleGuide.popularStyleGuideName.equals(popularStyleGuideName)) {
89+
return popularStyleGuide;
90+
}
91+
}
92+
return null;
93+
}
94+
}
95+
96+
public static Map<String, String> defaultDevDependenciesForTypescript() {
97+
return defaultDevDependenciesTypescriptWithEslint(DEFAULT_ESLINT_VERSION);
98+
}
99+
100+
public static Map<String, String> defaultDevDependenciesTypescriptWithEslint(String eslintVersion) {
101+
Map<String, String> dependencies = new LinkedHashMap<>();
102+
dependencies.put("@typescript-eslint/eslint-plugin", "5.45.0");
103+
dependencies.put("@typescript-eslint/parser", "5.45.0");
104+
dependencies.put("eslint", Objects.requireNonNull(eslintVersion));
105+
return dependencies;
106+
}
107+
108+
public static Map<String, String> defaultDevDependencies() {
109+
return defaultDevDependenciesWithEslint(DEFAULT_ESLINT_VERSION);
110+
}
111+
112+
public static Map<String, String> defaultDevDependenciesWithEslint(String version) {
113+
return Collections.singletonMap("eslint", version);
114+
}
115+
116+
public static FormatterStep create(Map<String, String> devDependencies, Provisioner provisioner, File buildDir, NpmPathResolver npmPathResolver, EslintConfig eslintConfig) {
117+
requireNonNull(devDependencies);
118+
requireNonNull(provisioner);
119+
requireNonNull(buildDir);
120+
return FormatterStep.createLazy(NAME,
121+
() -> new State(NAME, devDependencies, buildDir, npmPathResolver, eslintConfig),
122+
State::createFormatterFunc);
123+
}
124+
125+
private static class State extends NpmFormatterStepStateBase implements Serializable {
126+
127+
private static final long serialVersionUID = -539537027004745812L;
128+
private final EslintConfig eslintConfig;
129+
130+
State(String stepName, Map<String, String> devDependencies, File buildDir, NpmPathResolver npmPathResolver, EslintConfig eslintConfig) throws IOException {
131+
super(stepName,
132+
new NpmConfig(
133+
replaceDevDependencies(
134+
NpmResourceHelper.readUtf8StringFromClasspath(EslintFormatterStep.class, "/com/diffplug/spotless/npm/eslint-package.json"),
135+
new TreeMap<>(devDependencies)),
136+
"eslint",
137+
NpmResourceHelper.readUtf8StringFromClasspath(EslintFormatterStep.class,
138+
"/com/diffplug/spotless/npm/common-serve.js",
139+
"/com/diffplug/spotless/npm/eslint-serve.js"),
140+
npmPathResolver.resolveNpmrcContent()),
141+
buildDir,
142+
npmPathResolver.resolveNpmExecutable());
143+
this.eslintConfig = localCopyFiles(requireNonNull(eslintConfig));
144+
}
145+
146+
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
152+
FormattedPrinter.SYSOUT.print("Copying config file <%s> to <%s> and using the copy", orig.getEslintConfigPath(), nodeModulesDir);
153+
File configFileCopy = NpmResourceHelper.copyFileToDir(orig.getEslintConfigPath(), nodeModulesDir);
154+
return new EslintConfig(configFileCopy, orig.getEslintConfigJs());
155+
}
156+
157+
@Override
158+
@Nonnull
159+
public FormatterFunc createFormatterFunc() {
160+
try {
161+
FormattedPrinter.SYSOUT.print("creating formatter function (starting server)");
162+
ServerProcessInfo eslintRestServer = npmRunServer();
163+
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));
167+
} catch (IOException e) {
168+
throw ThrowingEx.asRuntime(e);
169+
}
170+
}
171+
172+
private void endServer(BaseNpmRestService restService, ServerProcessInfo restServer) throws Exception {
173+
FormattedPrinter.SYSOUT.print("Closing formatting function (ending server).");
174+
try {
175+
restService.shutdown();
176+
} catch (Throwable t) {
177+
logger.info("Failed to request shutdown of rest service via api. Trying via process.", t);
178+
}
179+
restServer.close();
180+
}
181+
182+
}
183+
184+
private static class EslintFilePathPassingFormatterFunc implements FormatterFunc.NeedsFile {
185+
private final File nodeModulesDir;
186+
private final EslintConfig eslintConfig;
187+
private final EslintRestService restService;
188+
189+
public EslintFilePathPassingFormatterFunc(File nodeModulesDir, EslintConfig eslintConfig, EslintRestService restService) {
190+
this.nodeModulesDir = nodeModulesDir;
191+
this.eslintConfig = requireNonNull(eslintConfig);
192+
this.restService = requireNonNull(restService);
193+
}
194+
195+
@Override
196+
public String applyWithFile(String unix, File file) throws Exception {
197+
FormattedPrinter.SYSOUT.print("formatting String '" + unix.substring(0, Math.min(50, unix.length())) + "[...]' in file '" + file + "'");
198+
199+
Map<FormatOption, Object> eslintCallOptions = new HashMap<>();
200+
setConfigToCallOptions(eslintCallOptions);
201+
setFilePathToCallOptions(eslintCallOptions, file);
202+
return restService.format(unix, eslintCallOptions);
203+
}
204+
205+
private void setFilePathToCallOptions(Map<FormatOption, Object> eslintCallOptions, File fileToBeFormatted) {
206+
eslintCallOptions.put(FormatOption.FILE_PATH, fileToBeFormatted.getAbsolutePath());
207+
}
208+
209+
private void setConfigToCallOptions(Map<FormatOption, Object> eslintCallOptions) {
210+
if (eslintConfig.getEslintConfigPath() != null) {
211+
eslintCallOptions.put(FormatOption.ESLINT_OVERRIDE_CONFIG_FILE, eslintConfig.getEslintConfigPath().getAbsolutePath());
212+
}
213+
if (eslintConfig.getEslintConfigJs() != null) {
214+
eslintCallOptions.put(FormatOption.ESLINT_OVERRIDE_CONFIG, eslintConfig.getEslintConfigJs());
215+
}
216+
eslintCallOptions.put(FormatOption.NODE_MODULES_DIR, nodeModulesDir.getAbsolutePath());
217+
}
218+
}
219+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
/*
2+
* Copyright 2016-2022 DiffPlug
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package com.diffplug.spotless.npm;
17+
18+
import java.util.LinkedHashMap;
19+
import java.util.Map;
20+
import java.util.Map.Entry;
21+
22+
public class EslintRestService extends BaseNpmRestService {
23+
24+
EslintRestService(String baseUrl) {
25+
super(baseUrl);
26+
}
27+
28+
public String format(String fileContent, Map<FormatOption, Object> formatOptions) {
29+
Map<String, Object> jsonProperties = new LinkedHashMap<>();
30+
jsonProperties.put("file_content", fileContent);
31+
for (Entry<FormatOption, Object> option : formatOptions.entrySet()) {
32+
jsonProperties.put(option.getKey().backendName, option.getValue());
33+
}
34+
return restClient.postJson("/eslint/format", jsonProperties);
35+
}
36+
37+
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");
39+
40+
private final String backendName;
41+
42+
FormatOption(String backendName) {
43+
this.backendName = backendName;
44+
}
45+
}
46+
}

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

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import java.io.*;
1919
import java.nio.charset.StandardCharsets;
2020
import java.nio.file.Files;
21+
import java.nio.file.StandardCopyOption;
2122
import java.time.Duration;
2223
import java.util.Arrays;
2324
import java.util.concurrent.TimeoutException;
@@ -100,4 +101,14 @@ static void awaitReadableFile(File file, Duration maxWaitTime) throws TimeoutExc
100101
}
101102
}
102103
}
104+
105+
static File copyFileToDir(File file, File targetDir) {
106+
try {
107+
File copiedFile = new File(targetDir, file.getName());
108+
Files.copy(file.toPath(), copiedFile.toPath(), StandardCopyOption.REPLACE_EXISTING);
109+
return copiedFile;
110+
} catch (IOException e) {
111+
throw ThrowingEx.asRuntime(e);
112+
}
113+
}
103114
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
{
2+
"name": "spotless-eslint-formatter-step",
3+
"version": "2.0.0",
4+
"description": "Spotless formatter step for running eslint as a rest service.",
5+
"repository": "https://github.com/diffplug/spotless",
6+
"license": "Apache-2.0",
7+
"scripts": {
8+
"start": "node serve.js"
9+
},
10+
"devDependencies": {
11+
${devDependencies},
12+
"express": "4.18.2",
13+
"@moebius/http-graceful-shutdown": "1.1.0"
14+
},
15+
"dependencies": {},
16+
"engines": {
17+
"node": ">=6"
18+
}
19+
}

0 commit comments

Comments
 (0)