All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.diffplug.gradle.spotless.FormatExtension Maven / Gradle / Ivy

There is a newer version: 7.0.0.BETA4
Show newest version
/*
 * Copyright 2016-2023 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 static com.diffplug.gradle.spotless.PluginGradlePreconditions.requireElementsNonNull;
import static java.util.Objects.requireNonNull;

import java.io.File;
import java.io.Serializable;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.TreeMap;
import java.util.function.Consumer;

import javax.annotation.Nullable;
import javax.inject.Inject;

import org.gradle.api.Action;
import org.gradle.api.GradleException;
import org.gradle.api.Project;
import org.gradle.api.file.ConfigurableFileTree;
import org.gradle.api.file.FileCollection;
import org.gradle.api.plugins.BasePlugin;
import org.gradle.api.tasks.TaskProvider;

import com.diffplug.common.base.Preconditions;
import com.diffplug.spotless.FormatExceptionPolicyStrict;
import com.diffplug.spotless.FormatterFunc;
import com.diffplug.spotless.FormatterStep;
import com.diffplug.spotless.LazyForwardingEquality;
import com.diffplug.spotless.LineEnding;
import com.diffplug.spotless.Provisioner;
import com.diffplug.spotless.cpp.ClangFormatStep;
import com.diffplug.spotless.extra.EclipseBasedStepBuilder;
import com.diffplug.spotless.extra.wtp.EclipseWtpFormatterStep;
import com.diffplug.spotless.generic.EndWithNewlineStep;
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;
import com.diffplug.spotless.npm.NpmPathResolver;
import com.diffplug.spotless.npm.PrettierFormatterStep;

import groovy.lang.Closure;

/** Adds a {@code spotless{Name}Check} and {@code spotless{Name}Apply} task. */
public class FormatExtension {
	final SpotlessExtension spotless;
	final List> lazyActions = new ArrayList<>();

	@Inject
	public FormatExtension(SpotlessExtension spotless) {
		this.spotless = requireNonNull(spotless);
	}

	protected final Provisioner provisioner() {
		return spotless.getRegisterDependenciesTask().getTaskService().get().provisionerFor(spotless);
	}

	private String formatName() {
		for (Map.Entry entry : spotless.formats.entrySet()) {
			if (entry.getValue() == this) {
				return entry.getKey();
			}
		}
		throw new IllegalStateException("This format is not contained by any SpotlessExtension.");
	}

	LineEnding lineEndings;

	/** Returns the line endings to use (defaults to {@link SpotlessExtensionImpl#getLineEndings()}. */
	public LineEnding getLineEndings() {
		return lineEndings == null ? spotless.getLineEndings() : lineEndings;
	}

	/** Sets the line endings to use (defaults to {@link SpotlessExtensionImpl#getLineEndings()}. */
	public void setLineEndings(LineEnding lineEndings) {
		this.lineEndings = requireNonNull(lineEndings);
	}

	Charset encoding;

	/** Returns the encoding to use (defaults to {@link SpotlessExtensionImpl#getEncoding()}. */
	public Charset getEncoding() {
		return encoding == null ? spotless.getEncoding() : encoding;
	}

	/** Sets the encoding to use (defaults to {@link SpotlessExtensionImpl#getEncoding()}. */
	public void setEncoding(String name) {
		setEncoding(Charset.forName(requireNonNull(name)));
	}

	/** Sentinel to distinguish between "don't ratchet this format" and "use spotless parent format". */
	private static final String RATCHETFROM_NOT_SET_AT_FORMAT_LEVEL = " not set at format level ";

	private String ratchetFrom = RATCHETFROM_NOT_SET_AT_FORMAT_LEVEL;

	/** @see #setRatchetFrom(String) */
	public String getRatchetFrom() {
		return ratchetFrom == RATCHETFROM_NOT_SET_AT_FORMAT_LEVEL ? spotless.getRatchetFrom() : ratchetFrom;
	}

