diff --git a/CHANGES.md b/CHANGES.md index 16f70e542d..683d90c8a3 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -10,6 +10,8 @@ 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] +### Added +* `PipeStepPair.Builder` now has a method `.buildStepWhichAppliesSubSteps(Path rootPath, Collection steps)`, which returns a single `FormatterStep` that applies the given steps within the regex defined earlier in the builder. Used for formatting inception (implements [#412](https://github.com/diffplug/spotless/issues/412)). ## [2.6.2] - 2020-09-18 ### Fixed diff --git a/lib/src/main/java/com/diffplug/spotless/generic/PipeStepPair.java b/lib/src/main/java/com/diffplug/spotless/generic/PipeStepPair.java index 907bd1da0f..7c54359bca 100644 --- a/lib/src/main/java/com/diffplug/spotless/generic/PipeStepPair.java +++ b/lib/src/main/java/com/diffplug/spotless/generic/PipeStepPair.java @@ -15,13 +15,21 @@ */ 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.Collection; +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 edu.umd.cs.findbugs.annotations.SuppressFBWarnings; @@ -63,13 +71,21 @@ public Builder regex(String regex) { /** Defines the pipe via regex. Must have *exactly one* capturing group. */ public Builder regex(Pattern regex) { - this.regex = regex; + this.regex = Objects.requireNonNull(regex); return this; } + /** Returns a pair of steps which captures in the first part, then returns in the second. */ public PipeStepPair buildPair() { return new PipeStepPair(name, regex); } + + /** Returns a single step which will apply the given steps only within the blocks selected by the regex / openClose pair. */ + public FormatterStep buildStepWhichAppliesSubSteps(Path rootPath, Collection steps) { + return FormatterStep.createLazy(name, + () -> new StateApplyToBlock(regex, steps), + state -> FormatterFunc.Closeable.of(state.buildFormatter(rootPath), state::format)); + } } final FormatterStep in, out; @@ -89,6 +105,39 @@ public FormatterStep out() { return out; } + @SuppressFBWarnings("SE_TRANSIENT_FIELD_NOT_RESTORED") + static class StateApplyToBlock extends StateIn implements Serializable { + private static final long serialVersionUID = -844178006407733370L; + + final List steps; + final transient StringBuilder builder = new StringBuilder(); + + StateApplyToBlock(Pattern regex, Collection steps) { + super(regex); + this.steps = new ArrayList<>(steps); + } + + Formatter buildFormatter(Path rootDir) { + 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(rootDir) + .build(); + } + + private String format(Formatter formatter, String unix, File file) throws Exception { + groups.clear(); + 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 stateOutCompute(this, builder, unix); + } + } + @SuppressFBWarnings("SE_TRANSIENT_FIELD_NOT_RESTORED") static class StateIn implements Serializable { private static final long serialVersionUID = -844178006407733370L; @@ -96,12 +145,12 @@ static class StateIn implements Serializable { final Pattern regex; public StateIn(Pattern regex) { - this.regex = regex; + this.regex = Objects.requireNonNull(regex); } final transient ArrayList groups = new ArrayList<>(); - private String format(String unix) { + private String format(String unix) throws Exception { groups.clear(); Matcher matcher = regex.matcher(unix); while (matcher.find()) { @@ -118,40 +167,44 @@ static class StateOut implements Serializable { final StateIn in; StateOut(StateIn in) { - this.in = in; + this.in = Objects.requireNonNull(in); } final transient StringBuilder builder = new StringBuilder(); private String format(String unix) { - if (in.groups.isEmpty()) { - return unix; - } - builder.setLength(0); - Matcher matcher = in.regex.matcher(unix); - int lastEnd = 0; - int groupIdx = 0; - while (matcher.find()) { - builder.append(unix, lastEnd, matcher.start(1)); - builder.append(in.groups.get(groupIdx)); - lastEnd = matcher.end(1); - ++groupIdx; - } - if (groupIdx == in.groups.size()) { - builder.append(unix, lastEnd, unix.length()); - return builder.toString(); + return stateOutCompute(in, builder, unix); + } + } + + private static String stateOutCompute(StateIn in, StringBuilder builder, String unix) { + if (in.groups.isEmpty()) { + return unix; + } + builder.setLength(0); + Matcher matcher = in.regex.matcher(unix); + int lastEnd = 0; + int groupIdx = 0; + while (matcher.find()) { + builder.append(unix, lastEnd, matcher.start(1)); + builder.append(in.groups.get(groupIdx)); + lastEnd = matcher.end(1); + ++groupIdx; + } + if (groupIdx == in.groups.size()) { + builder.append(unix, lastEnd, unix.length()); + return builder.toString(); + } else { + // 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(in.regex.pattern()); + String pattern; + if (openClose.matches()) { + pattern = openClose.group(1) + " " + openClose.group(2); } else { - // 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(in.regex.pattern()); - String pattern; - if (openClose.matches()) { - pattern = openClose.group(1) + " " + openClose.group(2); - } else { - pattern = in.regex.pattern(); - } - throw new Error("An intermediate step removed a match of " + pattern); + pattern = in.regex.pattern(); } + throw new Error("An intermediate step removed a match of " + pattern); } } } diff --git a/plugin-gradle/CHANGES.md b/plugin-gradle/CHANGES.md index bc87feaede..4ff35501b8 100644 --- a/plugin-gradle/CHANGES.md +++ b/plugin-gradle/CHANGES.md @@ -3,6 +3,8 @@ We adhere to the [keepachangelog](https://keepachangelog.com/en/1.0.0/) format (starting after version `3.27.0`). ## [Unreleased] +### Added +* [`withinBlocks` allows you to apply rules only to specific sections of files](README.md#inception-languages-within-languages-within), for example formatting javascript within html, code examples within markdown, and things like that (implements [#412](https://github.com/diffplug/spotless/issues/412) - formatting inception). ## [5.5.2] - 2020-09-18 ### Fixed diff --git a/plugin-gradle/README.md b/plugin-gradle/README.md index 32c1519609..79f6a2719a 100644 --- a/plugin-gradle/README.md +++ b/plugin-gradle/README.md @@ -82,6 +82,7 @@ Spotless supports all of Gradle's built-in performance features (incremental bui - [Line endings and encodings (invisible stuff)](#line-endings-and-encodings-invisible-stuff) - [Custom steps](#custom-steps) - [Multiple (or custom) language-specific blocks](#multiple-or-custom-language-specific-blocks) + - [Inception (languages within languages within...)](#inception-languages-within-languages-within) - [Disabling warnings and error messages](#disabling-warnings-and-error-messages) - [How do I preview what `spotlessApply` will do?](#how-do-i-preview-what-spotlessapply-will-do) - [Example configurations (from real-world projects)](#example-configurations-from-real-world-projects) @@ -774,6 +775,25 @@ spotless { If you'd like to create a one-off Spotless task outside of the `check`/`apply` framework, see [`FormatExtension.createIndependentApplyTask`](https://javadoc.io/static/com.diffplug.spotless/spotless-plugin-gradle/5.5.2/com/diffplug/gradle/spotless/FormatExtension.html#createIndependentApplyTask-java.lang.String-). +## Inception (languages within languages within...) + +In very rare cases, you might want to format e.g. javascript which is written inside JSP templates, or maybe java within a markdown file, or something wacky like that. You can specify hunks within a file using either open/close tags or a regex with a single capturing group, and then specify rules within it, like so. See [javadoc TODO](https://javadoc.io/static/com.diffplug.spotless/spotless-plugin-gradle/5.5.1/com/diffplug/gradle/spotless/FormatExtension.html#target-java.lang.Object...-) for more details. + +```gradle +import com.diffplug.gradle.spotless.JavaExtension + +spotless { + format 'templates', { + target 'src/templates/**/*.foo.html' + prettier().config(['parser': 'html']) + withinBlocks 'javascript block', '', { + prettier().config(['parser': 'javascript']) + } + withinBlocksRegex 'single-line @(java-expresion)', '@\\((.*?)\\)', JavaExtension, { + googleJavaFormat() + } +``` + ## Disabling warnings and error messages diff --git a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/Antlr4Extension.java b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/Antlr4Extension.java index 3954fb40d4..02795af355 100644 --- a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/Antlr4Extension.java +++ b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/Antlr4Extension.java @@ -17,6 +17,8 @@ import java.util.Objects; +import javax.inject.Inject; + import com.diffplug.spotless.FormatterStep; import com.diffplug.spotless.antlr4.Antlr4Defaults; import com.diffplug.spotless.antlr4.Antlr4FormatterStep; @@ -24,6 +26,7 @@ public class Antlr4Extension extends FormatExtension implements HasBuiltinDelimiterForLicense { static final String NAME = "antlr4"; + @Inject public Antlr4Extension(SpotlessExtension rootExtension) { super(rootExtension); } diff --git a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/CppExtension.java b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/CppExtension.java index affc8e49b6..64dabc9ca5 100644 --- a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/CppExtension.java +++ b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/CppExtension.java @@ -17,6 +17,8 @@ import static com.diffplug.gradle.spotless.PluginGradlePreconditions.requireElementsNonNull; +import javax.inject.Inject; + import org.gradle.api.Project; import com.diffplug.spotless.cpp.CppDefaults; @@ -26,6 +28,7 @@ public class CppExtension extends FormatExtension implements HasBuiltinDelimiterForLicense { static final String NAME = "cpp"; + @Inject public CppExtension(SpotlessExtension spotless) { super(spotless); } diff --git a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/FormatExtension.java b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/FormatExtension.java index efa056d421..27f597be9a 100644 --- a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/FormatExtension.java +++ b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/FormatExtension.java @@ -31,6 +31,7 @@ import java.util.stream.Stream; import javax.annotation.Nullable; +import javax.inject.Inject; import org.gradle.api.Action; import org.gradle.api.GradleException; @@ -66,6 +67,7 @@ public class FormatExtension { final SpotlessExtension spotless; final List> lazyActions = new ArrayList<>(); + @Inject public FormatExtension(SpotlessExtension spotless) { this.spotless = Objects.requireNonNull(spotless); } @@ -624,6 +626,59 @@ public EclipseWtpConfig eclipseWtp(EclipseWtpFormatterStep type, String version) return new EclipseWtpConfig(type, version); } + /** + * ```gradle + * spotless { + * format 'examples', { + * target 'src/**\/*.md' + * withinBlocks 'javascript examples', '\n```javascript\n', '\n```\n`, { + * prettier().config(['parser': 'javascript']) + * } + * ... + * ``` + */ + public void withinBlocks(String name, String open, String close, Action configure) { + withinBlocks(name, open, close, FormatExtension.class, configure); + } + + /** + * Same as {@link #withinBlocks(String, String, String, Action)}, except you can specify + * any language-specific subclass of {@link FormatExtension} to get language-specific steps. + * + * ```gradle + * spotless { + * format 'examples', { + * target 'src/**\/*.md' + * withinBlocks 'java examples', '\n```java\n', '\n```\n`, com.diffplug.gradle.spotless.JavaExtension, { + * googleJavaFormat() + * } + * ... + * ``` + */ + public void withinBlocks(String name, String open, String close, Class clazz, Action configure) { + withinBlocksHelper(PipeStepPair.named(name).openClose(open, close), clazz, configure); + } + + /** Same as {@link #withinBlocks(String, String, String, Action)}, except instead of an open/close pair, you specify a regex with exactly one capturing group. */ + public void withinBlocksRegex(String name, String regex, Action configure) { + withinBlocksRegex(name, regex, FormatExtension.class, configure); + } + + /** Same as {@link #withinBlocksRegex(String, String, Action)}, except you can specify any language-specific subclass of {@link FormatExtension} to get language-specific steps. */ + public void withinBlocksRegex(String name, String regex, Class clazz, Action configure) { + withinBlocksHelper(PipeStepPair.named(name).regex(regex), clazz, configure); + } + + private void withinBlocksHelper(PipeStepPair.Builder builder, Class clazz, Action 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); + addStep(step); + } + /** * Given a regex with *exactly one capturing group*, disables formatting * inside that captured group. diff --git a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/FreshMarkExtension.java b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/FreshMarkExtension.java index 6ea492cc4d..b046beda7c 100644 --- a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/FreshMarkExtension.java +++ b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/FreshMarkExtension.java @@ -23,6 +23,8 @@ import java.util.Map; import java.util.Objects; +import javax.inject.Inject; + import org.gradle.api.Action; import com.diffplug.spotless.FormatterProperties; @@ -33,6 +35,7 @@ public class FreshMarkExtension extends FormatExtension { public final List>> propertyActions = new ArrayList<>(); + @Inject public FreshMarkExtension(SpotlessExtension spotless) { super(spotless); addStep(FreshMarkStep.create(() -> { diff --git a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/GroovyExtension.java b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/GroovyExtension.java index c9fdd87bf6..8ea7825f69 100644 --- a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/GroovyExtension.java +++ b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/GroovyExtension.java @@ -19,6 +19,8 @@ import java.util.Objects; +import javax.inject.Inject; + import org.gradle.api.GradleException; import org.gradle.api.Project; import org.gradle.api.file.FileCollection; @@ -36,6 +38,7 @@ public class GroovyExtension extends FormatExtension implements HasBuiltinDelimiterForLicense { static final String NAME = "groovy"; + @Inject public GroovyExtension(SpotlessExtension spotless) { super(spotless); } diff --git a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/GroovyGradleExtension.java b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/GroovyGradleExtension.java index fe98478c92..30cb75a2a3 100644 --- a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/GroovyGradleExtension.java +++ b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/GroovyGradleExtension.java @@ -17,6 +17,8 @@ import java.util.Objects; +import javax.inject.Inject; + import com.diffplug.spotless.extra.groovy.GrEclipseFormatterStep; import com.diffplug.spotless.java.ImportOrderStep; @@ -24,6 +26,7 @@ public class GroovyGradleExtension extends FormatExtension { private static final String GRADLE_FILE_EXTENSION = "*.gradle"; static final String NAME = "groovyGradle"; + @Inject public GroovyGradleExtension(SpotlessExtension spotless) { super(spotless); } diff --git a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/JavaExtension.java b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/JavaExtension.java index c1d1eba2dd..db9080f9c3 100644 --- a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/JavaExtension.java +++ b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/JavaExtension.java @@ -19,6 +19,8 @@ import java.util.Objects; +import javax.inject.Inject; + import org.gradle.api.GradleException; import org.gradle.api.Project; import org.gradle.api.file.FileCollection; @@ -36,6 +38,7 @@ public class JavaExtension extends FormatExtension implements HasBuiltinDelimiterForLicense { static final String NAME = "java"; + @Inject public JavaExtension(SpotlessExtension spotless) { super(spotless); } diff --git a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/KotlinExtension.java b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/KotlinExtension.java index 5831f7dae2..e58129f5ac 100644 --- a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/KotlinExtension.java +++ b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/KotlinExtension.java @@ -21,6 +21,8 @@ import java.util.Map; import java.util.Objects; +import javax.inject.Inject; + import org.gradle.api.GradleException; import org.gradle.api.file.FileCollection; import org.gradle.api.plugins.JavaPluginConvention; @@ -34,6 +36,7 @@ public class KotlinExtension extends FormatExtension implements HasBuiltinDelimiterForLicense { static final String NAME = "kotlin"; + @Inject public KotlinExtension(SpotlessExtension spotless) { super(spotless); } diff --git a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/KotlinGradleExtension.java b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/KotlinGradleExtension.java index ba9d33dc31..fdc2f25be8 100644 --- a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/KotlinGradleExtension.java +++ b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/KotlinGradleExtension.java @@ -19,6 +19,8 @@ import java.util.Map; import java.util.Objects; +import javax.inject.Inject; + import com.diffplug.common.collect.ImmutableSortedMap; import com.diffplug.spotless.FormatterStep; import com.diffplug.spotless.kotlin.KtLintStep; @@ -30,6 +32,7 @@ public class KotlinGradleExtension extends FormatExtension { static final String NAME = "kotlinGradle"; + @Inject public KotlinGradleExtension(SpotlessExtension spotless) { super(spotless); } diff --git a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/PythonExtension.java b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/PythonExtension.java index ca12acfcae..fa7c6a6802 100644 --- a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/PythonExtension.java +++ b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/PythonExtension.java @@ -15,12 +15,15 @@ */ package com.diffplug.gradle.spotless; +import javax.inject.Inject; + import com.diffplug.spotless.FormatterStep; import com.diffplug.spotless.python.BlackStep; public class PythonExtension extends FormatExtension { static final String NAME = "python"; + @Inject public PythonExtension(SpotlessExtension spotless) { super(spotless); } diff --git a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/ScalaExtension.java b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/ScalaExtension.java index 40d6782820..f6db64348d 100644 --- a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/ScalaExtension.java +++ b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/ScalaExtension.java @@ -19,6 +19,7 @@ import java.util.Objects; import javax.annotation.Nullable; +import javax.inject.Inject; import org.gradle.api.GradleException; import org.gradle.api.file.FileCollection; @@ -31,6 +32,7 @@ public class ScalaExtension extends FormatExtension { static final String NAME = "scala"; + @Inject public ScalaExtension(SpotlessExtension spotless) { super(spotless); } diff --git a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/SpotlessExtension.java b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/SpotlessExtension.java index 13ec0b4be4..2530ef2205 100644 --- a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/SpotlessExtension.java +++ b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/SpotlessExtension.java @@ -17,7 +17,6 @@ import static java.util.Objects.requireNonNull; -import java.lang.reflect.Constructor; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.util.LinkedHashMap; @@ -29,7 +28,6 @@ import org.gradle.api.GradleException; import org.gradle.api.Project; -import com.diffplug.common.base.Errors; import com.diffplug.spotless.LineEnding; public abstract class SpotlessExtension { @@ -220,17 +218,18 @@ protected final T maybeCreate(String name, Class return (T) existing; } } else { - try { - Constructor constructor = clazz.getConstructor(SpotlessExtension.class); - T formatExtension = constructor.newInstance(this); - formats.put(name, formatExtension); - createFormatTasks(name, formatExtension); - return formatExtension; - } catch (NoSuchMethodException e) { - throw new GradleException("Must have a constructor " + clazz.getSimpleName() + "(SpotlessExtension root)", e); - } catch (Exception e) { - throw Errors.asRuntime(e); - } + T formatExtension = instantiateFormatExtension(clazz); + formats.put(name, formatExtension); + createFormatTasks(name, formatExtension); + return formatExtension; + } + } + + T instantiateFormatExtension(Class clazz) { + try { + return project.getObjects().newInstance(clazz, this); + } catch (Exception e) { + throw new GradleException("Must have a constructor " + clazz.getSimpleName() + "(SpotlessExtension root), annotated with @javax.inject.Inject", e); } } diff --git a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/SqlExtension.java b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/SqlExtension.java index b065ec0dac..59d7ecf584 100644 --- a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/SqlExtension.java +++ b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/SqlExtension.java @@ -17,6 +17,8 @@ import static com.diffplug.gradle.spotless.PluginGradlePreconditions.requireElementsNonNull; +import javax.inject.Inject; + import org.gradle.api.Project; import com.diffplug.spotless.FormatterStep; @@ -25,6 +27,7 @@ public class SqlExtension extends FormatExtension { static final String NAME = "sql"; + @Inject public SqlExtension(SpotlessExtension spotless) { super(spotless); } diff --git a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/TypescriptExtension.java b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/TypescriptExtension.java index aaccfde812..08b2b463e6 100644 --- a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/TypescriptExtension.java +++ b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/TypescriptExtension.java @@ -23,6 +23,7 @@ import java.util.TreeMap; import javax.annotation.Nullable; +import javax.inject.Inject; import org.gradle.api.Project; @@ -36,6 +37,7 @@ public class TypescriptExtension extends FormatExtension { static final String NAME = "typescript"; + @Inject public TypescriptExtension(SpotlessExtension spotless) { super(spotless); } diff --git a/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/WithinBlockTest.java b/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/WithinBlockTest.java new file mode 100644 index 0000000000..70ca2a5daa --- /dev/null +++ b/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/WithinBlockTest.java @@ -0,0 +1,85 @@ +/* + * Copyright 2020 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.gradle.spotless; + +import java.io.IOException; + +import org.junit.Test; + +public class WithinBlockTest extends GradleIntegrationHarness { + @Test + public void genericFormatTest() throws IOException { + // make sure that the "typed-generic" closure works + // it does, and it doesn't need `it` + setFile("build.gradle").toLines( + "plugins { id 'com.diffplug.spotless' }", + "import com.diffplug.gradle.spotless.JavaExtension", + "spotless {", + " format 'customJava', JavaExtension, {", + " target '*.java'", + " googleJavaFormat('1.2')", + " }", + "}"); + setFile("test.java").toResource("java/googlejavaformat/JavaCodeUnformatted.test"); + gradleRunner().withArguments("spotlessApply").build(); + assertFile("test.java").sameAsResource("java/googlejavaformat/JavaCodeFormatted.test"); + } + + @Test + public void withinBlocksTourDeForce() throws IOException { + // but down here, we need `it`, or it will bind to the parent context, why? + setFile("build.gradle").toLines( + "plugins { id 'com.diffplug.spotless' }", + "import com.diffplug.gradle.spotless.JavaExtension", + "spotless {", + " format 'docs', {", + " target '*.md'", + " withinBlocks 'toLower', '\\n```lower\\n', '\\n```\\n', {", + " custom 'lowercase', { str -> str.toLowerCase() }", + " }", + " withinBlocks 'java only', '\\n```java\\n', '\\n```\\n', JavaExtension, {", + " googleJavaFormat()", + " }", + " }", + "}"); + setFile("test.md").toLines( + "Some stuff", + "```lower", + " A B C", + "D E F", + "```", + "And some java stuff", + "```java", + " public class SomeClass { public void method() {}}", + "```", + "And MORE!"); + gradleRunner().forwardOutput().withArguments("spotlessApply", "--stacktrace").build(); + assertFile("test.md").hasLines( + "Some stuff", + "```lower", + " a b c", + "d e f", + "```", + "And some java stuff", + "```java", + "public class SomeClass {", + " public void method() {}", + "}", + "", + "```", + "And MORE!"); + } +} diff --git a/testlib/src/test/java/com/diffplug/spotless/generic/PipeStepPairTest.java b/testlib/src/test/java/com/diffplug/spotless/generic/PipeStepPairTest.java index 1a32168e4d..9236dc947e 100644 --- a/testlib/src/test/java/com/diffplug/spotless/generic/PipeStepPairTest.java +++ b/testlib/src/test/java/com/diffplug/spotless/generic/PipeStepPairTest.java @@ -15,6 +15,8 @@ */ package com.diffplug.spotless.generic; +import java.nio.file.Paths; +import java.util.Arrays; import java.util.Locale; import org.junit.Test; @@ -93,4 +95,24 @@ public void broken() throws Exception { exception.hasMessage("An intermediate step removed a match of spotless:off spotless:on"); }); } + + @Test + public void andApply() throws Exception { + FormatterStep lowercase = FormatterStep.createNeverUpToDate("lowercase", str -> str.toLowerCase(Locale.ROOT)); + FormatterStep lowercaseSometimes = PipeStepPair.named("lowercaseSometimes").openClose("", "") + .buildStepWhichAppliesSubSteps(Paths.get(""), Arrays.asList(lowercase)); + StepHarness.forSteps(lowercaseSometimes).test( + StringPrinter.buildStringFromLines( + "A B C", + "", + "D E F", + "", + "G H I"), + StringPrinter.buildStringFromLines( + "A B C", + "", + "d e f", + "", + "G H I")); + } }