Skip to content
New issue

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

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

Already on GitHub? # to your account

Linting - API #2149

Merged
merged 35 commits into from
Oct 21, 2024
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
9afa6dd
Handy tool for making encoding formats that don't have any forbidden …
nedtwigg May 30, 2024
319fc79
Revert "Handy tool for making encoding formats that don't have any fo…
nedtwigg May 30, 2024
777fd5c
Add a `Lint` class, along with `ShortcutException` for sending them.
nedtwigg May 30, 2024
322d5bf
Add a `lint` method to the core interfaces: `Formatter[Step|Func]`
nedtwigg May 30, 2024
c9f52d5
Pipe linting through the core FormatterStep implementations.
nedtwigg May 30, 2024
b57bf24
Formatter can now capture exceptions per-formatter, rethrows if you d…
nedtwigg May 30, 2024
a94dce9
Pipe the lints through `FenceStep`, preliminary.
nedtwigg May 30, 2024
c224929
Remove all of `FormatExceptionPolicy` except `Strict`, which remains …
nedtwigg May 30, 2024
b01e9e9
Rename `ExceptionPerStep` to `ValuePerStep`, and bring `Formatter` cl…
nedtwigg May 30, 2024
fe92d05
Update `Formatter` and `ValuePerStep` so that something (perhaps null…
nedtwigg May 31, 2024
f3be100
Introduce LintState which efficiently reads lint data from both `form…
nedtwigg May 31, 2024
5c42457
Restore the "legacy" error printing for both `Formatter` and `DirtySt…
nedtwigg May 31, 2024
d467545
Fix spotbugs.
nedtwigg May 31, 2024
700114f
Add Selfie, and configure it to not use triple quote literals.
nedtwigg Jun 4, 2024
ffc911e
Add a way to test for lints, and use that to bring back FenceStepTest…
nedtwigg Jun 4, 2024
c2fe9c6
Merge branch 'feat/prepare-for-lint-take-2' into feat/lint-take-2
nedtwigg Jun 4, 2024
6bbe556
Deal with `DirtyState.Calculation` is gone.
nedtwigg Jun 4, 2024
aa769d0
Merge branch 'feat/prepare-for-lint-take-2' into feat/lint-take-2
nedtwigg Oct 15, 2024
d08f442
Two maven tests which fail because errors are getting swallowed as li…
nedtwigg Oct 16, 2024
53e31f5
Some gradle tests which fail because errors are getting swallowed as …
nedtwigg Oct 16, 2024
9af4880
DirtyState now uses `LintPolicy.legacyBehavior` unless you passed in …
nedtwigg Oct 16, 2024
dfca397
Rework SpotlessTask to use `LintState` so that we can know which step…
nedtwigg Oct 17, 2024
29cba64
We don't need to move lints to their own place *yet*.
nedtwigg Oct 17, 2024
43cd94b
Make an explicit constant for a lint at an undefined line.
nedtwigg Oct 17, 2024
7bf6b1b
Merge branch 'main' into feat/lint-take-2
nedtwigg Oct 17, 2024
6619a76
Fix windows.
nedtwigg Oct 17, 2024
a9bd0a6
Update changelog.
nedtwigg Oct 17, 2024
6c480d8
Add info to CONTRIBUTING
nedtwigg Oct 17, 2024
2fb5a16
Merge branch 'main' into feat/lint-take-2
nedtwigg Oct 17, 2024
ae31ea5
Setup selfie.
nedtwigg Oct 20, 2024
8300fda
Fix ambiguities in the `Lint` class around treating it as data vs exc…
nedtwigg Oct 20, 2024
9658d41
Improve LintState's toString.
nedtwigg Oct 20, 2024
d00a4ab
Adapt gradle test for the API.
nedtwigg Oct 20, 2024
195bc16
Remove `testResourceExceptionMsg` and replace with `expectLintsOfReso…
nedtwigg Oct 20, 2024
14cbc52
Merge branch 'main' into feat/lint-take-2
nedtwigg Oct 20, 2024
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
1 change: 1 addition & 0 deletions gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,4 @@ VER_JGIT=6.7.0.202309050840-r
VER_JUNIT=5.10.2
VER_ASSERTJ=3.26.0
VER_MOCKITO=5.12.0
VER_SELFIE=2.2.0
22 changes: 17 additions & 5 deletions lib/src/main/java/com/diffplug/spotless/DirtyState.java
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ public boolean didNotConverge() {
return this == didNotConverge;
}

private byte[] canonicalBytes() {
byte[] canonicalBytes() {
if (canonicalBytes == null) {
throw new IllegalStateException("First make sure that {@code !isClean()} and {@code !didNotConverge()}");
}
Expand Down Expand Up @@ -81,7 +81,7 @@ public static class Calculation {
private final Formatter formatter;
private final File file;
private final byte[] rawBytes;
private final String raw;
final String raw;

private Calculation(Formatter formatter, File file, byte[] rawBytes) {
this.formatter = formatter;
Expand All @@ -101,10 +101,22 @@ private Calculation(Formatter formatter, File file, byte[] rawBytes) {
* due to diverging idempotence.
*/
public DirtyState calculateDirtyState() {
ValuePerStep<Throwable> exceptionPerStep = new ValuePerStep<>(formatter);
DirtyState result = calculateDirtyState(exceptionPerStep);
LintPolicy.legacyBehavior(formatter, file, exceptionPerStep);
return result;
}

/**
* Calculates whether the given file is dirty according to a PaddedCell invocation of the given formatter.
* DirtyState includes the clean state of the file, as well as a warning if we were not able to apply the formatter
* due to diverging idempotence.
*/
DirtyState calculateDirtyState(ValuePerStep<Throwable> exceptionPerStep) {
String rawUnix = LineEnding.toUnix(raw);

// enforce the format
String formattedUnix = formatter.compute(rawUnix, file);
String formattedUnix = formatter.computeWithLint(rawUnix, file, exceptionPerStep);
// convert the line endings if necessary
String formatted = formatter.computeLineEndings(formattedUnix, file);

Expand All @@ -115,13 +127,13 @@ public DirtyState calculateDirtyState() {
}

// F(input) != input, so we'll do a padded check
String doubleFormattedUnix = formatter.compute(formattedUnix, file);
String doubleFormattedUnix = formatter.computeWithLint(formattedUnix, file, exceptionPerStep);
if (doubleFormattedUnix.equals(formattedUnix)) {
// most dirty files are idempotent-dirty, so this is a quick-short circuit for that common case
return new DirtyState(formattedBytes);
}

PaddedCell cell = PaddedCell.check(formatter, file, rawUnix);
PaddedCell cell = PaddedCell.check(formatter, file, rawUnix, exceptionPerStep);
if (!cell.isResolvable()) {
return didNotConverge;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2016-2023 DiffPlug
* Copyright 2016-2024 DiffPlug
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -16,8 +16,8 @@
package com.diffplug.spotless;

import java.io.File;
import java.util.List;
import java.util.Objects;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.annotation.Nullable;
Expand All @@ -36,14 +36,24 @@ final class FilterByContentPatternFormatterStep extends DelegateFormatterStep {
public @Nullable String format(String raw, File file) throws Exception {
Objects.requireNonNull(raw, "raw");
Objects.requireNonNull(file, "file");
Matcher matcher = contentPattern.matcher(raw);
if (matcher.find() == (onMatch == OnMatch.INCLUDE)) {
if (contentPattern.matcher(raw).find() == (onMatch == OnMatch.INCLUDE)) {
return delegateStep.format(raw, file);
} else {
return raw;
}
}

@Override
public List<Lint> lint(String raw, File file) throws Exception {
Objects.requireNonNull(raw, "raw");
Objects.requireNonNull(file, "file");
if (contentPattern.matcher(raw).find() == (onMatch == OnMatch.INCLUDE)) {
return delegateStep.lint(raw, file);
} else {
return List.of();
}
}

@Override
public boolean equals(Object o) {
if (this == o) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2016-2022 DiffPlug
* Copyright 2016-2024 DiffPlug
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -16,6 +16,7 @@
package com.diffplug.spotless;

import java.io.File;
import java.util.List;
import java.util.Objects;

import javax.annotation.Nullable;
Expand All @@ -39,6 +40,17 @@ final class FilterByFileFormatterStep extends DelegateFormatterStep {
}
}

@Override
public List<Lint> lint(String content, File file) throws Exception {
Objects.requireNonNull(content, "content");
Objects.requireNonNull(file, "file");
if (filter.accept(file)) {
return delegateStep.lint(content, file);
} else {
return List.of();
}
}

@Override
public boolean equals(Object o) {
if (this == o) {
Expand Down
39 changes: 0 additions & 39 deletions lib/src/main/java/com/diffplug/spotless/FormatExceptionPolicy.java

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2016 DiffPlug
* Copyright 2016-2024 DiffPlug
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -23,7 +23,7 @@
* A policy for handling exceptions in the format. Any exceptions will
* halt the build except for a specifically excluded path or step.
*/
public class FormatExceptionPolicyStrict extends NoLambda.EqualityBasedOnSerialization implements FormatExceptionPolicy {
public class FormatExceptionPolicyStrict extends NoLambda.EqualityBasedOnSerialization {
private static final long serialVersionUID = 1L;

private final Set<String> excludeSteps = new TreeSet<>();
Expand All @@ -39,18 +39,17 @@ public void excludePath(String relativePath) {
excludePaths.add(Objects.requireNonNull(relativePath));
}

@Override
public void handleError(Throwable e, FormatterStep step, String relativePath) {
Objects.requireNonNull(e, "e");
Objects.requireNonNull(step, "step");
Objects.requireNonNull(relativePath, "relativePath");
if (excludeSteps.contains(step.getName())) {
FormatExceptionPolicyLegacy.warning(e, step, relativePath);
LintPolicy.warning(e, step, relativePath);
} else {
if (excludePaths.contains(relativePath)) {
FormatExceptionPolicyLegacy.warning(e, step, relativePath);
LintPolicy.warning(e, step, relativePath);
} else {
FormatExceptionPolicyLegacy.error(e, step, relativePath);
LintPolicy.error(e, step, relativePath);
throw ThrowingEx.asRuntimeRethrowError(e);
}
}
Expand Down
39 changes: 31 additions & 8 deletions lib/src/main/java/com/diffplug/spotless/Formatter.java
Original file line number Diff line number Diff line change
Expand Up @@ -127,28 +127,51 @@ public String computeLineEndings(String unix, File file) {
* is guaranteed to also have unix line endings.
*/
public String compute(String unix, File file) {
ValuePerStep<Throwable> exceptionPerStep = new ValuePerStep<>(this);
String result = computeWithLint(unix, file, exceptionPerStep);
LintPolicy.legacyBehavior(this, file, exceptionPerStep);
return result;
}

/**
* Returns the result of calling all of the FormatterSteps, while also
* tracking any exceptions which are thrown.
* <p>
* The input must have unix line endings, and the output
* is guaranteed to also have unix line endings.
* <p>
* It doesn't matter what is inside `ValuePerStep`, the value at every index will be overwritten
* when the method returns.
*/
String computeWithLint(String unix, File file, ValuePerStep<Throwable> exceptionPerStep) {
Objects.requireNonNull(unix, "unix");
Objects.requireNonNull(file, "file");

for (FormatterStep step : steps) {
for (int i = 0; i < steps.size(); ++i) {
FormatterStep step = steps.get(i);
Throwable storeForStep;
try {
String formatted = step.format(unix, file);
if (formatted == null) {
// This probably means it was a step that only checks
// for errors and doesn't actually have any fixes.
// No exception was thrown so we can just continue.
storeForStep = LintState.formatStepCausedNoChange();
} else {
// Should already be unix-only, but some steps might misbehave.
unix = LineEnding.toUnix(formatted);
String clean = LineEnding.toUnix(formatted);
if (clean.equals(unix)) {
storeForStep = LintState.formatStepCausedNoChange();
} else {
storeForStep = null;
unix = LineEnding.toUnix(formatted);
}
}
} catch (Throwable e) {
// TODO: this is bad, but it won't matter when add support for linting
if (e instanceof RuntimeException) {
throw (RuntimeException) e;
} else {
throw new RuntimeException(e);
}
// store the exception which was thrown and keep going
storeForStep = e;
}
exceptionPerStep.set(i, storeForStep);
}
return unix;
}
Expand Down
26 changes: 26 additions & 0 deletions lib/src/main/java/com/diffplug/spotless/FormatterFunc.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
package com.diffplug.spotless;

import java.io.File;
import java.util.List;
import java.util.Objects;

/**
Expand All @@ -32,6 +33,14 @@ default String apply(String unix, File file) throws Exception {
return apply(unix);
}

/**
* Calculates a list of lints against the given content.
* By default, that's just an throwables thrown by the lint.
*/
default List<Lint> lint(String content, File file) throws Exception {
return List.of();
}

/**
* {@code Function<String, String>} and {@code BiFunction<String, File, String>} whose implementation
* requires a resource which should be released when the function is no longer needed.
Expand Down Expand Up @@ -74,6 +83,14 @@ public String apply(String unix) throws Exception {
@FunctionalInterface
interface ResourceFunc<T extends AutoCloseable> {
String apply(T resource, String unix) throws Exception;

/**
* Calculates a list of lints against the given content.
* By default, that's just an throwables thrown by the lint.
*/
default List<Lint> lint(T resource, String unix) throws Exception {
return List.of();
}
}

/** Creates a {@link FormatterFunc.Closeable} which uses the given resource to execute the format function. */
Expand Down Expand Up @@ -101,6 +118,10 @@ public String apply(String unix) throws Exception {
@FunctionalInterface
interface ResourceFuncNeedsFile<T extends AutoCloseable> {
String apply(T resource, String unix, File file) throws Exception;

default List<Lint> lint(T resource, String content, File file) throws Exception {
return List.of();
}
}

/** Creates a {@link FormatterFunc.Closeable} which uses the given resource to execute the file-dependent format function. */
Expand All @@ -123,6 +144,11 @@ public String apply(String unix, File file) throws Exception {
public String apply(String unix) throws Exception {
return apply(unix, Formatter.NO_FILE_SENTINEL);
}

@Override
public List<Lint> lint(String content, File file) throws Exception {
return function.lint(resource, content, file);
}
};
}
}
Expand Down
17 changes: 17 additions & 0 deletions lib/src/main/java/com/diffplug/spotless/FormatterStep.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

import java.io.File;
import java.io.Serializable;
import java.util.List;
import java.util.Objects;

import javax.annotation.Nullable;
Expand Down Expand Up @@ -46,6 +47,22 @@ public interface FormatterStep extends Serializable, AutoCloseable {
@Nullable
String format(String rawUnix, File file) throws Exception;

/**
* Returns a list of lints against the given file content
*
* @param content
* the content to check
* @param file
* the file which {@code content} was obtained from; never null. Pass an empty file using
* {@code new File("")} if and only if no file is actually associated with {@code content}
* @return a list of lints
* @throws Exception if the formatter step experiences a problem
*/
@Nullable
default List<Lint> lint(String content, File file) throws Exception {
return List.of();
}

/**
* Returns a new {@code FormatterStep} which, observing the value of {@code formatIfMatches},
* will only apply, or not, its changes to files which pass the given filter.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import java.io.File;
import java.io.Serializable;
import java.util.Arrays;
import java.util.List;

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

Expand Down Expand Up @@ -48,6 +49,14 @@ public String format(String rawUnix, File file) throws Exception {
return formatter.apply(rawUnix, file);
}

@Override
public List<Lint> lint(String content, File file) throws Exception {
if (formatter == null) {
formatter = stateToFormatter(state());
}
return formatter.lint(content, file);
}

@Override
public boolean equals(Object o) {
if (o == null) {
Expand Down
Loading
Loading