	/**
	 * Allows you to override the value from the parent {@link SpotlessExtension#setRatchetFrom(String)}
	 * for this specific format.
	 */
	public void setRatchetFrom(String ratchetFrom) {
		this.ratchetFrom = ratchetFrom;
	}

	/** @see #setRatchetFrom(String) */
	public void ratchetFrom(String ratchetFrom) {
		setRatchetFrom(ratchetFrom);
	}

	/** Sets the encoding to use (defaults to {@link SpotlessExtensionImpl#getEncoding()}. */
	public void setEncoding(Charset charset) {
		encoding = requireNonNull(charset);
	}

	final FormatExceptionPolicyStrict exceptionPolicy = new FormatExceptionPolicyStrict();

	/** Ignores errors in the given step. */
	public void ignoreErrorForStep(String stepName) {
		exceptionPolicy.excludeStep(requireNonNull(stepName));
	}

	/** Ignores errors for the given relative path. */
	public void ignoreErrorForPath(String relativePath) {
		exceptionPolicy.excludePath(requireNonNull(relativePath));
	}

	/** Sets encoding to use (defaults to {@link SpotlessExtensionImpl#getEncoding()}). */
	public void encoding(String charset) {
		setEncoding(charset);
	}

	/** The files to be formatted = (target - targetExclude). */
	protected FileCollection target, targetExclude;

	protected boolean isLicenseHeaderStep(FormatterStep formatterStep) {
		String formatterStepName = formatterStep.getName();

		if (formatterStepName.startsWith(LicenseHeaderStep.class.getName())) {
			return true;
		}

		return false;
	}

	/**
	 * Sets which files should be formatted.  Files to be formatted = (target - targetExclude).
	 *
	 * When this method is called multiple times, only the last call has any effect.
	 *
	 * FileCollections pass through raw.
	 * Strings are treated as the 'include' arg to fileTree, with project.rootDir as the dir.
	 * List are treated as the 'includes' arg to fileTree, with project.rootDir as the dir.
	 * Anything else gets passed to getProject().files().
	 *
	 * If you pass any strings that start with "**\/*", this method will automatically filter out
	 * "build", ".gradle", and ".git" folders.
	 */
	public void target(Object... targets) {
		this.target = parseTargetsIsExclude(targets, false);
	}

	/**
	 * Sets which files will be excluded from formatting.  Files to be formatted = (target - targetExclude).
	 *
	 * When this method is called multiple times, only the last call has any effect.
	 *
	 * FileCollections pass through raw.
	 * Strings are treated as the 'include' arg to fileTree, with project.rootDir as the dir.
	 * List are treated as the 'includes' arg to fileTree, with project.rootDir as the dir.
	 * Anything else gets passed to getProject().files().
	 */
	public void targetExclude(Object... targets) {
		this.targetExclude = parseTargetsIsExclude(targets, true);
	}

	private FileCollection parseTargetsIsExclude(Object[] targets, boolean isExclude) {
		requireElementsNonNull(targets);
		if (targets.length == 0) {
			return getProject().files();
		} else if (targets.length == 1) {
			return parseTargetIsExclude(targets[0], isExclude);
		} else {
			FileCollection union = getProject().files();
			for (Object target : targets) {
				union = union.plus(parseTargetIsExclude(target, isExclude));
			}
			return union;
		}
	}

	/**
	 * FileCollections pass through raw.
	 * Strings are treated as the 'include' arg to fileTree, with project.rootDir as the dir.
	 * List are treated as the 'includes' arg to fileTree, with project.rootDir as the dir.
	 * Anything else gets passed to getProject().files().
	 */
	protected final FileCollection parseTarget(Object target) {
		return parseTargetIsExclude(target, false);
	}

