diff --git a/CHANGES.md b/CHANGES.md index 641d7207c..7f0283257 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -7,6 +7,8 @@ You might be looking for: ### Version 1.3.0-SNAPSHOT - TBD (javadoc [lib](https://diffplug.github.io/spotless/javadoc/spotless-lib/snapshot/) [lib-extra](https://diffplug.github.io/spotless/javadoc/spotless-lib-extra/snapshot/), [snapshot repo](https://oss.sonatype.org/content/repositories/snapshots/com/diffplug/spotless/)) +* Added support for Groovy via [greclipse](https://github.com/groovy/groovy-eclipse). + ### Version 1.2.0 - April 3rd 2017 (javadoc [lib](https://diffplug.github.io/spotless/javadoc/spotless-lib/1.2.0/) [lib-extra](https://diffplug.github.io/spotless/javadoc/spotless-lib-extra/1.2.0/), artifact [lib]([jcenter](https://bintray.com/diffplug/opensource/spotless-lib), [lib-extra]([jcenter](https://bintray.com/diffplug/opensource/spotless-lib-extra))) * Deprecated `FileSignature.from` in favor of `FileSignature.signAsSet` and the new `FileSignature.signAsList`. diff --git a/README.md b/README.md index cf0cad947..f694e42fd 100644 --- a/README.md +++ b/README.md @@ -38,6 +38,7 @@ lib('generic.LicenseHeaderStep') +'{{yes}} | {{no}} lib('generic.ReplaceRegexStep') +'{{yes}} | {{no}} | {{no}} |', lib('generic.ReplaceStep') +'{{yes}} | {{no}} | {{no}} |', lib('generic.TrimTrailingWhitespaceStep') +'{{yes}} | {{no}} | {{no}} |', +extra('groovy.GrEclipseFormatterStep') +'{{yes}} | {{no}} | {{no}} |', lib('java.GoogleJavaFormatStep') +'{{yes}} | {{no}} | {{no}} |', lib('java.ImportOrderStep') +'{{yes}} | {{no}} | {{no}} |', extra('java.EclipseFormatterStep') +'{{yes}} | {{no}} | {{no}} |', @@ -58,6 +59,7 @@ lib('scala.ScalaFmtStep') +'{{yes}} | {{no}} | [`generic.ReplaceRegexStep`](lib/src/main/java/com/diffplug/spotless/generic/ReplaceRegexStep.java) | :+1: | :white_large_square: | :white_large_square: | | [`generic.ReplaceStep`](lib/src/main/java/com/diffplug/spotless/generic/ReplaceStep.java) | :+1: | :white_large_square: | :white_large_square: | | [`generic.TrimTrailingWhitespaceStep`](lib/src/main/java/com/diffplug/spotless/generic/TrimTrailingWhitespaceStep.java) | :+1: | :white_large_square: | :white_large_square: | +| [`groovy.GrEclipseFormatterStep`](lib-extra/src/main/java/com/diffplug/spotless/extra/groovy/GrEclipseFormatterStep.java) | :+1: | :white_large_square: | :white_large_square: | | [`java.GoogleJavaFormatStep`](lib/src/main/java/com/diffplug/spotless/java/GoogleJavaFormatStep.java) | :+1: | :white_large_square: | :white_large_square: | | [`java.ImportOrderStep`](lib/src/main/java/com/diffplug/spotless/java/ImportOrderStep.java) | :+1: | :white_large_square: | :white_large_square: | | [`java.EclipseFormatterStep`](lib-extra/src/main/java/com/diffplug/spotless/extra/java/EclipseFormatterStep.java) | :+1: | :white_large_square: | :white_large_square: | @@ -75,6 +77,7 @@ lib('scala.ScalaFmtStep') +'{{yes}} | {{no}} + implementing up-to-date checking [#31](https://github.com/diffplug/spotless/issues/31) + breaking spotless into libraries [#56](https://github.com/diffplug/spotless/issues/56) + lots of other things, but especially the diff support in `spotlessCheck` +* Huge thanks to [Frank Vennemeyer](https://github.com/fvgh) for [Groovy support via greclipse](https://github.com/diffplug/spotless/issues/13). * Huge thanks to [Stefan Oehme](https://github.com/oehme) for tons of help on the internal mechanics of Gradle. * Formatting by Eclipse + Special thanks to [Mateusz Matela](https://waynebeaton.wordpress.com/2015/03/15/great-fixes-for-mars-winners-part-i/) for huge improvements to the eclipse code formatter! diff --git a/_ext/greclipse/gradle.properties b/_ext/greclipse/gradle.properties index 3244358d4..dbffb524c 100644 --- a/_ext/greclipse/gradle.properties +++ b/_ext/greclipse/gradle.properties @@ -1,6 +1,6 @@ # Mayor/Minor versions are in line with the Groovy version values. # Patch version is an incremental counter for the Spotless plugin. -grec_version=2.3.0-SNAPSHOT +grec_version=2.3.0 grec_artifactId=spotless-ext-greclipse grec_description=Groovy Eclipse's formatter bundled for Spotless diff --git a/build.gradle b/build.gradle index b4c6ac9a6..4a36eaea7 100644 --- a/build.gradle +++ b/build.gradle @@ -1,5 +1,5 @@ buildscript { - repositories { maven { url 'https://plugins.gradle.org/m2/' } } + repositories { maven { url 'https://plugins.gradle.org/m2/' }} dependencies { classpath "com.diffplug.gradle:goomph:${VER_GOOMPH}" classpath "com.jfrog.bintray.gradle:gradle-bintray-plugin:${VER_BINTRAY}" diff --git a/gradle/java-publish.gradle b/gradle/java-publish.gradle index 5e8da50ce..affc2da74 100644 --- a/gradle/java-publish.gradle +++ b/gradle/java-publish.gradle @@ -20,7 +20,7 @@ javadoc { // use markdown in javadoc def makeLink = { url, text -> "${text}" } def javadocInfo = '

