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

Serializable refactor pipe/fence #1954

Merged
merged 10 commits into from
Jan 24, 2024
1 change: 1 addition & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ We adhere to the [keepachangelog](https://keepachangelog.com/en/1.0.0/) format (
* `FileSignature.Promised` and `JarState.Promised` to facilitate round-trip serialization for the Gradle configuration cache. ([#1945](https://github.com/diffplug/spotless/pull/1945))
### Removed
* **BREAKING** Remove `JarState.getMavenCoordinate(String prefix)`. ([#1945](https://github.com/diffplug/spotless/pull/1945))
* **BREAKING** Replace `PipeStepPair` with `FenceStep`. ([#1954](https://github.com/diffplug/spotless/pull/1954))
### Fixed
* Ignore system git config when running tests ([#1990](https://github.com/diffplug/spotless/issues/1990))

Expand Down
222 changes: 222 additions & 0 deletions lib/src/main/java/com/diffplug/spotless/generic/FenceStep.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,222 @@
/*
* Copyright 2020-2024 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.generic;

import java.io.File;
import java.io.Serializable;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import com.diffplug.spotless.Formatter;
import com.diffplug.spotless.FormatterFunc;
import com.diffplug.spotless.FormatterStep;
import com.diffplug.spotless.LineEnding;
import com.diffplug.spotless.SerializedFunction;

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

public class FenceStep {
/** Declares the name of the step. */
public static FenceStep named(String name) {
return new FenceStep(name);
}

public static String defaultToggleName() {
return "toggle";
}

public static String defaultToggleOff() {
return "spotless:off";
}

public static String defaultToggleOn() {
return "spotless:on";
}

String name;
Pattern regex;

private FenceStep(String name) {
this.name = Objects.requireNonNull(name);
}

/** Defines the opening and closing markers. */
public FenceStep openClose(String open, String close) {
return regex(Pattern.quote(open) + "([\\s\\S]*?)" + Pattern.quote(close));
}

/** Defines the pipe via regex. Must have *exactly one* capturing group. */
public FenceStep regex(String regex) {
return regex(Pattern.compile(regex));
}

/** Defines the pipe via regex. Must have *exactly one* capturing group. */
public FenceStep regex(Pattern regex) {
this.regex = Objects.requireNonNull(regex);
return this;
}

private void assertRegexSet() {
Objects.requireNonNull(regex, "must call regex() or openClose()");
}

/** Returns a step which will apply the given steps but preserve the content selected by the regex / openClose pair. */
public FormatterStep preserveWithin(List<FormatterStep> steps) {
assertRegexSet();
return FormatterStep.createLazy(name,
() -> new PreserveWithin(regex, steps),
SerializedFunction.identity(),
state -> FormatterFunc.Closeable.of(state.buildFormatter(), state));
}

/**
* Returns a step which will apply the given steps only within the blocks selected by the regex / openClose pair.
* Linting within the substeps is not supported.
*/
public FormatterStep applyWithin(List<FormatterStep> steps) {
assertRegexSet();
return FormatterStep.createLazy(name,
() -> new ApplyWithin(regex, steps),
SerializedFunction.identity(),
state -> FormatterFunc.Closeable.of(state.buildFormatter(), state));
}

static class ApplyWithin extends Apply implements FormatterFunc.Closeable.ResourceFuncNeedsFile<Formatter> {
private static final long serialVersionUID = 17061466531957339L;

ApplyWithin(Pattern regex, List<FormatterStep> steps) {
super(regex, steps);
}

@Override
public String apply(Formatter formatter, String unix, File file) throws Exception {
List<String> groups = groupsZeroed();
Matcher matcher = regex.matcher(unix);
while (matcher.find()) {
// apply the formatter to each group
groups.add(formatter.compute(matcher.group(1), file));
}
// and then assemble the result right away
return assembleGroups(unix);
}
}

static class PreserveWithin extends Apply implements FormatterFunc.Closeable.ResourceFuncNeedsFile<Formatter> {
private static final long serialVersionUID = -8676786492305178343L;

PreserveWithin(Pattern regex, List<FormatterStep> steps) {
super(regex, steps);
}

private void storeGroups(String unix) {
List<String> groups = groupsZeroed();
Matcher matcher = regex.matcher(unix);
while (matcher.find()) {
// store whatever is within the open/close tags
groups.add(matcher.group(1));
}
}

@Override
public String apply(Formatter formatter, String unix, File file) throws Exception {
storeGroups(unix);
String formatted = formatter.compute(unix, file);
return assembleGroups(formatted);
}
}

@SuppressFBWarnings("SE_TRANSIENT_FIELD_NOT_RESTORED")
static class Apply implements Serializable {
private static final long serialVersionUID = -2301848328356559915L;
final Pattern regex;
final List<FormatterStep> steps;

transient ArrayList<String> groups = new ArrayList<>();
transient StringBuilder builderInternal;

public Apply(Pattern regex, List<FormatterStep> steps) {
this.regex = regex;
this.steps = steps;
}

protected ArrayList<String> groupsZeroed() {
if (groups == null) {
groups = new ArrayList<>();
} else {
groups.clear();
}
return groups;
}

private StringBuilder builderZeroed() {
if (builderInternal == null) {
builderInternal = new StringBuilder();
} else {
builderInternal.setLength(0);
}
return builderInternal;
}

protected Formatter buildFormatter() {
return Formatter.builder()
.encoding(StandardCharsets.UTF_8) // can be any UTF, doesn't matter
.lineEndingsPolicy(LineEnding.UNIX.createPolicy()) // just internal, won't conflict with user
.steps(steps)
.rootDir(Path.of("")) // TODO: error messages will be suboptimal for now, but it will get fixed when we ship linting
.build();
}

protected String assembleGroups(String unix) {
if (groups.isEmpty()) {
return unix;
}
StringBuilder builder = builderZeroed();
Matcher matcher = regex.matcher(unix);
int lastEnd = 0;
int groupIdx = 0;
while (matcher.find()) {
builder.append(unix, lastEnd, matcher.start(1));
builder.append(groups.get(groupIdx));
lastEnd = matcher.end(1);
++groupIdx;
}
if (groupIdx == groups.size()) {
builder.append(unix, lastEnd, unix.length());
return builder.toString();
} else {
// these will be needed to generate Lints later on
// int startLine = 1 + (int) builder.toString().codePoints().filter(c -> c == '\n').count();
// int endLine = 1 + (int) unix.codePoints().filter(c -> c == '\n').count();

// throw an error with either the full regex, or the nicer open/close pair
Matcher openClose = Pattern.compile("\\\\Q([\\s\\S]*?)\\\\E" + "\\Q([\\s\\S]*?)\\E" + "\\\\Q([\\s\\S]*?)\\\\E")
.matcher(regex.pattern());
String pattern;
if (openClose.matches()) {
pattern = openClose.group(1) + " " + openClose.group(2);
} else {
pattern = regex.pattern();
}
throw new Error("An intermediate step removed a match of " + pattern);
}
}
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2020-2021 DiffPlug
* Copyright 2020-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 Down Expand Up @@ -33,6 +33,10 @@

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

/**
* @deprecated use FenceStep instead
*/
@Deprecated
public class PipeStepPair {
/** The two steps will be named {@code <name>In} and {@code <name>Out}. */
public static Builder named(String name) {
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 Down Expand Up @@ -57,11 +57,11 @@
import com.diffplug.spotless.extra.EclipseBasedStepBuilder;
import com.diffplug.spotless.extra.wtp.EclipseWtpFormatterStep;
import com.diffplug.spotless.generic.EndWithNewlineStep;
import com.diffplug.spotless.generic.FenceStep;
import com.diffplug.spotless.generic.IndentStep;
import com.diffplug.spotless.generic.LicenseHeaderStep;
import com.diffplug.spotless.generic.LicenseHeaderStep.YearMode;
import com.diffplug.spotless.generic.NativeCmdStep;
import com.diffplug.spotless.generic.PipeStepPair;
import com.diffplug.spotless.generic.ReplaceRegexStep;
import com.diffplug.spotless.generic.ReplaceStep;
import com.diffplug.spotless.generic.TrimTrailingWhitespaceStep;
Expand Down Expand Up @@ -989,7 +989,7 @@ public void withinBlocks(String name, String open, String close, Action<FormatEx
*/
public <T extends FormatExtension> void withinBlocks(String name, String open, String close, Class<T> clazz,
Action<T> configure) {
withinBlocksHelper(PipeStepPair.named(name).openClose(open, close), clazz, configure);
withinBlocksHelper(FenceStep.named(name).openClose(open, close), clazz, configure);
}

/**
Expand All @@ -1007,18 +1007,17 @@ public void withinBlocksRegex(String name, String regex, Action<FormatExtension>
*/
public <T extends FormatExtension> void withinBlocksRegex(String name, String regex, Class<T> clazz,
Action<T> configure) {
withinBlocksHelper(PipeStepPair.named(name).regex(regex), clazz, configure);
withinBlocksHelper(FenceStep.named(name).regex(regex), clazz, configure);
}

private <T extends FormatExtension> void withinBlocksHelper(PipeStepPair.Builder builder, Class<T> clazz,
private <T extends FormatExtension> void withinBlocksHelper(FenceStep fence, Class<T> clazz,
Action<T> configure) {
// create the sub-extension
T formatExtension = spotless.instantiateFormatExtension(clazz);
// configure it
configure.execute(formatExtension);
// create a step which applies all of those steps as sub-steps
FormatterStep step = builder.buildStepWhichAppliesSubSteps(spotless.project.getRootDir().toPath(),
formatExtension.steps);
FormatterStep step = fence.applyWithin(formatExtension.steps);
addStep(step);
}

Expand All @@ -1027,28 +1026,28 @@ private <T extends FormatExtension> void withinBlocksHelper(PipeStepPair.Builder
* that captured group.
*/
public void toggleOffOnRegex(String regex) {
this.togglePair = PipeStepPair.named(PipeStepPair.defaultToggleName()).regex(regex).buildPair();
this.toggleFence = FenceStep.named(FenceStep.defaultToggleName()).regex(regex);
}

/** Disables formatting between the given tags. */
public void toggleOffOn(String off, String on) {
this.togglePair = PipeStepPair.named(PipeStepPair.defaultToggleName()).openClose(off, on).buildPair();
this.toggleFence = FenceStep.named(FenceStep.defaultToggleName()).openClose(off, on);
}

/** Disables formatting between {@code spotless:off} and {@code spotless:on}. */
public void toggleOffOn() {
toggleOffOn(PipeStepPair.defaultToggleOff(), PipeStepPair.defaultToggleOn());
toggleOffOn(FenceStep.defaultToggleOff(), FenceStep.defaultToggleOn());
}

/**
* Undoes all previous calls to {@link #toggleOffOn()} and
* {@link #toggleOffOn(String, String)}.
*/
public void toggleOffOnDisable() {
this.togglePair = null;
this.toggleFence = null;
}

private @Nullable PipeStepPair togglePair;
private @Nullable FenceStep toggleFence;

/** Sets up a format task according to the values in this extension. */
protected void setupTask(SpotlessTask task) {
Expand All @@ -1057,11 +1056,8 @@ protected void setupTask(SpotlessTask task) {
FileCollection totalTarget = targetExclude == null ? target : target.minus(targetExclude);
task.setTarget(totalTarget);
List<FormatterStep> steps;
if (togglePair != null) {
steps = new ArrayList<>(this.steps.size() + 2);
steps.add(togglePair.in());
steps.addAll(this.steps);
steps.add(togglePair.out());
if (toggleFence != null) {
steps = List.of(toggleFence.preserveWithin(this.steps));
} else {
steps = this.steps;
}
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 Down Expand Up @@ -36,7 +36,6 @@
import com.diffplug.spotless.Formatter;
import com.diffplug.spotless.FormatterStep;
import com.diffplug.spotless.LineEnding;
import com.diffplug.spotless.generic.PipeStepPair;
import com.diffplug.spotless.maven.generic.EclipseWtp;
import com.diffplug.spotless.maven.generic.EndWithNewline;
import com.diffplug.spotless.maven.generic.Indent;
Expand Down Expand Up @@ -97,9 +96,8 @@ public final Formatter newFormatter(Supplier<Iterable<File>> filesToFormat, Form
.map(factory -> factory.newFormatterStep(stepConfig))
.collect(Collectors.toCollection(() -> new ArrayList<FormatterStep>()));
if (toggle != null) {
PipeStepPair pair = toggle.createPair();
formatterSteps.add(0, pair.in());
formatterSteps.add(pair.out());
List<FormatterStep> formatterStepsBeforeToggle = formatterSteps;
formatterSteps = List.of(toggle.createFence().preserveWithin(formatterStepsBeforeToggle));
}

String formatterName = this.getClass().getSimpleName();
Expand Down
Loading
Loading