	private final FileCollection parseTargetIsExclude(Object target, boolean isExclude) {
		if (target instanceof Collection) {
			return parseTargetsIsExclude(((Collection) target).toArray(), isExclude);
		} else if (target instanceof FileCollection) {
			return (FileCollection) target;
		} else if (target instanceof String) {
			File dir = getProject().getProjectDir();
			ConfigurableFileTree matchedFiles = getProject().fileTree(dir);
			String targetString = (String) target;
			matchedFiles.include(targetString);

			// since people are likely to do '**/*.md', we want to make sure to exclude folders
			// they don't want to format which will slow down the operation greatly
			// but we only want to do that if they are *including* - if they are specifying
			// what they want to exclude, we shouldn't filter at all
			if (isExclude) {
				return matchedFiles;
			}
			if (targetString.startsWith("**/") || targetString.startsWith("**\\")) {
				List excludes = new ArrayList<>();
				// no git
				excludes.add(".git");
				// no .gradle
				if (getProject() == getProject().getRootProject()) {
					excludes.add(".gradle");
				}
				// no build folders (flatInclude means that subproject might not be subfolders, see https://github.com/diffplug/spotless/issues/121)
				relativizeIfSubdir(excludes, dir, getProject().getBuildDir());
				for (Project subproject : getProject().getSubprojects()) {
					relativizeIfSubdir(excludes, dir, subproject.getBuildDir());
				}
				matchedFiles.exclude(excludes);
			}
			return matchedFiles;
		} else {
			return getProject().files(target);
		}
	}

	private static void relativizeIfSubdir(List relativePaths, File root, File dest) {
		String relativized = relativize(root, dest);
		if (relativized != null) {
			relativePaths.add(relativized);
		}
	}

	/**
	 * Returns the relative path between root and dest,
	 * or null if dest is not a child of root.
	 */
	static @Nullable String relativize(File root, File dest) {
		String rootPath = root.getAbsolutePath();
		String destPath = dest.getAbsolutePath();
		if (!destPath.startsWith(rootPath)) {
			return null;
		} else {
			return destPath.substring(rootPath.length());
		}
	}

	/** The steps that need to be added. */
	protected final List steps = new ArrayList<>();

	/** Adds a new step. */
	public void addStep(FormatterStep newStep) {
		requireNonNull(newStep);
		int existingIdx = getExistingStepIdx(newStep.getName());
		if (existingIdx != -1) {
			throw new GradleException("Multiple steps with name '" + newStep.getName() + "' for spotless format '" + formatName() + "'");
		}
		steps.add(newStep);
	}

	/** Returns the index of the existing step with the given name, or -1 if no such step exists. */
	protected int getExistingStepIdx(String stepName) {
		for (int i = 0; i < steps.size(); ++i) {
			if (steps.get(i).getName().equals(stepName)) {
				return i;
			}
		}
		return -1;
	}

	/** Replaces the given step. */
	protected void replaceStep(FormatterStep replacementStep) {
		int existingIdx = getExistingStepIdx(replacementStep.getName());
		if (existingIdx == -1) {
			throw new GradleException("Cannot replace step '" + replacementStep.getName() + "' for spotless format '" + formatName() + "' because it hasn't been added yet.");
		}
		steps.set(existingIdx, replacementStep);
	}

	/** Clears all of the existing steps. */
	public void clearSteps() {
		steps.clear();
	}

	/**
	 * An optional performance optimization if you are using any of the {@code custom}
	 * methods.  If you aren't explicitly calling {@code custom}, then this method
	 * has no effect.
	 *
	 * Spotless tracks what files have changed from run to run, so that it can run faster
	 * by only checking files which have changed, or whose formatting steps have changed.
	 * If you use the {@code custom} methods, then gradle can never mark
	 * your files as {@code up-to-date}, because it can't know if perhaps the behavior of your
	 * custom function has changed.
	 *
	 * If you set {@code bumpThisNumberIfACustomStepChanges(  )}, then spotless will
	 * assume that the custom rules have not changed if the number has not changed.  If a
	 * custom rule does change, then you must bump the number so that spotless will know
	 * that it must recheck the files it has already checked.
	 */
	public void bumpThisNumberIfACustomStepChanges(int number) {
		globalState = number;
	}

