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

Make padded cell mandatory #561

Merged
merged 19 commits into from
May 3, 2020
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
a86e56c
Remove `paddedCell` option throughout Spotless, and deprecate its get…
nedtwigg Apr 30, 2020
eeb85ba
Update documentation for the always-paddedcell future.
nedtwigg Apr 30, 2020
ed81aad
Fix compile-errors in the "remove paddedcell" adventure.
nedtwigg Apr 30, 2020
f8ab7ee
Update spotbugs to latest.
nedtwigg Apr 30, 2020
d4af11f
Fix tests now that PaddedCell isn't present.
nedtwigg Apr 30, 2020
f47d6bb
Refactor PaddedCellBulk's important API into PaddedCell.canonicalIfDirty
nedtwigg Apr 30, 2020
345c519
Improve performance for the common case where PaddedCell is not needed.
nedtwigg Apr 30, 2020
18719fd
Deprecated all of PaddedCellBulk, deleted PaddedCellGradle.
nedtwigg Apr 30, 2020
8f54aa1
Renamed PaddedCell.canonicalIfDirty to PaddedCell.calculateDirtyState…
nedtwigg May 1, 2020
1684d65
Added the spotlessDiagnose task, which does what used to be baked-in …
nedtwigg May 1, 2020
7b81436
Fix spotbugs warning.
nedtwigg May 1, 2020
df30fdb
Small change to the PaddedCell.DirtyState API.
nedtwigg May 1, 2020
d8db03f
Minor changelog improvement.
nedtwigg May 1, 2020
9bfdc90
Update the lib changelog.
nedtwigg May 1, 2020
b9ac203
Improve the padded cell documentation.
nedtwigg May 2, 2020
8c1a006
Protect `PaddedCell.DirtyState` byte[] from user corruption
nedtwigg May 2, 2020
58def19
Merge branch 'master' into feat/paddedcell-mandatory
nedtwigg May 3, 2020
5fbf5bb
Oops! Meant to get the plain OutputStream, not the CORBA one.
nedtwigg May 3, 2020
36a8f97
Revert the spotbugs upgrade.
nedtwigg May 3, 2020
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 CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ This document is intended for Spotless developers.
We adhere to the [keepachangelog](https://keepachangelog.com/en/1.0.0/) format (starting after version `1.27.0`).

## [Unreleased]
* Improved PaddedCell such that it is as performant as non-padded cell - no reason not to have it always enabled. Deprecated all of `PaddedCellBulk`. ([#561](https://github.com/diffplug/spotless/pull/561))

## [1.28.1] - 2020-04-02
### Fixed
Expand Down
17 changes: 1 addition & 16 deletions PADDEDCELL.md
Original file line number Diff line number Diff line change
@@ -1,19 +1,4 @@
# You have a misbehaving rule that needs a `paddedCell()`

`spotlessCheck` has detected that one of your rules is misbehaving. This will cause `spotlessCheck` to fail even after you have called `spotlessApply`. To bandaid over this problem, add `paddedCell()` to your `build.gradle`, as such:

```gradle
spotless {
java {
...
paddedCell()
}
}
```

This is not a bug in Spotless itself, but in one of the third-party formatters, such as the [eclipse formatter](https://bugs.eclipse.org/bugs/show_bug.cgi?id=310642), [google-java-format](https://github.com/google/google-java-format/issues), or some custom rule.

`paddedCell()` will ensure that your code continues to be formatted, although it will be a little slower. Now when you run `spotlessCheck`, it will generate helpful bug report files in the `build/spotless-diagnose-<FORMAT_NAME>` folder which will contain the states that your rules are fighting over. These files are very helpful to the developers of the code formatter you are using.
# PaddedCell

## How spotless works

Expand Down
4 changes: 2 additions & 2 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ plugins {
// https://github.com/mnlipp/jdrupes-mdoclet/releases
id 'org.jdrupes.mdoclet' version '1.0.9' apply false
// https://github.com/spotbugs/spotbugs/releases
id "com.github.spotbugs" version "2.0.0" apply false
id "com.github.spotbugs" version "4.0.8" apply false
//https://github.com/diffplug/goomph
id "com.diffplug.p2.asmaven" version "3.21.0" apply false
// https://github.com/diffplug/spotless-changelog
Expand Down Expand Up @@ -47,5 +47,5 @@ eclipseResourceFilters {
}

static Class<?> spotBugsTaskType() {
return com.github.spotbugs.SpotBugsTask
return com.github.spotbugs.snom.SpotBugsTask
}
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ artifactIdGradle=spotless-plugin-gradle

# Build requirements
VER_JAVA=1.8
VER_SPOTBUGS=3.1.6
VER_SPOTBUGS=4.0.2

# Dependencies provided by Spotless plugin
VER_SLF4J=[1.6,2.0[
Expand Down
16 changes: 8 additions & 8 deletions gradle/java-setup.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -34,23 +34,23 @@ eclipseResourceFilters {
apply plugin: 'com.github.spotbugs'
spotbugs {
toolVersion = VER_SPOTBUGS
sourceSets = [
// don't check the test code
sourceSets.main
]
ignoreFailures = false // bug free or it doesn't ship!
reportsDir = file('build/spotbugs')
effort = 'max' // min|default|max
reportLevel = 'medium' // low|medium|high (low = sensitive to even minor mistakes)
omitVisitors = [] // bugs that we want to ignore
}
// HTML instead of XML
tasks.withType(spotBugsTaskType()) {
tasks.named('spotbugsTest') {
enabled = false
}
spotbugsMain {
reports {
xml.enabled = false
html.enabled = true
html {
enabled = true
}
}
}

dependencies {
compileOnly 'net.jcip:jcip-annotations:1.0'
compileOnly 'com.github.spotbugs:spotbugs-annotations:3.1.6'
nedtwigg marked this conversation as resolved.
Show resolved Hide resolved
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,6 @@ public static class Builder {
private Builder() {}

private String runToFix;
private boolean isPaddedCell;
private Formatter formatter;
private List<File> problemFiles;

Expand All @@ -58,8 +57,9 @@ public Builder runToFix(String runToFix) {
return this;
}

@Deprecated
public Builder isPaddedCell(boolean isPaddedCell) {
this.isPaddedCell = isPaddedCell;
System.err.println("PaddedCell is now always on, and cannot be turned off.");
return this;
}

Expand Down Expand Up @@ -171,11 +171,7 @@ private static String diff(Builder builder, File file) throws IOException {
String raw = new String(Files.readAllBytes(file.toPath()), builder.formatter.getEncoding());
String rawUnix = LineEnding.toUnix(raw);
String formattedUnix;
if (builder.isPaddedCell) {
formattedUnix = PaddedCell.check(builder.formatter, file, rawUnix).canonical();
} else {
formattedUnix = builder.formatter.compute(rawUnix, file);
}
formattedUnix = PaddedCell.check(builder.formatter, file, rawUnix).canonical();

if (rawUnix.equals(formattedUnix)) {
// the formatting is fine, so it's a line-ending issue
Expand Down
80 changes: 80 additions & 0 deletions lib/src/main/java/com/diffplug/spotless/PaddedCell.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,10 @@
import static com.diffplug.spotless.LibPreconditions.requireElementsNonNull;

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
Expand Down Expand Up @@ -171,4 +173,82 @@ public String userMessage() {
}
// @formatter:on
}

/**
* 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.
*/
public static DirtyState calculateDirtyState(Formatter formatter, File file) throws IOException {
Objects.requireNonNull(formatter, "formatter");
Objects.requireNonNull(file, "file");

byte[] rawBytes = Files.readAllBytes(file.toPath());
String raw = new String(rawBytes, formatter.getEncoding());
String rawUnix = LineEnding.toUnix(raw);

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

// if F(input) == input, then the formatter is well-behaving and the input is clean
byte[] formattedBytes = formatted.getBytes(formatter.getEncoding());
if (Arrays.equals(rawBytes, formattedBytes)) {
return isClean;
}

// F(input) != input, so we'll do a padded check
String doubleFormattedUnix = formatter.compute(formattedUnix, file);
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);
if (!cell.isResolvable()) {
return didNotConverge;
}

// get the canonical bytes
String canonicalUnix = cell.canonical();
String canonical = formatter.computeLineEndings(canonicalUnix, file);
byte[] canonicalBytes = canonical.getBytes(formatter.getEncoding());
if (!Arrays.equals(rawBytes, canonicalBytes)) {
// and write them to disk if needed
return new DirtyState(canonicalBytes);
} else {
return isClean;
}
}

/**
* The clean/dirty state of a single file. Intended use:
* - {@link #isClean()} means that the file is is clean, and there's nothing else to say
* - {@link #isConverged()} means that we were able to determine a clean state
* - once you've tested the above conditions and you know that it's a dirty file with a converged state,
* then you can call {@link #canonicalBytes()} to get the canonical form of the given file.
*/
public static class DirtyState {
private final byte[] canonicalBytes;

private DirtyState(byte[] canonicalBytes) {
this.canonicalBytes = canonicalBytes;
}

public boolean isClean() {
return this == isClean;
}

public boolean didNotConverge() {
return this == didNotConverge;
}

public byte[] canonicalBytes() {
return Objects.requireNonNull(canonicalBytes, "First make sure that `!isClean()` and `!didNotConverge()`");
nedtwigg marked this conversation as resolved.
Show resolved Hide resolved
}
nedtwigg marked this conversation as resolved.
Show resolved Hide resolved
}

private static final DirtyState didNotConverge = new DirtyState(null);
private static final DirtyState isClean = new DirtyState(null);
}
66 changes: 11 additions & 55 deletions lib/src/main/java/com/diffplug/spotless/PaddedCellBulk.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@
import java.nio.file.StandardOpenOption;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
Expand All @@ -35,27 +34,8 @@

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

/**
* Incorporates the PaddedCell machinery into broader apply / check usage.
*
* Here's the general workflow:
*
* ### Identify that paddedCell is needed
*
* Initially, paddedCell is off. That's the default, and there's no need for users to know about it.
*
* If they encounter a scenario where `spotlessCheck` fails after calling `spotlessApply`, then they would
* justifiably be frustrated. Luckily, every time `spotlessCheck` fails, it passes the failed files to
* {@link #anyMisbehave(Formatter, List)}, which checks to see if any of the rules are causing a cycle
* or some other kind of mischief. If they are, it can give the user a special error message instructing
* them to turn on paddedCell.
*
* ### spotlessCheck with paddedCell on
*
* Spotless check behaves as normal, finding a list of problem files, but then passes that list
* to {@link #check(File, File, Formatter, List)}. If there were no problem files, then `paddedCell`
* is no longer necessary, so users might as well turn it off, so we give that info as a warning.
*/
/** COMPLETELY DEPRECATED, use {@link PaddedCell#canonicalIfDirty(Formatter, File)} instead. */
@Deprecated
public final class PaddedCellBulk {
private static final Logger logger = Logger.getLogger(PaddedCellBulk.class.getName());

Expand All @@ -72,11 +52,13 @@ public final class PaddedCellBulk {
* tell the user about a misbehaving rule and alert her to how to enable
* paddedCell mode, with minimal effort.
*/
@Deprecated
public static boolean anyMisbehave(Formatter formatter, List<File> problemFiles) {
return anyMisbehave(formatter, problemFiles, 500);
}

/** Same as {@link #anyMisbehave(Formatter, List)} but with a customizable timeout. */
@Deprecated
public static boolean anyMisbehave(Formatter formatter, List<File> problemFiles, long timeoutMs) {
Objects.requireNonNull(formatter, "formatter");
Objects.requireNonNull(problemFiles, "problemFiles");
Expand Down Expand Up @@ -105,6 +87,7 @@ public static boolean anyMisbehave(Formatter formatter, List<File> problemFiles,
* @return A list of files which are failing because of paddedCell problems, but could be fixed. (specifically, the files for which spotlessApply would be effective)
*/
@SuppressFBWarnings("NP_NULL_ON_SOME_PATH_FROM_RETURN_VALUE")
@Deprecated
public static List<File> check(File rootDir, File diagnoseDir, Formatter formatter, List<File> problemFiles) throws IOException {
Objects.requireNonNull(rootDir, "rootDir");
Objects.requireNonNull(diagnoseDir, "diagnoseDir");
Expand Down Expand Up @@ -191,47 +174,20 @@ public String getName() {
}

/** Performs the typical spotlessApply, but with PaddedCell handling of misbehaving FormatterSteps. */
@Deprecated
public static void apply(Formatter formatter, File file) throws IOException {
applyAnyChanged(formatter, file);
}

/** Performs the typical spotlessApply, but with PaddedCell handling of misbehaving FormatterSteps. */
@Deprecated
public static boolean applyAnyChanged(Formatter formatter, File file) throws IOException {
Objects.requireNonNull(formatter, "formatter");
Objects.requireNonNull(file, "file");

byte[] rawBytes = Files.readAllBytes(file.toPath());
String raw = new String(rawBytes, formatter.getEncoding());
String rawUnix = LineEnding.toUnix(raw);

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

// if F(input) == input, then the formatter is well-behaving and the input is clean
byte[] formattedBytes = formatted.getBytes(formatter.getEncoding());
if (Arrays.equals(rawBytes, formattedBytes)) {
PaddedCell.DirtyState dirtyState = PaddedCell.calculateDirtyState(formatter, file);
if (dirtyState.isClean() || dirtyState.didNotConverge()) {
return false;
}

// F(input) != input, so we'll do a padded check
PaddedCell cell = PaddedCell.check(formatter, file, rawUnix);
if (!cell.isResolvable()) {
// nothing we can do, but check will warn and dump out the divergence path
return false;
}

// get the canonical bytes
String canonicalUnix = cell.canonical();
String canonical = formatter.computeLineEndings(canonicalUnix, file);
byte[] canonicalBytes = canonical.getBytes(formatter.getEncoding());
if (!Arrays.equals(rawBytes, canonicalBytes)) {
// and write them to disk if needed
Files.write(file.toPath(), canonicalBytes, StandardOpenOption.TRUNCATE_EXISTING);
return true;
} else {
return false;
Files.write(file.toPath(), dirtyState.canonicalBytes(), StandardOpenOption.TRUNCATE_EXISTING);
return true;
}
}

Expand Down
2 changes: 2 additions & 0 deletions plugin-gradle/CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
We adhere to the [keepachangelog](https://keepachangelog.com/en/1.0.0/) format (starting after version `3.27.0`).

## [Unreleased]
### Changed
* PaddedCell is now always enabled. It is strictly better than non-padded cell, and there is no performance penalty. [See here](https://github.com/diffplug/spotless/pull/560#issuecomment-621752798) for detailed explanation. ([#561](https://github.com/diffplug/spotless/pull/561))

## [3.28.1] - 2020-04-02
### Fixed
Expand Down
3 changes: 0 additions & 3 deletions plugin-gradle/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -168,8 +168,6 @@ Configuration for Groovy is similar to [Java](#java). Most java steps, like `li

The groovy formatter's default behavior is to format all `.groovy` and `.java` files found in the Groovy source directories. If you would like to exclude the `.java` files, set the parameter `excludeJava`, or you can set the `target` parameter as described in the [Custom rules](#custom) section.

Due to cyclic ambiguities of groovy formatter results, e.g. for nested closures, the use of [paddedCell()](../PADDEDCELL.md) and/or [Custom rules](#custom) is recommended to bandaid over this third-party formatter problem.

```gradle
apply plugin: 'groovy'
...
Expand All @@ -182,7 +180,6 @@ spotless {
groovy {
licenseHeaderFile 'spotless.license.java'
excludeJava() // excludes all Java sources within the Groovy source dirs from formatting
paddedCell() // Avoid cyclic ambiguities
// the Groovy Eclipse formatter extends the Java Eclipse formatter,
// so it formats Java files by default (unless `excludeJava` is used).
greclipse().configFile('greclipse.properties')
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,16 +67,16 @@ private String formatName() {
throw new IllegalStateException("This format is not contained by any SpotlessExtension.");
}

boolean paddedCell = false;

/** Enables paddedCell mode. @see <a href="https://github.com/diffplug/spotless/blob/master/PADDEDCELL.md">Padded cell</a> */
@Deprecated
public void paddedCell() {
paddedCell(true);
}

/** Enables paddedCell mode. @see <a href="https://github.com/diffplug/spotless/blob/master/PADDEDCELL.md">Padded cell</a> */
@Deprecated
public void paddedCell(boolean paddedCell) {
this.paddedCell = paddedCell;
root.project.getLogger().warn("PaddedCell is now always on, and cannot be turned off.");
}

LineEnding lineEndings;
Expand Down Expand Up @@ -593,7 +593,6 @@ public EclipseWtpConfig eclipseWtp(EclipseWtpFormatterStep type, String version)

/** Sets up a format task according to the values in this extension. */
protected void setupTask(SpotlessTask task) {
task.setPaddedCell(paddedCell);
task.setEncoding(getEncoding().name());
task.setExceptionPolicy(exceptionPolicy);
if (targetExclude == null) {
Expand Down
Loading