' + makeLink("https://github.com/${org}/${name}", "${group}:${project.ext.artifactId}:${ext.version}") + -' by ' + makeLink('http://www.diffplug.com', 'DiffPlug') + '

' + ' by ' + makeLink('http://www.diffplug.com', 'DiffPlug') + '' String version_str = ext.version.endsWith('-SNAPSHOT') ? 'snapshot' : ext.version apply plugin: 'ch.raffael.pegdown-doclet' @@ -110,13 +110,14 @@ model { } if (project.ext.isSnapshot) { // upload snapshots to oss.sonatype.org - repositories { maven { - url = 'https://oss.sonatype.org/content/repositories/snapshots' - credentials { - username = cred('nexus_user') - password = cred('nexus_pass') - } - } } + repositories { + maven { + url = 'https://oss.sonatype.org/content/repositories/snapshots' + credentials { + username = cred('nexus_user') + password = cred('nexus_pass') + } + }} } } } @@ -126,7 +127,9 @@ if (!ext.isSnapshot) { bintray { user = cred('bintray_user') key = cred('bintray_pass') - publications = ['pluginMaven'] + publications = [ + 'pluginMaven' + ] publish = true pkg { repo = 'opensource' @@ -143,5 +146,10 @@ if (!ext.isSnapshot) { } publish.dependsOn(bintrayUpload) - bintrayUpload.dependsOn(['generatePomFileForPluginMavenPublication', jar, sourcesJar, javadocJar]) + bintrayUpload.dependsOn([ + 'generatePomFileForPluginMavenPublication', + jar, + sourcesJar, + javadocJar + ]) } diff --git a/gradle/java-setup.gradle b/gradle/java-setup.gradle index 305c4ec33..1de634c08 100644 --- a/gradle/java-setup.gradle +++ b/gradle/java-setup.gradle @@ -1,18 +1,14 @@ ////////// // JAVA // ////////// -repositories { - jcenter() -} +repositories { jcenter() } // setup java apply plugin: 'java' sourceCompatibility = VER_JAVA targetCompatibility = VER_JAVA -tasks.withType(JavaCompile) { - options.encoding = 'UTF-8' -} +tasks.withType(JavaCompile) { options.encoding = 'UTF-8' } ///////////// // ECLIPSE // @@ -38,7 +34,10 @@ eclipseResourceFilters { apply plugin: 'findbugs' findbugs { toolVersion = VER_FINDBUGS - sourceSets = [sourceSets.main] // don't check the test code + sourceSets = [ + // don't check the test code + sourceSets.main + ] ignoreFailures = false // bug free or it doesn't ship! reportsDir = file('build/findbugs') effort = 'max' // min|default|max diff --git a/lib-extra/build.gradle b/lib-extra/build.gradle index ac0328a50..38bbaa2ab 100644 --- a/lib-extra/build.gradle +++ b/lib-extra/build.gradle @@ -22,6 +22,5 @@ dependencies { } // we'll hold the core lib to a high standard -findbugs { - reportLevel = 'low' // low|medium|high (low = sensitive to even minor mistakes) -} +findbugs { reportLevel = 'low' } // low|medium|high (low = sensitive to even minor mistakes) + diff --git a/lib-extra/src/main/java/com/diffplug/spotless/extra/groovy/GrEclipseFormatterStep.java b/lib-extra/src/main/java/com/diffplug/spotless/extra/groovy/GrEclipseFormatterStep.java new file mode 100644 index 000000000..aa6d82ddb --- /dev/null +++ b/lib-extra/src/main/java/com/diffplug/spotless/extra/groovy/GrEclipseFormatterStep.java @@ -0,0 +1,91 @@ +/* + * Copyright 2016 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.extra.groovy; + +import java.io.File; +import java.io.Serializable; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.Objects; +import java.util.Properties; + +import com.diffplug.spotless.FileSignature; +import com.diffplug.spotless.FormatterFunc; +import com.diffplug.spotless.FormatterProperties; +import com.diffplug.spotless.FormatterStep; +import com.diffplug.spotless.JarState; +import com.diffplug.spotless.Provisioner; + +/** Formatter step which calls out to the Groovy-Eclipse formatter. */ +public class GrEclipseFormatterStep { + public static final String defaultVersion() { + return DEFAULT_VERSION; + } + + private static final String NAME = "groovy-eclipse formatter"; + private static final String FORMATTER_CLASS = "com.diffplug.gradle.spotless.groovy.eclipse.GrEclipseFormatterStepImpl"; + private static final String DEFAULT_VERSION = "2.3.0"; + private static final String MAVEN_COORDINATE = "com.diffplug.spotless:spotless-ext-greclipse:"; + private static final String FORMATTER_METHOD = "format"; + + /** Creates a formatter step using the default version for the given settings file. */ + public static FormatterStep create(Iterable settingsFiles, Provisioner provisioner) { + return create(defaultVersion(), settingsFiles, provisioner); + } + + /** Creates a formatter step for the given version and settings file. */ + public static FormatterStep create(String version, Iterable settingsFiles, Provisioner provisioner) { + return FormatterStep.createLazy( + NAME, + () -> new State(JarState.from(MAVEN_COORDINATE + version, provisioner), settingsFiles), + State::createFormat); + } + + private static class State implements Serializable { + private static final long serialVersionUID = 1L; + + /** The jar that contains the eclipse formatter. */ + final JarState jarState; + /** The signature of the settings file. */ + final FileSignature settings; + + State(JarState jar, final Iterable settingsFiles) throws Exception { + this.jarState = Objects.requireNonNull(jar); + this.settings = FileSignature.signAsList(settingsFiles); + } + + FormatterFunc createFormat() throws Exception { + FormatterProperties preferences = FormatterProperties.from(settings.files()); + + ClassLoader classLoader = jarState.getClassLoader(); + + // instantiate the formatter and get its format method + Class formatterClazz = classLoader.loadClass(FORMATTER_CLASS); + Object formatter = formatterClazz.getConstructor(Properties.class).newInstance(preferences.getProperties()); + Method method = formatterClazz.getMethod(FORMATTER_METHOD, String.class); + return input -> { + try { + return (String) method.invoke(formatter, input); + } catch (InvocationTargetException exceptionWrapper) { + Throwable throwable = exceptionWrapper.getTargetException(); + Exception exception = (throwable instanceof Exception) ? (Exception) throwable : null; + throw (null == exception) ? exceptionWrapper : exception; + } + }; + } + } + +} diff --git a/lib-extra/src/main/java/com/diffplug/spotless/extra/groovy/package-info.java b/lib-extra/src/main/java/com/diffplug/spotless/extra/groovy/package-info.java new file mode 100644 index 000000000..6712109bb --- /dev/null +++ b/lib-extra/src/main/java/com/diffplug/spotless/extra/groovy/package-info.java @@ -0,0 +1,7 @@ +@ParametersAreNonnullByDefault +@ReturnValuesAreNonnullByDefault +package com.diffplug.spotless.extra.groovy; + +import javax.annotation.ParametersAreNonnullByDefault; + +import com.diffplug.spotless.annotations.ReturnValuesAreNonnullByDefault; diff --git a/lib-extra/src/test/java/com/diffplug/spotless/extra/groovy/GrEclipseFormatterStepTest.java b/lib-extra/src/test/java/com/diffplug/spotless/extra/groovy/GrEclipseFormatterStepTest.java new file mode 100644 index 000000000..4ba349f8d --- /dev/null +++ b/lib-extra/src/test/java/com/diffplug/spotless/extra/groovy/GrEclipseFormatterStepTest.java @@ -0,0 +1,88 @@ +/* + * Copyright 2016 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.extra.groovy; + +import java.io.File; +import java.io.IOException; +import java.util.List; + +import org.junit.Test; + +import com.diffplug.spotless.FormatterStep; +import com.diffplug.spotless.Provisioner; +import com.diffplug.spotless.ResourceHarness; +import com.diffplug.spotless.SerializableEqualityTester; +import com.diffplug.spotless.StepHarness; +import com.diffplug.spotless.TestProvisioner; + +public class GrEclipseFormatterStepTest extends ResourceHarness { + + private static final String RESOURCE_PATH = "groovy/greclipse/format/"; + private static final String CONFIG_FILE = RESOURCE_PATH + "greclipse.properties"; + + //String is hard-coded in the GrEclipseFormatter + private static final String FORMATTER_FILENAME_REPALCEMENT = "Hello.groovy"; + + private static Provisioner provisioner() { + return TestProvisioner.mavenCentral(); + } + + @Test + public void nominal() throws Throwable { + List config = createTestFiles(CONFIG_FILE); + StepHarness.forStep(GrEclipseFormatterStep.create(config, provisioner())) + .testResource(RESOURCE_PATH + "unformatted.test", RESOURCE_PATH + "formatted.test"); + } + + @Test + public void formatterException() throws Throwable { + List config = createTestFiles(CONFIG_FILE); + StepHarness.forStep(GrEclipseFormatterStep.create(config, provisioner())) + .testException(RESOURCE_PATH + "exception.test", assertion -> { + assertion.isInstanceOf(IllegalArgumentException.class); + assertion.hasMessageContaining(FORMATTER_FILENAME_REPALCEMENT); + }); + } + + @Test + public void configurationException() throws Throwable { + String configFileName = "greclipse.exception"; + List config = createTestFiles(RESOURCE_PATH + configFileName); + StepHarness.forStep(GrEclipseFormatterStep.create(config, provisioner())) + .testException(RESOURCE_PATH + "unformatted.test", assertion -> { + assertion.isInstanceOf(IllegalArgumentException.class); + assertion.hasMessageContaining(configFileName); + }); + } + + @Test + public void equality() throws IOException { + List configFile = createTestFiles(CONFIG_FILE); + new SerializableEqualityTester() { + + @Override + protected void setupTest(API api) { + api.areDifferentThan(); + } + + @Override + protected FormatterStep create() { + return GrEclipseFormatterStep.create(configFile, provisioner()); + } + }.testEquals(); + } + +} diff --git a/lib-extra/src/test/resources/groovy/greclipse/format/exception.test b/lib-extra/src/test/resources/groovy/greclipse/format/exception.test new file mode 100644 index 000000000..9a298618c --- /dev/null +++ b/lib-extra/src/test/resources/groovy/greclipse/format/exception.test @@ -0,0 +1,18 @@ +/** +* Class description +*/ +class Foo{ +String foo + +/* Method */ +def callBar() { +new Bar().bar(foo) +} +//Compiler error. Missing close-brace. +// +//Note that the error message is additionally logged to system-err +//within GrEclipse itself. That error message is just a dump of a RAW data report. +//The whole error handling therefore looks not nicely readable. +//Since these errors should only occur if the input file is not compile-able, +//the error format is considered acceptable, since the spotless task runs +//in general only after a successful compilation. \ No newline at end of file diff --git a/lib-extra/src/test/resources/groovy/greclipse/format/formatted.test b/lib-extra/src/test/resources/groovy/greclipse/format/formatted.test new file mode 100644 index 000000000..9384c375e --- /dev/null +++ b/lib-extra/src/test/resources/groovy/greclipse/format/formatted.test @@ -0,0 +1,22 @@ +/** + * Class description + */ +class Foo +{ + String foo + + /* Method */ + def callBar() + { + new Bar().bar(foo) } + + /** Inner class */ + class Bar + { + + def bar(foo) + { + println "${foo}Bar" }}} + +def foo = new Foo(foo: 'Foo') +foo.callBar() \ No newline at end of file diff --git a/lib-extra/src/test/resources/groovy/greclipse/format/greclipse.exception b/lib-extra/src/test/resources/groovy/greclipse/format/greclipse.exception new file mode 100644 index 000000000..c991f4498 --- /dev/null +++ b/lib-extra/src/test/resources/groovy/greclipse/format/greclipse.exception @@ -0,0 +1 @@ +The file name extension 'exception' not supported for GrEclipse configuration files. \ No newline at end of file diff --git a/lib-extra/src/test/resources/groovy/greclipse/format/greclipse.properties b/lib-extra/src/test/resources/groovy/greclipse/format/greclipse.properties new file mode 100644 index 000000000..631310a0e --- /dev/null +++ b/lib-extra/src/test/resources/groovy/greclipse/format/greclipse.properties @@ -0,0 +1,34 @@ +#Whether to use 'space', 'tab' or 'mixed' (both) characters for indentation. +#The default value is 'tab'. +org.eclipse.jdt.core.formatter.tabulation.char=space + +#Number of spaces used for indentation in case 'space' characters +#have been selected. The default value is 4. +org.eclipse.jdt.core.formatter.tabulation.size=2 + +#Number of spaces used for indentation in case 'mixed' characters +#have been selected. The default value is 4. +org.eclipse.jdt.core.formatter.indentation.size=2 + +#Whether or not indentation characters are inserted into empty lines. +#The default value is 'true'. +org.eclipse.jdt.core.formatter.indent_empty_lines=false + +#Number of spaces used for multiline indentation. +#The default value is 2. +groovy.formatter.multiline.indentation=1 + +#Length after which list are considered too long. These will be wrapped. +#The default value is 30. +groovy.formatter.longListLength=30 + +#Whether opening braces position shall be the next line. +#The default value is 'same'. +groovy.formatter.braces.start=next + +#Whether closing braces position shall be the next line. +#The default value is 'next'. +groovy.formatter.braces.end=same + +#Remove unnecessary semicolons. The default value is 'false'. +groovy.formatter.remove.unnecessary.semicolons=true diff --git a/lib-extra/src/test/resources/groovy/greclipse/format/unformatted.test b/lib-extra/src/test/resources/groovy/greclipse/format/unformatted.test new file mode 100644 index 000000000..c05fc936f --- /dev/null +++ b/lib-extra/src/test/resources/groovy/greclipse/format/unformatted.test @@ -0,0 +1,20 @@ +/** +* Class description +*/ +class Foo{ +String foo + +/* Method */ +def callBar() { +new Bar().bar(foo) +} + +/** Inner class */ +class Bar { + +def bar(foo) { +println "${foo}Bar" +}}} + +def foo = new Foo(foo: 'Foo'); +foo.callBar() \ No newline at end of file diff --git a/lib/build.gradle b/lib/build.gradle index a58e73560..3493f5a7b 100644 --- a/lib/build.gradle +++ b/lib/build.gradle @@ -12,6 +12,4 @@ dependencies { } // we'll hold the core lib to a high standard -findbugs { - reportLevel = 'low' // low|medium|high (low = sensitive to even minor mistakes) -} +findbugs { reportLevel = 'low' } // low|medium|high (low = sensitive to even minor mistakes) diff --git a/lib/src/main/java/com/diffplug/spotless/FileSignature.java b/lib/src/main/java/com/diffplug/spotless/FileSignature.java index 8df42cbd7..c0da096d1 100644 --- a/lib/src/main/java/com/diffplug/spotless/FileSignature.java +++ b/lib/src/main/java/com/diffplug/spotless/FileSignature.java @@ -69,10 +69,14 @@ public static FileSignature signAsList(Iterable files) throws IOException return new FileSignature(asNonNullList(files)); } + /** Creates file signature whereas order of the files remains unchanged. */ + public static FileSignature signAsSet(File... files) throws IOException { + return signAsSet(Arrays.asList(files)); + } + /** Creates file signature insensitive to the order of the files. */ public static FileSignature signAsSet(Iterable files) throws IOException { - return new FileSignature( - toSortedSet(asNonNullList(files))); + return new FileSignature(toSortedSet(files)); } private FileSignature(final List files) throws IOException { @@ -106,7 +110,8 @@ public File getOnlyFile() { } /** Sorts "toBeSorted" and removes duplicates. */ - private static > List toSortedSet(List toBeSorted) { + static > List toSortedSet(Iterable raw) { + List toBeSorted = asNonNullList(raw); // sort it Collections.sort(toBeSorted); // remove any duplicates (normally there won't be any) @@ -136,5 +141,4 @@ private static List asNonNullList(Iterable input) { }); return shallowCopy; } - } diff --git a/lib/src/main/java/com/diffplug/spotless/SerializableFileFilter.java b/lib/src/main/java/com/diffplug/spotless/SerializableFileFilter.java index 27561758f..6b5c16df6 100644 --- a/lib/src/main/java/com/diffplug/spotless/SerializableFileFilter.java +++ b/lib/src/main/java/com/diffplug/spotless/SerializableFileFilter.java @@ -20,8 +20,8 @@ /** A file filter with full support for serialization. */ public interface SerializableFileFilter extends FileFilter, Serializable, NoLambda { - /** Creates a FileFilter which will accept all files except files with the given name. */ - public static SerializableFileFilter skipFilesNamed(String name) { - return new SerializableFileFilterImpl.SkipFilesNamed(name); + /** Creates a FileFilter which will accept all files except files with the given name(s). */ + public static SerializableFileFilter skipFilesNamed(String... names) { + return new SerializableFileFilterImpl.SkipFilesNamed(names); } } diff --git a/lib/src/main/java/com/diffplug/spotless/SerializableFileFilterImpl.java b/lib/src/main/java/com/diffplug/spotless/SerializableFileFilterImpl.java index 82b935839..a0330582b 100644 --- a/lib/src/main/java/com/diffplug/spotless/SerializableFileFilterImpl.java +++ b/lib/src/main/java/com/diffplug/spotless/SerializableFileFilterImpl.java @@ -16,21 +16,31 @@ package com.diffplug.spotless; import java.io.File; +import java.util.Arrays; +import java.util.List; import java.util.Objects; class SerializableFileFilterImpl { static class SkipFilesNamed extends NoLambda.EqualityBasedOnSerialization implements SerializableFileFilter { private static final long serialVersionUID = 1L; - private final String nameToSkip; + private final String[] namesToSkip; - SkipFilesNamed(String nameToSkip) { - this.nameToSkip = Objects.requireNonNull(nameToSkip); + SkipFilesNamed(String... namesToSkip) { + Objects.requireNonNull(namesToSkip); + List sorted = FileSignature.toSortedSet(Arrays.asList(namesToSkip)); + this.namesToSkip = sorted.toArray(new String[sorted.size()]); } @Override public boolean accept(File pathname) { - return !pathname.getName().equals(nameToSkip); + String name = pathname.getName(); + for (String toSkip : namesToSkip) { + if (toSkip.equals(name)) { + return false; + } + } + return true; } } } diff --git a/plugin-gradle/CHANGES.md b/plugin-gradle/CHANGES.md index 384c1a621..deda0cb2a 100644 --- a/plugin-gradle/CHANGES.md +++ b/plugin-gradle/CHANGES.md @@ -2,6 +2,8 @@ ### Version 3.3.0-SNAPSHOT - TBD ([javadoc](https://diffplug.github.io/spotless/javadoc/snapshot/), [snapshot](https://oss.sonatype.org/content/repositories/snapshots/com/diffplug/spotless/spotless-plugin-gradle/)) +* Added support for groovy formatting (huge thanks to Frank Vennemeyer! [#94](https://github.com/diffplug/spotless/pull/94), [#89](https://github.com/diffplug/spotless/pull/89), [#88](https://github.com/diffplug/spotless/pull/88), [#85](https://github.com/diffplug/spotless/pull/85)) + ### Version 3.2.0 - April 3rd 2017 ([javadoc](https://diffplug.github.io/spotless/javadoc/spotless-plugin-gradle/3.2.0/), [jcenter](https://bintray.com/diffplug/opensource/spotless-plugin-gradle/3.2.0)) * Update default KtLint from 0.3.1 to 0.6.1 (thanks to @kvnxiao [#93](https://github.com/diffplug/spotless/pull/93)). diff --git a/plugin-gradle/README.md b/plugin-gradle/README.md index c4b3ac81e..37247f9e8 100644 --- a/plugin-gradle/README.md +++ b/plugin-gradle/README.md @@ -79,6 +79,7 @@ Spotless can check and apply formatting to any plain-text file, using simple rul * Eclipse's java code formatter (including style and import ordering) * Google's [google-java-format](https://github.com/google/google-java-format) +* [Groovy Eclipse](https://github.com/groovy/groovy-eclipse/wiki)'s groovy code formatter * [FreshMark](https://github.com/diffplug/freshmark) (markdown with variables) * Any user-defined function which takes an unformatted string and outputs a formatted version. @@ -86,17 +87,19 @@ Contributions are welcome, see [the contributing guide](../CONTRIBUTING.md) for Spotless requires Gradle to be running on JRE 8+.See [issue #7](https://github.com/diffplug/spotless/issues/7) for details. + + ## Applying to Java source +By default, all Java source sets will be formatted. To change this, +set the `target` parameter as described in the [Custom rules](#custom) section. + ```gradle apply plugin: 'java' ... spotless { java { - // By default, all Java source sets will be formatted. To change - // this, set the 'target' parameter as described in the next section. - licenseHeader '/* Licensed under Apache-2.0 */' // License header licenseHeaderFile 'spotless.license.java' // License header file // Obviously, you can't specify both licenseHeader and licenseHeaderFile at the same time @@ -117,6 +120,8 @@ spotless { See [ECLIPSE_SCREENSHOTS](../ECLIPSE_SCREENSHOTS.md) for screenshots that demonstrate how to get and install the eclipseFormatFile and importOrderFile mentioned above. + + ## Applying to Java source ([google-java-format](https://github.com/google/google-java-format)) ```gradle @@ -129,6 +134,49 @@ spotless { } ``` + + +## Applying to Groovy source + +Configuration for Groovy is similar to [Java](#java). Most java steps, like `licenseHeader` and `importOrder`, support Groovy as well as Java. + +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. + +```gradle +apply plugin: 'groovy' +... + +spotless { + java { + licenseHeaderFile 'spotless.license.java' + googleJavaFormat() // use a specific formatter for Java files + } + groovy { + licenseHeaderFile 'spotless.license.java' + excludeJava() // excludes all Java sources within the Groovy source dirs from formatting + + // the Groovy Eclipse formatter extends the Java Eclipse formatter, + // so it formats Java files by default (unless `excludeJava` is used). + greclipse().configFile('greclipse.properties') + } +} +``` + +The [Groovy-Eclipse](https://github.com/groovy/groovy-eclipse) formatter is based on the Eclipse Java formatter as used by `eclipseFormatFile`. It uses the same configuration parameters plus a few additional ones. These parameters can be configured within a single file, like the Java properties file [greclipse.properties](../lib-extra/src/test/resources/groovy/greclipse/format/greclipse.properties) in the previous example. The formatter step can also load the [exported Eclipse properties](../ECLIPSE_SCREENSHOTS.md) and augment it with the `org.codehaus.groovy.eclipse.ui.prefs` from the Eclipse workspace as shown below. + +```gradle +spotless { + groovy { + // Use the default version and Groovy-Eclipse default configuration + greclipse() + // optional: you can specify a specific version or config file(s) + greclipse('2.3.0').configFile('spotless.eclipseformat.xml', 'org.codehaus.groovy.eclipse.ui.prefs') + } +} +``` + + + ## Applying [FreshMark](https://github.com/diffplug/freshmark) to markdown files Freshmark lets you generate markdown in the comments of your markdown. This helps to keep badges and links up-to-date (see the source for this file), and can @@ -148,6 +196,8 @@ spotless { } ``` + + ## Applying [scalafmt](https://olafurpg.github.io/scalafmt/#Scalafmt-codeformatterforScala) to Scala files ```gradle @@ -160,6 +210,8 @@ spotless { } ``` + + ## Applying [ktlint](https://github.com/shyiko/ktlint) to Kotlin files ```gradle @@ -174,6 +226,7 @@ spotless { } } ``` + ## Custom rules @@ -218,6 +271,8 @@ If you use `custom` or `customLazy`, you might want to take a look at [this java See [`JavaExtension.java`](src/main/java/com/diffplug/gradle/spotless/java/JavaExtension.java?ts=4) if you'd like to see how a language-specific set of custom rules is implemented. We'd love PR's which add support for other languages. + + ## Line endings and encodings (invisible stuff) Spotless uses UTF-8 by default, but you can use [any encoding which Java supports](https://docs.oracle.com/javase/8/docs/technotes/guides/intl/encoding.doc.html). You can set it globally, and you can also set it per-format. @@ -236,6 +291,8 @@ Line endings can also be set globally or per-format using the `lineEndings` prop You can easily set the line endings of different files using [a `.gitattributes` file](https://help.github.com/articles/dealing-with-line-endings/). Here's an example `.gitattributes` which sets all files to unix newlines: `* text eol=lf`. + + ## Disabling warnings and error messages The `check` task is Gradle's built-in task for grouping all verification tasks - unit tests, static analysis, etc. By default, `spotlessCheck` is added as a dependency to `check`. @@ -264,6 +321,8 @@ spotless { Note that `enforceCheck` is a global property which affects all formats (outside the java block), while `ignoreErrorForStep/Path` are local to a single format (inside the java block). + + ## How do I preview what `spotlessApply` will do? - Save your working tree with `git add -A`, then `git commit -m "Checkpoint before spotless."` @@ -272,6 +331,8 @@ Note that `enforceCheck` is a global property which affects all formats (outside - If you don't like what spotless did, `git reset --hard` - If you'd like to remove the "checkpoint" commit, `git reset --soft head~1` will make the checkpoint commit "disappear" from history, but keeps the changes in your working directory. + + ## Example configurations (from real-world projects) Spotless is hosted on jcenter and at plugins.gradle.org. [Go here](https://plugins.gradle.org/plugin/com.diffplug.gradle.spotless) if you're not sure how to import the plugin. diff --git a/plugin-gradle/build.gradle b/plugin-gradle/build.gradle index 21a6c68e0..785e45db4 100644 --- a/plugin-gradle/build.gradle +++ b/plugin-gradle/build.gradle @@ -33,9 +33,7 @@ task spotlessApply(type: JavaExec) { classpath sourceSets.test.runtimeClasspath main = 'com.diffplug.gradle.spotless.SelfTestApply' } -test { - testLogging.showStandardStreams = true -} +test { testLogging.showStandardStreams = true } ////////////////////////// // GRADLE PLUGIN PORTAL // @@ -51,7 +49,12 @@ pluginBundle { spotlessPlugin { id = 'com.diffplug.gradle.spotless' displayName = 'Spotless formatting plugin' - tags = ['format', 'style', 'license', 'header'] + tags = [ + 'format', + 'style', + 'license', + 'header' + ] } } 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 new file mode 100644 index 000000000..063cb3615 --- /dev/null +++ b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/GroovyExtension.java @@ -0,0 +1,135 @@ +/* + * Copyright 2016 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.util.List; +import java.util.Objects; + +import org.gradle.api.GradleException; +import org.gradle.api.Project; +import org.gradle.api.internal.file.UnionFileCollection; +import org.gradle.api.internal.plugins.DslObject; +import org.gradle.api.plugins.JavaPluginConvention; +import org.gradle.api.tasks.GroovySourceSet; +import org.gradle.api.tasks.SourceSet; + +import com.diffplug.spotless.FormatterStep; +import com.diffplug.spotless.SerializableFileFilter; +import com.diffplug.spotless.extra.groovy.GrEclipseFormatterStep; +import com.diffplug.spotless.generic.LicenseHeaderStep; +import com.diffplug.spotless.java.ImportOrderStep; + +public class GroovyExtension extends FormatExtension { + static final String NAME = "groovy"; + + public GroovyExtension(SpotlessExtension rootExtension) { + super(rootExtension); + } + + boolean excludeJava = false; + + /** Excludes .java files, to focus on only .groovy files. */ + public void excludeJava() { + excludeJava(true); + } + + /** Determines whether to exclude .java files, to focus on only .groovy files. */ + public void excludeJava(boolean excludeJava) { + this.excludeJava = excludeJava; + } + + public void licenseHeader(String licenseHeader) { + licenseHeader(licenseHeader, JavaExtension.LICENSE_HEADER_DELIMITER); + } + + public void licenseHeaderFile(Object licenseHeaderFile) { + licenseHeaderFile(licenseHeaderFile, JavaExtension.LICENSE_HEADER_DELIMITER); + } + + public void importOrder(List importOrder) { + addStep(ImportOrderStep.createFromOrder(importOrder)); + } + + public void importOrderFile(Object importOrderFile) { + addStep(ImportOrderStep.createFromFile(getProject().file(importOrderFile))); + } + + public GrEclipseConfig greclipse() { + return greclipse(GrEclipseFormatterStep.defaultVersion()); + } + + public GrEclipseConfig greclipse(String version) { + return new GrEclipseConfig(version); + } + + public class GrEclipseConfig { + final String version; + Object[] configFiles; + + GrEclipseConfig(String version) { + configFiles = new Object[0]; + this.version = Objects.requireNonNull(version); + addStep(createStep()); + } + + public void configFile(Object... configFiles) { + this.configFiles = configFiles; + replaceStep(createStep()); + } + + private FormatterStep createStep() { + Project project = getProject(); + return GrEclipseFormatterStep.create(version, + project.files(configFiles).getFiles(), + GradleProvisioner.fromProject(project)); + } + } + + /** If the user hasn't specified the files yet, we'll assume he/she means all of the groovy files. */ + @Override + protected void setupTask(SpotlessTask task) { + if (target == null) { + JavaPluginConvention convention = getProject().getConvention().getPlugin(JavaPluginConvention.class); + if (convention == null) { + throw new GradleException("You must apply the groovy plugin before the spotless plugin if you are using the groovy extension."); + } + //Add all Groovy files (may contain Java files as well) + + UnionFileCollection union = new UnionFileCollection(); + for (SourceSet sourceSet : convention.getSourceSets()) { + GroovySourceSet groovySourceSet = new DslObject(sourceSet).getConvention().getPlugin(GroovySourceSet.class); + if (excludeJava) { + union.add(groovySourceSet.getAllGroovy()); + } else { + union.add(groovySourceSet.getGroovy()); + } + } + target = union; + } else if (excludeJava) { + throw new IllegalArgumentException("'excludeJava' is not supported in combination with a custom 'target'."); + } + // LicenseHeaderStep completely blows apart package-info.java/groovy - this common-sense check + // ensures that it skips both. See https://github.com/diffplug/spotless/issues/1 + steps.replaceAll(step -> { + if (LicenseHeaderStep.name().equals(step.getName())) { + return step.filterByFile(SerializableFileFilter.skipFilesNamed("package-info.java", "package-info.groovy")); + } else { + return step; + } + }); + super.setupTask(task); + } +} 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 c259db8b7..7f90a096d 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 @@ -39,7 +39,7 @@ public JavaExtension(SpotlessExtension rootExtension) { // If this constant changes, don't forget to change the similarly-named one in // testlib/src/test/java/com/diffplug/spotless/generic/LicenseHeaderStepTest.java as well - private static final String LICENSE_HEADER_DELIMITER = "package "; + static final String LICENSE_HEADER_DELIMITER = "package "; public void licenseHeader(String licenseHeader) { licenseHeader(licenseHeader, LICENSE_HEADER_DELIMITER); 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 b9de108e2..410d285f2 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 @@ -29,14 +29,12 @@ public KotlinExtension(SpotlessExtension rootExtension) { super(rootExtension); } - private static final String LICENSE_HEADER_DELIMITER = "package "; - public void licenseHeader(String licenseHeader) { - licenseHeader(licenseHeader, LICENSE_HEADER_DELIMITER); + licenseHeader(licenseHeader, JavaExtension.LICENSE_HEADER_DELIMITER); } public void licenseHeaderFile(Object licenseHeaderFile) { - licenseHeaderFile(licenseHeaderFile, LICENSE_HEADER_DELIMITER); + licenseHeaderFile(licenseHeaderFile, JavaExtension.LICENSE_HEADER_DELIMITER); } /** Adds the specified version of [ktlint](https://github.com/shyiko/ktlint). */ 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 301b6a7ac..6f2cc4ba1 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 @@ -91,6 +91,11 @@ public void freshmark(Action closure) { configure(FreshMarkExtension.NAME, FreshMarkExtension.class, closure); } + /** Configures the special groovy-specific extension. */ + public void groovy(Action closure) { + configure(GroovyExtension.NAME, GroovyExtension.class, closure); + } + /** Configures a custom extension. */ public void format(String name, Action closure) { configure(name, FormatExtension.class, closure); diff --git a/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/GroovyDefaultTargetTest.java b/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/GroovyDefaultTargetTest.java new file mode 100644 index 000000000..8673dbe8d --- /dev/null +++ b/plugin-gradle/src/test/java/com/diffplug/gradle/spotless/GroovyDefaultTargetTest.java @@ -0,0 +1,107 @@ +/* + * Copyright 2016 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.File; +import java.io.IOException; + +import org.assertj.core.api.Assertions; +import org.junit.Assert; +import org.junit.Test; + +import com.diffplug.gradle.spotless.GradleIntegrationTest; + +public class GroovyDefaultTargetTest extends GradleIntegrationTest { + + private final String HEADER = "//My tests header"; + + @Test + public void includeJava() throws IOException { + testIncludeExcludeOption(false); + } + + @Test + public void excludeJava() throws IOException { + testIncludeExcludeOption(true); + } + + private void testIncludeExcludeOption(boolean excludeJava) throws IOException { + String excludeStatement = excludeJava ? "excludeJava()" : ""; + write("build.gradle", + "plugins {", + " id 'com.diffplug.gradle.spotless'", + "}", + "repositories { mavenLocal() }", + "", + "apply plugin: 'groovy'", + "", + "spotless {", + " groovy {", + excludeStatement, + " licenseHeader('" + HEADER + "')", + " }", + "}"); + + String original = getTestResource("groovy/licenseheader/JavaCodeWithoutHeader.test"); + + File javaSrcJavaFile = write("src/main/java/test.java", original); + File groovySrcJavaFile = write("src/main/groovy/test.java", original); + File groovySrcGroovyFile = write("src/main/groovy/test.groovy", original); + + // write appends a line ending so re-read to see what the original currently looks like + original = read("src/main/java/test.java"); + + // Run + gradleRunner().withArguments("spotlessApply").build(); + + // Common checks + assertFileContent(original, javaSrcJavaFile); + + Assertions.assertThat(read(groovySrcGroovyFile.toPath())).contains(HEADER); + + if (excludeJava) { + assertFileContent(original, groovySrcJavaFile); + } else { + Assertions.assertThat(read(groovySrcJavaFile.toPath())).contains(HEADER); + } + } + + @Test + public void excludeJavaWithCustomTarget() throws IOException { + write("build.gradle", + "plugins {", + " id 'com.diffplug.gradle.spotless'", + "}", + "repositories { mavenLocal() }", + "", + "apply plugin: 'groovy'", + "", + "spotless {", + " groovy {", + " excludeJava()", + " target '**/*.java', '**/*.groovy'", + " }", + "}"); + + try { + gradleRunner().withArguments("spotlessApply").build(); + Assert.fail("Exception expected when running 'excludeJava' in combination with 'target'."); + } catch (Throwable t) { + Assertions.assertThat(t).hasMessageContaining("'excludeJava' is not supported"); + } + } + +} diff --git a/plugin-gradle/src/test/resources/groovy/licenseheader/JavaCodeWithoutHeader.test b/plugin-gradle/src/test/resources/groovy/licenseheader/JavaCodeWithoutHeader.test new file mode 100644 index 000000000..420b75bb0 --- /dev/null +++ b/plugin-gradle/src/test/resources/groovy/licenseheader/JavaCodeWithoutHeader.test @@ -0,0 +1,5 @@ +package my.test + +class Clazz { + +} \ No newline at end of file diff --git a/spotless.groovyformat.prefs b/spotless.groovyformat.prefs new file mode 100644 index 000000000..da2cb796c --- /dev/null +++ b/spotless.groovyformat.prefs @@ -0,0 +1,2 @@ +groovy.formatter.remove.unnecessary.semicolons=true +groovy.formatter.longListLength=1 \ No newline at end of file diff --git a/spotlessSelf.gradle b/spotlessSelf.gradle index 726c5ead6..a9eab5c40 100644 --- a/spotlessSelf.gradle +++ b/spotlessSelf.gradle @@ -4,26 +4,36 @@ plugins { id 'java' } -repositories { mavenCentral() } +repositories { jcenter() } spotless { + def noInternalDepsClosure = { + if (it.contains('import org.gradle.internal.') && + !it.contains('def noInternalDepsClosure')) { + throw new AssertionError("Accidental internal import") + } + } java { target fileTree('.') { include '**/*.java' exclude '_ext/*/build/**' } - - custom 'noInternalDeps', { - if (it.contains('import org.gradle.internal.')) { - throw new AssertionError("Accidental internal import") - } - } + custom 'noInternalDeps', noInternalDepsClosure bumpThisNumberIfACustomStepChanges(1) - licenseHeaderFile 'spotless.license' importOrderFile 'spotless.importorder' eclipseFormatFile 'spotless.eclipseformat.xml' trimTrailingWhitespace() } + groovy { + target fileTree('.') { + include '**/*.gradle' + exclude '_ext/**' + } + custom 'noInternalDeps', noInternalDepsClosure + custom 'preventFormatPingPong', { return it.replaceAll('}[ \t]+}', '}}') } + bumpThisNumberIfACustomStepChanges(1) + greclipse().configFile('spotless.eclipseformat.xml', 'spotless.groovyformat.prefs') + } freshmark { target '**/*.md' propertiesFile('gradle.properties') @@ -33,7 +43,7 @@ spotless { } } format 'misc', { - target '**/*.gradle', '**/*.md', '**/*.gitignore' + target '**/*.md', '**/*.gitignore' indentWithTabs() trimTrailingWhitespace() endWithNewline() diff --git a/testlib/build.gradle b/testlib/build.gradle index 7903b7a45..953e1b168 100644 --- a/testlib/build.gradle +++ b/testlib/build.gradle @@ -13,6 +13,5 @@ dependencies { } // we'll hold the testlib to a low standard (prize brevity) -findbugs { - reportLevel = 'high' // low|medium|high (low = sensitive to even minor mistakes) -} +findbugs { reportLevel = 'high' } // low|medium|high (low = sensitive to even minor mistakes) + diff --git a/testlib/src/main/java/com/diffplug/spotless/ResourceHarness.java b/testlib/src/main/java/com/diffplug/spotless/ResourceHarness.java index d0f740c7e..529461afa 100644 --- a/testlib/src/main/java/com/diffplug/spotless/ResourceHarness.java +++ b/testlib/src/main/java/com/diffplug/spotless/ResourceHarness.java @@ -76,6 +76,10 @@ protected File write(String path, LineEnding ending, Charset encoding, String... return target.toFile(); } + protected String read(Path path) throws IOException { + return read(path, LineEnding.UNIX); + } + protected String read(String path) throws IOException { return read(path, LineEnding.UNIX); } @@ -84,9 +88,17 @@ protected String read(String path, LineEnding ending) throws IOException { return read(path, ending, StandardCharsets.UTF_8); } + protected String read(Path path, LineEnding ending) throws IOException { + return read(path, ending, StandardCharsets.UTF_8); + } + protected String read(String path, LineEnding ending, Charset encoding) throws IOException { Path target = newFile(path).toPath(); - String content = new String(Files.readAllBytes(target), encoding); + return read(target, ending, encoding); + } + + protected String read(Path path, LineEnding ending, Charset encoding) throws IOException { + String content = new String(Files.readAllBytes(path), encoding); String allUnixNewline = LineEnding.toUnix(content); return allUnixNewline.replace("\n", ending.str()); }