	private Serializable globalState = new NeverUpToDateBetweenRuns();

	static class NeverUpToDateBetweenRuns extends LazyForwardingEquality {
		private static final long serialVersionUID = 1L;
		private static final Random RANDOM = new Random();

		@Override
		protected Integer calculateState() throws Exception {
			return RANDOM.nextInt();
		}
	}

	/** Adds a custom step. Receives a string with unix-newlines, must return a string with unix newlines. */
	public void custom(String name, Closure formatter) {
		requireNonNull(formatter, "formatter");
		custom(name, formatter::call);
	}

	/** Adds a custom step. Receives a string with unix-newlines, must return a string with unix newlines. */
	public void custom(String name, FormatterFunc formatter) {
		requireNonNull(formatter, "formatter");
		addStep(FormatterStep.createLazy(name, () -> globalState, unusedState -> formatter));
	}

	/** Highly efficient find-replace char sequence. */
	public void replace(String name, CharSequence original, CharSequence after) {
		addStep(ReplaceStep.create(name, original, after));
	}

	/** Highly efficient find-replace regex. */
	public void replaceRegex(String name, String regex, String replacement) {
		addStep(ReplaceRegexStep.create(name, regex, replacement));
	}

	/** Removes trailing whitespace. */
	public void trimTrailingWhitespace() {
		addStep(TrimTrailingWhitespaceStep.create());
	}

	/** Ensures that files end with a single newline. */
	public void endWithNewline() {
		addStep(EndWithNewlineStep.create());
	}

	/** Ensures that the files are indented using spaces. */
	public void indentWithSpaces(int numSpacesPerTab) {
		addStep(IndentStep.Type.SPACE.create(numSpacesPerTab));
	}

	/** Ensures that the files are indented using spaces. */
	public void indentWithSpaces() {
		addStep(IndentStep.Type.SPACE.create());
	}

	/** Ensures that the files are indented using tabs. */
	public void indentWithTabs(int tabToSpaces) {
		addStep(IndentStep.Type.TAB.create(tabToSpaces));
	}

	/** Ensures that the files are indented using tabs. */
	public void indentWithTabs() {
		addStep(IndentStep.Type.TAB.create());
	}

	/** Ensures formatting of files via native binary. */
	public void nativeCmd(String name, String pathToExe, List arguments) {
		addStep(NativeCmdStep.create(name, new File(pathToExe), arguments));
	}

	/**
	 * Created by {@link FormatExtension#licenseHeader(String, String)} or {@link FormatExtension#licenseHeaderFile(Object, String)}.
	 * For most language-specific formats (e.g. java, scala, etc.) you can omit the second {@code delimiter} argument, because it is supplied
	 * automatically ({@link HasBuiltinDelimiterForLicense}).
	 */
	public class LicenseHeaderConfig {
		LicenseHeaderStep builder;
		Boolean updateYearWithLatest = null;

		public LicenseHeaderConfig named(String name) {
			String existingStepName = builder.getName();
			builder = builder.withName(name);
			int existingStepIdx = getExistingStepIdx(existingStepName);
			if (existingStepIdx != -1) {
				steps.set(existingStepIdx, createStep());
			} else {
				addStep(createStep());
			}
			return this;
		}

		public LicenseHeaderConfig onlyIfContentMatches(String contentPattern) {
			builder = builder.withContentPattern(contentPattern);
			replaceStep(createStep());
			return this;
		}

		public LicenseHeaderConfig(LicenseHeaderStep builder) {
			this.builder = builder;
		}

		/**
		 * @param delimiter
		 *            Spotless will look for a line that starts with this regular expression pattern to know what the "top" is.
		 */
		public LicenseHeaderConfig delimiter(String delimiter) {
			builder = builder.withDelimiter(delimiter);
			replaceStep(createStep());
			return this;
		}

