Skip to content

Commit 86c6f96

Browse files
authored
Merge pull request #637 - adds FormatterStep.NeedsFile
2 parents 6797d93 + f57e557 commit 86c6f96

File tree

8 files changed

+66
-63
lines changed

8 files changed

+66
-63
lines changed

CHANGES.md

+1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ We adhere to the [keepachangelog](https://keepachangelog.com/en/1.0.0/) format (
1616
* `prettier` will now autodetect the parser (and formatter) to use based on the filename, unless you override this using `config` or `configFile` with the option `parser` or `filepath`. ([#620](https://github.com/diffplug/spotless/pull/620))
1717
* `GitRatchet` now lives in `lib-extra`, and is shared across `plugin-gradle` and `plugin-maven` ([#626](https://github.com/diffplug/spotless/pull/626)).
1818
* Added ANTLR4 support ([#326](https://github.com/diffplug/spotless/issues/326)).
19+
* `FormatterFunc.NeedsFile` for implementing file-based formatters more cleanly than we have so far ([#637](https://github.com/diffplug/spotless/issues/637)).
1920
### Changed
2021
* **BREAKING** `FileSignature` can no longer sign folders, only files. Signatures are now based only on filename (not path), size, and a content hash. It throws an error if a signature is attempted on a folder or on multiple files with different paths but the same filename - it never breaks silently. This change does not break any of Spotless' internal logic, so it is unlikely to affect any of Spotless' consumers either. ([#571](https://github.com/diffplug/spotless/pull/571))
2122
* This change allows the maven plugin to cache classloaders across subprojects when loading config resources from the classpath (fixes [#559](https://github.com/diffplug/spotless/issues/559)).

CONTRIBUTING.md

+6-2
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ For the folders below in monospace text, they are published on maven central at
4444
The easiest way to create a FormatterStep is `FormatterStep createNeverUpToDate(String name, FormatterFunc function)`, which you can use like this:
4545

4646
```java
47-
FormatterStep identityStep = FormatterStep.createNeverUpToDate("identity", unix -> unix)
47+
FormatterStep identityStep = FormatterStep.createNeverUpToDate("identity", unixStr -> unixStr)
4848
```
4949

5050
This creates a step which will fail up-to-date checks (it is equal only to itself), and will use the function you passed in to do the formatting pass.
@@ -73,7 +73,7 @@ public final class ReplaceStep {
7373
}
7474

7575
FormatterFunc toFormatter() {
76-
return raw -> raw.replace(target, replacement);
76+
return unixStr -> unixStr.replace(target, replacement);
7777
}
7878
}
7979
}
@@ -100,6 +100,10 @@ Here's a checklist for creating a new step for Spotless:
100100
- [ ] Test class has test methods to verify behavior.
101101
- [ ] Test class has a test method `equality()` which tests equality using `StepEqualityTester` (see existing methods for examples).
102102

103+
### Accessing the underlying File
104+
105+
In order for Spotless' model to work, each step needs to look only at the `String` input, otherwise they cannot compose. However, there are some cases where the source `File` is useful, such as to look at the file extension. In this case, you can pass a `FormatterFunc.NeedsFile` instead of a `FormatterFunc`. This should only be used in [rare circumstances](https://github.com/diffplug/spotless/pull/637), be careful that you don't accidentally depend on the bytes inside of the `File`!
106+
103107
## How to enable the _ext projects
104108

105109
The `_ext` projects are disabled per default, since:

lib-extra/src/main/java/com/diffplug/spotless/extra/wtp/EclipseWtpFormatterStep.java

+3-13
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515
*/
1616
package com.diffplug.spotless.extra.wtp;
1717

18-
import java.io.File;
1918
import java.lang.reflect.InvocationTargetException;
2019
import java.lang.reflect.Method;
2120
import java.util.Properties;
@@ -71,27 +70,18 @@ private static FormatterFunc applyWithoutFile(String className, EclipseBasedStep
7170
};
7271
}
7372

74-
private static FormatterFuncWithFile applyWithFile(String className, EclipseBasedStepBuilder.State state) throws Exception {
73+
private static FormatterFunc.NeedsFile applyWithFile(String className, EclipseBasedStepBuilder.State state) throws Exception {
7574
Class<?> formatterClazz = state.loadClass(FORMATTER_PACKAGE + className);
7675
Object formatter = formatterClazz.getConstructor(Properties.class).newInstance(state.getPreferences());
7776
Method method = formatterClazz.getMethod(FORMATTER_METHOD, String.class, String.class);
78-
return (input, source) -> {
77+
return (unixString, file) -> {
7978
try {
80-
return (String) method.invoke(formatter, input, source.getAbsolutePath());
79+
return (String) method.invoke(formatter, unixString, file.getAbsolutePath());
8180
} catch (InvocationTargetException exceptionWrapper) {
8281
Throwable throwable = exceptionWrapper.getTargetException();
8382
Exception exception = (throwable instanceof Exception) ? (Exception) throwable : null;
8483
throw (null == exception) ? exceptionWrapper : exception;
8584
}
8685
};
8786
}
88-
89-
private static interface FormatterFuncWithFile extends FormatterFunc {
90-
@Override
91-
default String apply(String input) throws Exception {
92-
throw new UnsupportedOperationException("Formatter requires file path of source.");
93-
}
94-
95-
public String apply(String input, File source) throws Exception;
96-
}
9787
}

lib/src/main/java/com/diffplug/spotless/FormatterFunc.java

+41-11
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2016 DiffPlug
2+
* Copyright 2016-2020 DiffPlug
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -19,16 +19,18 @@
1919
import java.util.Objects;
2020

2121
/**
22-
* A `Function<String, String>` which can throw an exception.
23-
* Also the `BiFunction<String, File, String>` is supported, whereas the default
24-
* implementation only requires the `Function<String, String>` implementation.
22+
* A `Function<String, String>` which can throw an exception. Technically, there
23+
* is also a `File` argument which gets passed around as well, but that is invisible
24+
* to formatters. If you need the File, see {@link NeedsFile}.
2525
*/
26+
@FunctionalInterface
2627
public interface FormatterFunc
2728
extends ThrowingEx.Function<String, String>, ThrowingEx.BiFunction<String, File, String> {
2829

29-
@Override
30-
default String apply(String input, File source) throws Exception {
31-
return apply(input);
30+
String apply(String input) throws Exception;
31+
32+
default String apply(String unix, File file) throws Exception {
33+
return apply(unix);
3234
}
3335

3436
/**
@@ -50,16 +52,44 @@ public void close() {
5052
}
5153

5254
@Override
53-
public String apply(String input, File source) throws Exception {
54-
return function.apply(Objects.requireNonNull(input), Objects.requireNonNull(source));
55+
public String apply(String unix, File file) throws Exception {
56+
return function.apply(Objects.requireNonNull(unix), Objects.requireNonNull(file));
5557
}
5658

5759
@Override
58-
public String apply(String input) throws Exception {
59-
return function.apply(Objects.requireNonNull(input));
60+
public String apply(String unix) throws Exception {
61+
return function.apply(Objects.requireNonNull(unix));
6062
}
6163
};
6264
}
6365
}
6466

67+
/**
68+
* Ideally, formatters don't need the underlying file. But in case they do, they should only use it's path,
69+
* and should never read the content inside the file, because that breaks the `Function<String, String>` composition
70+
* that Spotless is based on. For the rare case that you need access to the file, use this method
71+
* or {@link NeedsFile} to create a {@link FormatterFunc} which needs the File.
72+
*/
73+
static FormatterFunc needsFile(NeedsFile needsFile) {
74+
return needsFile;
75+
}
76+
77+
/** @see FormatterFunc#needsFile(NeedsFile) */
78+
@FunctionalInterface
79+
interface NeedsFile extends FormatterFunc {
80+
String applyWithFile(String unix, File file) throws Exception;
81+
82+
@Override
83+
default String apply(String unix, File file) throws Exception {
84+
if (file == FormatterStepImpl.SENTINEL) {
85+
throw new IllegalArgumentException("This step requires the underlying file. If this is a test, use StepHarnessWithFile");
86+
}
87+
return applyWithFile(unix, file);
88+
}
89+
90+
@Override
91+
default String apply(String unix) throws Exception {
92+
return apply(unix, FormatterStepImpl.SENTINEL);
93+
}
94+
}
6595
}

lib/src/main/java/com/diffplug/spotless/FormatterStepImpl.java

+4-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2016 DiffPlug
2+
* Copyright 2016-2020 DiffPlug
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -110,4 +110,7 @@ protected String format(Integer state, String rawUnix, File file) throws Excepti
110110
return formatter.apply(rawUnix, file);
111111
}
112112
}
113+
114+
/** A dummy SENTINEL file. */
115+
static final File SENTINEL = new File("");
113116
}

lib/src/main/java/com/diffplug/spotless/generic/LicenseHeaderStep.java

+2-20
Original file line numberDiff line numberDiff line change
@@ -89,30 +89,12 @@ public LicenseHeaderStep withYearModeLazy(Supplier<YearMode> yearMode) {
8989
return new LicenseHeaderStep(headerLazy, delimiter, yearSeparator, yearMode);
9090
}
9191

92-
private static class SetFromGitFormatterFunc implements FormatterFunc {
93-
private final Runtime runtime;
94-
95-
private SetFromGitFormatterFunc(Runtime runtime) {
96-
this.runtime = runtime;
97-
}
98-
99-
@Override
100-
public String apply(String input, File source) throws Exception {
101-
return runtime.setLicenseHeaderYearsFromGitHistory(input, source);
102-
}
103-
104-
@Override
105-
public String apply(String input) throws Exception {
106-
throw new UnsupportedOperationException();
107-
}
108-
}
109-
11092
public FormatterStep build() {
11193
if (yearMode.get() == YearMode.SET_FROM_GIT) {
11294
return FormatterStep.createNeverUpToDateLazy(LicenseHeaderStep.name(), () -> {
11395
boolean updateYear = false; // doesn't matter
114-
Runtime step = new Runtime(headerLazy.get(), delimiter, yearSeparator, updateYear);
115-
return new SetFromGitFormatterFunc(step);
96+
Runtime runtime = new Runtime(headerLazy.get(), delimiter, yearSeparator, updateYear);
97+
return FormatterFunc.needsFile(runtime::setLicenseHeaderYearsFromGitHistory);
11698
});
11799
} else {
118100
return FormatterStep.createLazy(LicenseHeaderStep.name(), () -> {

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

+5-12
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ public static FormatterStep create(Map<String, String> devDependencies, Provisio
5858
State::createFormatterFunc);
5959
}
6060

61-
public static class State extends NpmFormatterStepStateBase implements Serializable {
61+
private static class State extends NpmFormatterStepStateBase implements Serializable {
6262

6363
private static final long serialVersionUID = -539537027004745812L;
6464
private final PrettierConfig prettierConfig;
@@ -100,7 +100,7 @@ private void endServer(PrettierRestService restService, ServerProcessInfo restSe
100100

101101
}
102102

103-
public static class PrettierFilePathPassingFormatterFunc implements FormatterFunc {
103+
private static class PrettierFilePathPassingFormatterFunc implements FormatterFunc.NeedsFile {
104104
private final String prettierConfigOptions;
105105
private final PrettierRestService restService;
106106

@@ -110,16 +110,9 @@ public PrettierFilePathPassingFormatterFunc(String prettierConfigOptions, Pretti
110110
}
111111

112112
@Override
113-
public String apply(String input) throws Exception {
114-
return apply(input, new File(""));
115-
}
116-
117-
@Override
118-
public String apply(String input, File source) throws Exception {
119-
requireNonNull(input, "input must not be null");
120-
requireNonNull(source, "source must not be null");
121-
final String prettierConfigOptionsWithFilepath = assertFilepathInConfigOptions(source);
122-
return restService.format(input, prettierConfigOptionsWithFilepath);
113+
public String applyWithFile(String unix, File file) throws Exception {
114+
final String prettierConfigOptionsWithFilepath = assertFilepathInConfigOptions(file);
115+
return restService.format(unix, prettierConfigOptionsWithFilepath);
123116
}
124117

125118
private String assertFilepathInConfigOptions(File file) {

testlib/src/main/java/com/diffplug/spotless/StepHarnessWithFile.java

+4-4
Original file line numberDiff line numberDiff line change
@@ -44,13 +44,13 @@ public static StepHarnessWithFile forStep(FormatterStep step) {
4444
},
4545
new FormatterFunc() {
4646
@Override
47-
public String apply(String input) throws Exception {
48-
return apply(input, new File(""));
47+
public String apply(String unix) throws Exception {
48+
return apply(unix, new File(""));
4949
}
5050

5151
@Override
52-
public String apply(String input, File source) throws Exception {
53-
return LineEnding.toUnix(step.format(input, source));
52+
public String apply(String unix, File file) throws Exception {
53+
return LineEnding.toUnix(step.format(unix, file));
5454
}
5555
}));
5656
}

0 commit comments

Comments
 (0)