Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding ESLint as formatter step #1453

Merged
merged 39 commits into from
Jan 10, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
4959227
extract base serve code
simschla Nov 28, 2022
c70bd34
upgrade express to latest
simschla Nov 28, 2022
3884e14
extract common rest code on client side
simschla Dec 3, 2022
c20d3bb
add "eslint --fix" as formatter
simschla Dec 3, 2022
4f5e4d3
eslint: add support for xo standard typescript ruleset
simschla Dec 7, 2022
9633967
eslint: add support for standard-with-typescript ruleset
simschla Dec 9, 2022
7022b7f
eslint: add unit tests, cleanup code
simschla Dec 15, 2022
a2a5502
eslint: reduce required options
simschla Dec 15, 2022
35b2b7c
eslint: remove unneeded configuration
simschla Dec 16, 2022
016dd3a
eslint: test supplying inline config
simschla Dec 16, 2022
a9f89d8
eslint: adding javascript formatting (part 1)
simschla Dec 16, 2022
4efc19b
eslint: introduce separate javascript extension
simschla Dec 18, 2022
0206246
eslint: adding tests for js
simschla Dec 20, 2022
393a4c4
eslint: bumping version numbers
simschla Dec 20, 2022
8dc740e
eslint: adapt to extended api on maven side
simschla Dec 20, 2022
cb3dd55
eslint: speedup npm install
simschla Dec 21, 2022
e12c5ef
eslint: speedup npmrc tests
simschla Dec 21, 2022
218db79
eslint: extract base properties
simschla Dec 21, 2022
31462a2
eslint: initial maven support
simschla Dec 22, 2022
dd5ae78
eslint: tests for maven integration into typescript
simschla Dec 22, 2022
7d2e5ad
eslint: adding maven javascript tests
simschla Dec 23, 2022
1d164d3
eslint: also test custom devDependencies case
simschla Dec 23, 2022
3f37053
eslint: adding readme (common and plugin-gradle)
simschla Jan 4, 2023
bb843ce
eslint: apply spotless
simschla Jan 4, 2023
508dd69
bumping default prettier version
simschla Jan 4, 2023
67a0a05
eslint: update readme for maven
simschla Jan 4, 2023
e07082a
eslint: unify naming in documentation
simschla Jan 4, 2023
2943c66
eslint: adapt to api changes in TestResource
simschla Jan 4, 2023
ce21de7
eslint: adapt PR number
simschla Jan 4, 2023
3a1d43b
eslint: add default version bump to CHANGES
simschla Jan 4, 2023
b4f76b4
eslint: adapt maven code to match documentation
simschla Jan 4, 2023
4598319
eslint: spotlessApply
simschla Jan 4, 2023
3540242
eslint: reduce log-noise, cleanup error codes
simschla Jan 5, 2023
98c023b
eslint: spotbugs fix
simschla Jan 5, 2023
9a0932e
Merge branch 'main' of https://github.com/diffplug/spotless into feat…
simschla Jan 9, 2023
d4d91cb
eslint: remove direct api support for styleguides
simschla Jan 10, 2023
7673e22
Merge branch 'main' of https://github.com/diffplug/spotless into feat…
simschla Jan 10, 2023
6577e29
eslint: fix anchor navigation
simschla Jan 10, 2023
0227e89
eslint: cleanup doc
simschla Jan 10, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,10 @@ We adhere to the [keepachangelog](https://keepachangelog.com/en/1.0.0/) format (
* Added `skipLinesMatching` option to `licenseHeader` to support formats where license header cannot be immediately added to the top of the file (e.g. xml, sh). ([#1441](https://github.com/diffplug/spotless/pull/1441)).
### Fixed
* Support `ktlint` 0.48+ new rule disabling syntax ([#1456](https://github.com/diffplug/spotless/pull/1456)) fixes ([#1444](https://github.com/diffplug/spotless/issues/1444))
* Added support for npm-based [ESLint](https://eslint.org/)-formatter for javascript and typescript ([#1453](https://github.com/diffplug/spotless/pull/1453))

### Changes
* Bump default version for `prettier` from `2.0.5` to `2.8.1` ([#1453](https://github.com/diffplug/spotless/pull/1453))
* Bump the dev version of Gradle from `7.5.1` to `7.6` ([#1409](https://github.com/diffplug/spotless/pull/1409))
* We also removed the no-longer-required dependency `org.codehaus.groovy:groovy-xml`
* Breaking changes to Spotless' internal testing infrastructure `testlib` ([#1443](https://github.com/diffplug/spotless/pull/1443))
Expand All @@ -29,6 +31,7 @@ We adhere to the [keepachangelog](https://keepachangelog.com/en/1.0.0/) format (
* Switch our publishing infrastructure from CircleCI to GitHub Actions ([#1462](https://github.com/diffplug/spotless/pull/1462)).
* Help wanted for moving our tests too ([#1472](https://github.com/diffplug/spotless/issues/1472))


## [2.31.1] - 2023-01-02
### Fixed
* Improve memory usage when using git ratchet ([#1426](https://github.com/diffplug/spotless/pull/1426))
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ lib('kotlin.KtfmtStep') +'{{yes}} | {{yes}}
lib('kotlin.DiktatStep') +'{{yes}} | {{yes}} | {{no}} | {{no}} |',
lib('markdown.FreshMarkStep') +'{{yes}} | {{no}} | {{no}} | {{no}} |',
lib('markdown.FlexmarkStep') +'{{no}} | {{yes}} | {{no}} | {{no}} |',
lib('npm.EslintFormatterStep') +'{{yes}} | {{yes}} | {{no}} | {{no}} |',
lib('npm.PrettierFormatterStep') +'{{yes}} | {{yes}} | {{no}} | {{no}} |',
lib('npm.TsFmtFormatterStep') +'{{yes}} | {{yes}} | {{no}} | {{no}} |',
lib('pom.SortPomStep') +'{{no}} | {{yes}} | {{no}} | {{no}} |',
Expand Down Expand Up @@ -113,6 +114,7 @@ extra('wtp.EclipseWtpFormatterStep') +'{{yes}} | {{yes}}
| [`kotlin.DiktatStep`](lib/src/main/java/com/diffplug/spotless/kotlin/DiktatStep.java) | :+1: | :+1: | :white_large_square: | :white_large_square: |
| [`markdown.FreshMarkStep`](lib/src/main/java/com/diffplug/spotless/markdown/FreshMarkStep.java) | :+1: | :white_large_square: | :white_large_square: | :white_large_square: |
| [`markdown.FlexmarkStep`](lib/src/main/java/com/diffplug/spotless/markdown/FlexmarkStep.java) | :white_large_square: | :+1: | :white_large_square: | :white_large_square: |
| [`npm.EslintFormatterStep`](lib/src/main/java/com/diffplug/spotless/npm/EslintFormatterStep.java) | :+1: | :+1: | :white_large_square: | :white_large_square: |
| [`npm.PrettierFormatterStep`](lib/src/main/java/com/diffplug/spotless/npm/PrettierFormatterStep.java) | :+1: | :+1: | :white_large_square: | :white_large_square: |
| [`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: |
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2020-2022 DiffPlug
* Copyright 2020-2023 DiffPlug
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/*
* 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.
* 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.npm;

abstract class BaseNpmRestService {

protected final SimpleRestClient restClient;

BaseNpmRestService(String baseUrl) {
this.restClient = SimpleRestClient.forBaseUrl(baseUrl);
}

public String shutdown() {
return restClient.post("/shutdown");
}

}
72 changes: 72 additions & 0 deletions lib/src/main/java/com/diffplug/spotless/npm/EslintConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
/*
* 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.
* 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.npm;

import java.io.File;
import java.io.IOException;
import java.io.Serializable;

import javax.annotation.Nullable;

import com.diffplug.spotless.FileSignature;
import com.diffplug.spotless.ThrowingEx;

import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;

public class EslintConfig implements Serializable {

private static final long serialVersionUID = -6196834313082791248L;

@SuppressFBWarnings("SE_TRANSIENT_FIELD_NOT_RESTORED")
@Nullable
private final transient File eslintConfigPath;

@SuppressWarnings("unused")
private final FileSignature eslintConfigPathSignature;

private final String eslintConfigJs;

public EslintConfig(@Nullable File eslintConfigPath, @Nullable String eslintConfigJs) {
try {
this.eslintConfigPath = eslintConfigPath;
this.eslintConfigPathSignature = eslintConfigPath != null ? FileSignature.signAsList(this.eslintConfigPath) : FileSignature.signAsList();
this.eslintConfigJs = eslintConfigJs;
} catch (IOException e) {
throw ThrowingEx.asRuntime(e);
}
}

public EslintConfig withEslintConfigPath(@Nullable File eslintConfigPath) {
return new EslintConfig(eslintConfigPath, this.eslintConfigJs);
}

@Nullable
public File getEslintConfigPath() {
return eslintConfigPath;
}

@Nullable
public String getEslintConfigJs() {
return eslintConfigJs;
}

public EslintConfig verify() {
if (eslintConfigPath == null && eslintConfigJs == null) {
throw new IllegalArgumentException("ESLint must be configured using either a configFile or a configJs - but both are null.");
}
return this;
}
}
182 changes: 182 additions & 0 deletions lib/src/main/java/com/diffplug/spotless/npm/EslintFormatterStep.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
/*
* 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.
* 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.npm;

import static java.util.Objects.requireNonNull;

import java.io.File;
import java.io.IOException;
import java.io.Serializable;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Objects;
import java.util.TreeMap;

import javax.annotation.Nonnull;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.diffplug.spotless.FormatterFunc;
import com.diffplug.spotless.FormatterFunc.Closeable;
import com.diffplug.spotless.FormatterStep;
import com.diffplug.spotless.Provisioner;
import com.diffplug.spotless.ThrowingEx;
import com.diffplug.spotless.npm.EslintRestService.FormatOption;

public class EslintFormatterStep {

private static final Logger logger = LoggerFactory.getLogger(EslintFormatterStep.class);

public static final String NAME = "eslint-format";

public static final String DEFAULT_ESLINT_VERSION = "^8.31.0";

public static Map<String, String> defaultDevDependenciesForTypescript() {
return defaultDevDependenciesTypescriptWithEslint(DEFAULT_ESLINT_VERSION);
}

public static Map<String, String> defaultDevDependenciesTypescriptWithEslint(String eslintVersion) {
Map<String, String> dependencies = new LinkedHashMap<>();
dependencies.put("@typescript-eslint/eslint-plugin", "^5.47.0");
dependencies.put("@typescript-eslint/parser", "^5.47.0");
dependencies.put("typescript", "^4.9.4");
dependencies.put("eslint", Objects.requireNonNull(eslintVersion));
return dependencies;
}

public static Map<String, String> defaultDevDependencies() {
return defaultDevDependenciesWithEslint(DEFAULT_ESLINT_VERSION);
}

public static Map<String, String> defaultDevDependenciesWithEslint(String version) {
return Collections.singletonMap("eslint", version);
}

public static FormatterStep create(Map<String, String> devDependencies, Provisioner provisioner, File projectDir, File buildDir, NpmPathResolver npmPathResolver, EslintConfig eslintConfig) {
requireNonNull(devDependencies);
requireNonNull(provisioner);
requireNonNull(projectDir);
requireNonNull(buildDir);
return FormatterStep.createLazy(NAME,
() -> new State(NAME, devDependencies, projectDir, buildDir, npmPathResolver, eslintConfig),
State::createFormatterFunc);
}

private static class State extends NpmFormatterStepStateBase implements Serializable {

private static final long serialVersionUID = -539537027004745812L;
private final EslintConfig eslintConfig;

State(String stepName, Map<String, String> devDependencies, File projectDir, File buildDir, NpmPathResolver npmPathResolver, EslintConfig eslintConfig) throws IOException {
super(stepName,
new NpmConfig(
replaceDevDependencies(
NpmResourceHelper.readUtf8StringFromClasspath(EslintFormatterStep.class, "/com/diffplug/spotless/npm/eslint-package.json"),
new TreeMap<>(devDependencies)),
"eslint",
NpmResourceHelper.readUtf8StringFromClasspath(EslintFormatterStep.class,
"/com/diffplug/spotless/npm/common-serve.js",
"/com/diffplug/spotless/npm/eslint-serve.js"),
npmPathResolver.resolveNpmrcContent()),
projectDir,
buildDir,
npmPathResolver.resolveNpmExecutable());
this.eslintConfig = localCopyFiles(requireNonNull(eslintConfig));
}

private EslintConfig localCopyFiles(EslintConfig orig) {
if (orig.getEslintConfigPath() == null) {
return orig.verify();
}
// If any config files are provided, we need to make sure they are at the same location as the node modules
// as eslint will try to resolve plugin/config names relatively to the config file location and some
// eslint configs contain relative paths to additional config files (such as tsconfig.json e.g.)
FormattedPrinter.SYSOUT.print("Copying config file <%s> to <%s> and using the copy", orig.getEslintConfigPath(), nodeModulesDir);
File configFileCopy = NpmResourceHelper.copyFileToDir(orig.getEslintConfigPath(), nodeModulesDir);
return orig.withEslintConfigPath(configFileCopy).verify();
}

@Override
@Nonnull
public FormatterFunc createFormatterFunc() {
try {
FormattedPrinter.SYSOUT.print("creating formatter function (starting server)");
ServerProcessInfo eslintRestServer = npmRunServer();
EslintRestService restService = new EslintRestService(eslintRestServer.getBaseUrl());
return Closeable.ofDangerous(() -> endServer(restService, eslintRestServer), new EslintFilePathPassingFormatterFunc(projectDir, nodeModulesDir, eslintConfig, restService));
} catch (IOException e) {
throw ThrowingEx.asRuntime(e);
}
}

private void endServer(BaseNpmRestService restService, ServerProcessInfo restServer) throws Exception {
FormattedPrinter.SYSOUT.print("Closing formatting function (ending server).");
try {
restService.shutdown();
} catch (Throwable t) {
logger.info("Failed to request shutdown of rest service via api. Trying via process.", t);
}
restServer.close();
}

}

private static class EslintFilePathPassingFormatterFunc implements FormatterFunc.NeedsFile {
private final File projectDir;
private final File nodeModulesDir;
private final EslintConfig eslintConfig;
private final EslintRestService restService;

public EslintFilePathPassingFormatterFunc(File projectDir, File nodeModulesDir, EslintConfig eslintConfig, EslintRestService restService) {
this.projectDir = requireNonNull(projectDir);
this.nodeModulesDir = requireNonNull(nodeModulesDir);
this.eslintConfig = requireNonNull(eslintConfig);
this.restService = requireNonNull(restService);
}

@Override
public String applyWithFile(String unix, File file) throws Exception {
FormattedPrinter.SYSOUT.print("formatting String '" + unix.substring(0, Math.min(50, unix.length())) + "[...]' in file '" + file + "'");

Map<FormatOption, Object> eslintCallOptions = new HashMap<>();
setConfigToCallOptions(eslintCallOptions);
setFilePathToCallOptions(eslintCallOptions, file);
return restService.format(unix, eslintCallOptions);
}

private void setFilePathToCallOptions(Map<FormatOption, Object> eslintCallOptions, File fileToBeFormatted) {
eslintCallOptions.put(FormatOption.FILE_PATH, fileToBeFormatted.getAbsolutePath());
}

private void setConfigToCallOptions(Map<FormatOption, Object> eslintCallOptions) {
if (eslintConfig.getEslintConfigPath() != null) {
eslintCallOptions.put(FormatOption.ESLINT_OVERRIDE_CONFIG_FILE, eslintConfig.getEslintConfigPath().getAbsolutePath());
}
if (eslintConfig.getEslintConfigJs() != null) {
eslintCallOptions.put(FormatOption.ESLINT_OVERRIDE_CONFIG, eslintConfig.getEslintConfigJs());
}
if (eslintConfig instanceof EslintTypescriptConfig) {
// if we are a ts config, see if we need to use specific paths or use default projectDir
File tsConfigFilePath = ((EslintTypescriptConfig) eslintConfig).getTypescriptConfigPath();
File tsConfigRootDir = tsConfigFilePath != null ? tsConfigFilePath.getParentFile() : projectDir;
eslintCallOptions.put(FormatOption.TS_CONFIG_ROOT_DIR, nodeModulesDir.getAbsoluteFile().toPath().relativize(tsConfigRootDir.getAbsoluteFile().toPath()).toString());
}
}
}
}
46 changes: 46 additions & 0 deletions lib/src/main/java/com/diffplug/spotless/npm/EslintRestService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/*
* 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.
* 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.npm;

import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Map.Entry;

public class EslintRestService extends BaseNpmRestService {

EslintRestService(String baseUrl) {
super(baseUrl);
}

public String format(String fileContent, Map<FormatOption, Object> formatOptions) {
Map<String, Object> jsonProperties = new LinkedHashMap<>();
jsonProperties.put("file_content", fileContent);
for (Entry<FormatOption, Object> option : formatOptions.entrySet()) {
jsonProperties.put(option.getKey().backendName, option.getValue());
}
return restClient.postJson("/eslint/format", jsonProperties);
}

enum FormatOption {
ESLINT_OVERRIDE_CONFIG("eslint_override_config"), ESLINT_OVERRIDE_CONFIG_FILE("eslint_override_config_file"), FILE_PATH("file_path"), TS_CONFIG_ROOT_DIR("ts_config_root_dir");

private final String backendName;

FormatOption(String backendName) {
this.backendName = backendName;
}
}
}
Loading