		/**
		 * @param yearSeparator
		 *           The characters used to separate the first and last years in multi years patterns.
		 */
		public LicenseHeaderConfig yearSeparator(String yearSeparator) {
			builder = builder.withYearSeparator(yearSeparator);
			replaceStep(createStep());
			return this;
		}

		public LicenseHeaderConfig skipLinesMatching(String skipLinesMatching) {
			builder = builder.withSkipLinesMatching(skipLinesMatching);
			replaceStep(createStep());
			return this;
		}

		/**
		 * @param updateYearWithLatest
		 *           Will turn {@code 2004} into {@code 2004-2020}, and {@code 2004-2019} into {@code 2004-2020}
		 *           Default value is false, unless {@link SpotlessExtensionImpl#ratchetFrom(String)} is used, in which case default value is true.
		 */
		public LicenseHeaderConfig updateYearWithLatest(boolean updateYearWithLatest) {
			this.updateYearWithLatest = updateYearWithLatest;
			replaceStep(createStep());
			return this;
		}

		FormatterStep createStep() {
			return builder.withYearModeLazy(() -> {
				if ("true".equals(spotless.project.findProperty(LicenseHeaderStep.FLAG_SET_LICENSE_HEADER_YEARS_FROM_GIT_HISTORY()))) {
					return YearMode.SET_FROM_GIT;
				} else {
					boolean updateYear = updateYearWithLatest == null ? getRatchetFrom() != null : updateYearWithLatest;
					return updateYear ? YearMode.UPDATE_TO_TODAY : YearMode.PRESERVE;
				}
			}).build();
		}
	}

	/**
	 * @param licenseHeader
	 *            Content that should be at the top of every file.
	 * @param delimiter
	 *            Spotless will look for a line that starts with this regular expression pattern to know what the "top" is.
	 */
	public LicenseHeaderConfig licenseHeader(String licenseHeader, String delimiter) {
		LicenseHeaderConfig config = new LicenseHeaderConfig(LicenseHeaderStep.headerDelimiter(licenseHeader, delimiter));
		addStep(config.createStep());
		return config;
	}

	/**
	 * @param licenseHeaderFile
	 *            Content that should be at the top of every file.
	 * @param delimiter
	 *            Spotless will look for a line that starts with this regular expression pattern to know what the "top" is.
	 */
	public LicenseHeaderConfig licenseHeaderFile(Object licenseHeaderFile, String delimiter) {
		LicenseHeaderConfig config = new LicenseHeaderConfig(LicenseHeaderStep.headerDelimiter(() -> {
			File file = getProject().file(licenseHeaderFile);
			byte[] data = Files.readAllBytes(file.toPath());
			return new String(data, getEncoding());
		}, delimiter));
		addStep(config.createStep());
		return config;
	}

	public abstract static class NpmStepConfig> {
		@Nullable
		protected Object npmFile;

		@Nullable
		protected Object nodeFile;

		@Nullable
		protected Object npmrcFile;

		protected Project project;

		private Consumer replaceStep;

		public NpmStepConfig(Project project, Consumer replaceStep) {
			this.project = requireNonNull(project);
			this.replaceStep = requireNonNull(replaceStep);
		}

		@SuppressWarnings("unchecked")
		public T npmExecutable(final Object npmFile) {
			this.npmFile = npmFile;
			replaceStep();
			return (T) this;
		}

		@SuppressWarnings("unchecked")
		public T nodeExecutable(final Object nodeFile) {
			this.nodeFile = nodeFile;
			replaceStep();
			return (T) this;
		}

		public T npmrc(final Object npmrcFile) {
			this.npmrcFile = npmrcFile;
			replaceStep();
			return (T) this;
		}

		File npmFileOrNull() {
			return fileOrNull(npmFile);
		}

		File nodeFileOrNull() {
			return fileOrNull(nodeFile);
		}

		File npmrcFileOrNull() {
			return fileOrNull(npmrcFile);
		}

		private File fileOrNull(Object npmFile) {
			return npmFile != null ? project.file(npmFile) : null;
		}

		protected void replaceStep() {
			replaceStep.accept(createStep());
		}

		abstract protected FormatterStep createStep();

	}

	public class PrettierConfig extends NpmStepConfig {

		@Nullable
		Object prettierConfigFile;

		@Nullable
		Map prettierConfig;

		final Map devDependencies;

		PrettierConfig(Map devDependencies) {
			super(getProject(), FormatExtension.this::replaceStep);
			this.devDependencies = requireNonNull(devDependencies);
		}

		public PrettierConfig configFile(final Object prettierConfigFile) {
			this.prettierConfigFile = prettierConfigFile;
			replaceStep();
			return this;
		}

		public PrettierConfig config(final Map prettierConfig) {
			this.prettierConfig = new TreeMap<>(prettierConfig);
			replaceStep();
			return this;
		}

		@Override
		protected FormatterStep createStep() {
			final Project project = getProject();
			return PrettierFormatterStep.create(
					devDependencies,
					provisioner(),
					project.getProjectDir(),
					project.getBuildDir(),
					new NpmPathResolver(npmFileOrNull(), nodeFileOrNull(), npmrcFileOrNull(), Arrays.asList(project.getProjectDir(), project.getRootDir())),
					new com.diffplug.spotless.npm.PrettierConfig(
							this.prettierConfigFile != null ? project.file(this.prettierConfigFile) : null,
							this.prettierConfig));
		}
	}

	/** Uses the default version of prettier. */
	public PrettierConfig prettier() {
		return prettier(PrettierFormatterStep.defaultDevDependencies());
	}

	/** Uses the specified version of prettier. */
	public PrettierConfig prettier(String version) {
		return prettier(PrettierFormatterStep.defaultDevDependenciesWithPrettier(version));
	}

	/** Uses exactly the npm packages specified in the map. */
	public PrettierConfig prettier(Map devDependencies) {
		PrettierConfig prettierConfig = new PrettierConfig(devDependencies);
		addStep(prettierConfig.createStep());
		return prettierConfig;
	}

	/** Uses the default version of clang-format. */
	public ClangFormatConfig clangFormat() {
		return clangFormat(ClangFormatStep.defaultVersion());
	}

	/** Uses the specified version of clang-format. */
	public ClangFormatConfig clangFormat(String version) {
		return new ClangFormatConfig(version);
	}

	public class ClangFormatConfig {
		ClangFormatStep stepCfg;

		ClangFormatConfig(String version) {
			this.stepCfg = ClangFormatStep.withVersion(version);
			addStep(createStep());
		}

		/** Any of: LLVM, Google, Chromium, Mozilla, WebKit. */
		public ClangFormatConfig style(String style) {
			stepCfg = stepCfg.withStyle(style);
			replaceStep(createStep());
			return this;
		}

		public ClangFormatConfig pathToExe(String pathToBlack) {
			stepCfg = stepCfg.withPathToExe(pathToBlack);
			replaceStep(createStep());
			return this;
		}

		private FormatterStep createStep() {
			return stepCfg.create();
		}
	}

	public class EclipseWtpConfig {
		private final EclipseBasedStepBuilder builder;

		EclipseWtpConfig(EclipseWtpFormatterStep type, String version) {
			builder = type.createBuilder(provisioner());
			builder.setVersion(version);
			addStep(builder.build());
		}

		public void configFile(Object... configFiles) {
			requireElementsNonNull(configFiles);
			Project project = getProject();
			builder.setPreferences(project.files(configFiles).getFiles());
			replaceStep(builder.build());
		}
	}

	public EclipseWtpConfig eclipseWtp(EclipseWtpFormatterStep type) {
		return eclipseWtp(type, EclipseWtpFormatterStep.defaultVersion());
	}

	public EclipseWtpConfig eclipseWtp(EclipseWtpFormatterStep type, String version) {
		return new EclipseWtpConfig(type, version);
	}

	/**
	 * 
	 * spotless {
	 *   format 'examples', {
	 *     target '*.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. * *
	 * spotless {
	 *   format 'examples', {
	 *     target '*.md'
	 *     withinBlocks 'java examples', '\n```java\n', '\n```\n', com.diffplug.gradle.spotless.JavaExtension, {
	 *       googleJavaFormat()
	 *       formatAnnotations()
	 *     }
	 *     ...
	 * 
*/ 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. */ public void toggleOffOnRegex(String regex) { this.togglePair = PipeStepPair.named(PipeStepPair.defaultToggleName()).regex(regex).buildPair(); } /** Disables formatting between the given tags. */ public void toggleOffOn(String off, String on) { this.togglePair = PipeStepPair.named(PipeStepPair.defaultToggleName()).openClose(off, on).buildPair(); } /** Disables formatting between {@code spotless:off} and {@code spotless:on}. */ public void toggleOffOn() { toggleOffOn(PipeStepPair.defaultToggleOff(), PipeStepPair.defaultToggleOn()); } /** Undoes all previous calls to {@link #toggleOffOn()} and {@link #toggleOffOn(String, String)}. */ public void toggleOffOnDisable() { this.togglePair = null; } private @Nullable PipeStepPair togglePair; /** Sets up a format task according to the values in this extension. */ protected void setupTask(SpotlessTask task) { task.setEncoding(getEncoding().name()); task.setExceptionPolicy(exceptionPolicy); FileCollection totalTarget = targetExclude == null ? target : target.minus(targetExclude); task.setTarget(totalTarget); List steps; if (togglePair != null) { steps = new ArrayList<>(this.steps.size() + 2); steps.add(togglePair.in()); steps.addAll(this.steps); steps.add(togglePair.out()); } else { steps = this.steps; } task.setSteps(steps); task.setLineEndingsPolicy(getLineEndings().createPolicy(getProject().getProjectDir(), () -> totalTarget)); spotless.getRegisterDependenciesTask().hookSubprojectTask(task); task.setupRatchet(getRatchetFrom() != null ? getRatchetFrom() : ""); } /** Returns the project that this extension is attached to. */ protected Project getProject() { return spotless.project; } /** Eager version of {@link #createIndependentApplyTaskLazy(String)} */ public SpotlessApply createIndependentApplyTask(String taskName) { return createIndependentApplyTaskLazy(taskName).get(); } /** * Creates an independent {@link SpotlessApply} for (very) unusual circumstances. * * Most users will not want this method. In the rare case that you want to create * a {@code SpotlessApply} which is independent of the normal Spotless machinery, this will * let you do that. * * The returned task will not be hooked up to the global {@code spotlessApply}, and there will be no corresponding {@code check} task. * * The task name must not end with `Apply`. * * NOTE: does not respect the rarely-used {@code spotlessFiles} property. */ public TaskProvider createIndependentApplyTaskLazy(String taskName) { Preconditions.checkArgument(!taskName.endsWith(SpotlessExtension.APPLY), "Task name must not end with " + SpotlessExtension.APPLY); TaskProvider spotlessTask = spotless.project.getTasks().register(taskName + SpotlessTaskService.INDEPENDENT_HELPER, SpotlessTaskImpl.class, task -> { task.init(spotless.getRegisterDependenciesTask().getTaskService()); setupTask(task); // clean removes the SpotlessCache, so we have to run after clean task.mustRunAfter(BasePlugin.CLEAN_TASK_NAME); }); // create the apply task TaskProvider applyTask = spotless.project.getTasks().register(taskName, SpotlessApply.class, task -> { task.dependsOn(spotlessTask); task.init(spotlessTask.get()); }); return applyTask; } protected GradleException noDefaultTargetException() { return new GradleException("Spotless failure, no target set! You must set a target for " + formatName()); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy