Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
* Support for Scalafix ([#591](https://github.com/diffplug/spotless/pull/591))

## [2.1.0] - 2020-07-04
### Added
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ lib('kotlin.KtfmtStep') +'{{yes}} | {{yes}}
lib('markdown.FreshMarkStep') +'{{yes}} | {{no}} | {{no}} |',
lib('npm.PrettierFormatterStep') +'{{yes}} | {{yes}} | {{no}} |',
lib('npm.TsFmtFormatterStep') +'{{yes}} | {{yes}} | {{no}} |',
lib('scala.ScalaFixStep') +'{{yes}} | {{no}} | {{no}} |',
lib('scala.ScalaFmtStep') +'{{yes}} | {{yes}} | {{no}} |',
lib('sql.DBeaverSQLFormatterStep') +'{{yes}} | {{no}} | {{no}} |',
extra('wtp.EclipseWtpFormatterStep') +'{{yes}} | {{yes}} | {{no}} |',
Expand Down Expand Up @@ -89,6 +90,7 @@ extra('wtp.EclipseWtpFormatterStep') +'{{yes}} | {{yes}}
| [`markdown.FreshMarkStep`](lib/src/main/java/com/diffplug/spotless/markdown/FreshMarkStep.java) | :+1: | :white_large_square: | :white_large_square: |
| [`npm.PrettierFormatterStep`](lib/src/main/java/com/diffplug/spotless/npm/PrettierFormatterStep.java) | :+1: | :+1: | :white_large_square: |
| [`npm.TsFmtFormatterStep`](lib/src/main/java/com/diffplug/spotless/npm/TsFmtFormatterStep.java) | :+1: | :+1: | :white_large_square: |
| [`scala.ScalaFixStep`](lib/src/main/java/com/diffplug/spotless/scala/ScalaFixStep.java) | :+1: | :white_large_square: | :white_large_square: |
| [`scala.ScalaFmtStep`](lib/src/main/java/com/diffplug/spotless/scala/ScalaFmtStep.java) | :+1: | :+1: | :white_large_square: |
| [`sql.DBeaverSQLFormatterStep`](lib/src/main/java/com/diffplug/spotless/sql/DBeaverSQLFormatterStep.java) | :+1: | :white_large_square: | :white_large_square: |
| [`wtp.EclipseWtpFormatterStep`](lib-extra/src/main/java/com/diffplug/spotless/extra/wtp/EclipseWtpFormatterStep.java) | :+1: | :+1: | :white_large_square: |
Expand Down
2 changes: 1 addition & 1 deletion lib/src/main/java/com/diffplug/spotless/FileSignature.java
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ public static FileSignature signAsSet(Iterable<File> files) throws IOException {
builder.append(" " + file.getAbsolutePath() + "\n");
}
builder.append("a caching signature is being generated, which will be based only on their\n");
builder.append("names, not their full path (foo.txt, not C:\folder\foo.txt). Unexpectedly,\n");
builder.append("names, not their full path (foo.txt, not C:\\folder\\foo.txt). Unexpectedly,\n");
builder.append("you have two files with different paths, but the same names. You must\n");
builder.append("rename one of them so that all files have unique names.");
throw new IllegalArgumentException(builder.toString());
Expand Down
73 changes: 73 additions & 0 deletions lib/src/main/java/com/diffplug/spotless/scala/ScalaCompiler.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
/*
* Copyright 2016-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.spotless.scala;

import java.io.Serializable;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;

public class ScalaCompiler implements Serializable {
private static final long serialVersionUID = 1L;

private final String version;
private final Set<String> plugins;
private final Set<String> compilerOptions;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Up-to-dateness depends on the serialized state of this class, so it is better to use either TreeSet or LinkedHashSet which will both have predictable serialization.

private final String classpath;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a string containing the absolute paths of a bunch of jars, correct? It would be better to use FileSignature here, and then do FileSignature.files() to recreate the string in a public method. Besides being slightly safer, this will allow remote buildcache to work once #566 is implemented.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#566 has been implemented, so using FileSignature here will allow remote build cache to work.

private Boolean enabled = false;

public ScalaCompiler(final String version, final String classpath) {
this.plugins = new HashSet<>();
this.compilerOptions = new HashSet<>();
this.version = version;
this.classpath = classpath;
}

public String getVersion() {
return version;
}

public String getClasspath() {
return classpath;
}

public Set<String> getPlugins() {
return plugins;
}

public Set<String> getCompilerOptions() {
return compilerOptions;
}

public Boolean isEnabled() {
return enabled;
}

public void addPlugin(final String dependency) {
plugins.add(dependency);
}

public void addCompilerOptions(final Collection<String> options) {
compilerOptions.addAll(options);
}

/**
* Call this to compile the project before spotless is applied
*/
public void enable() {
enabled = true;
}
}
136 changes: 136 additions & 0 deletions lib/src/main/java/com/diffplug/spotless/scala/ScalaFixStep.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
/*
* Copyright 2016-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.spotless.scala;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.PrintStream;
import java.io.Serializable;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.*;

import javax.annotation.Nullable;

import com.diffplug.spotless.FileSignature;
import com.diffplug.spotless.FormatterFunc;
import com.diffplug.spotless.FormatterStep;
import com.diffplug.spotless.JarState;
import com.diffplug.spotless.Provisioner;

public class ScalaFixStep {
// prevent direct instantiation
private ScalaFixStep() {}

public static final String NAME = "scalafix";
private static final String DEFAULT_VERSION = "0.9.16";
private static final String DEFAULT_SCALA_VERSION = "2.12.11";
private static final String MAVEN_COORDINATE = "ch.epfl.scala:scalafix-cli_%s:%s";
private static final String SEMANTICDB_MAVEN_COORDINATE = "org.scalameta:semanticdb-scalac_%s:latest.release";

public static void init(final ScalaCompiler scalaCompiler, final File projectDir) {
final List<String> compilerOptions = Arrays.asList(
"-Ywarn-unused",
"-Yrangepos",
"-P:semanticdb:synthetics:on",
"-P:semanticdb:sourceroot:" + projectDir);

scalaCompiler.enable();
scalaCompiler.addPlugin(String.format(SEMANTICDB_MAVEN_COORDINATE, scalaCompiler.getVersion()));
scalaCompiler.addCompilerOptions(compilerOptions);
}

public static FormatterStep create(final String version, final String scalaVersion, final Provisioner provisioner, final ScalaCompiler scalaCompiler, @Nullable final File configFile) {
Objects.requireNonNull(version, "version");
Objects.requireNonNull(scalaVersion, "scalaVersion");
Objects.requireNonNull(provisioner, "provisioner");
Objects.requireNonNull(scalaCompiler, "scalaCompiler");
return FormatterStep.createLazy(NAME, () -> new State(version, scalaVersion, provisioner, scalaCompiler, configFile), State::createFormat);
}

public static String defaultVersion() {
return DEFAULT_VERSION;
}

public static String defaultScalaVersion() {
return DEFAULT_SCALA_VERSION;
}

static final class State implements Serializable {
private static final long serialVersionUID = 1L;

final JarState jarState;
final FileSignature configSignature;
final ScalaCompiler scalaCompiler;

State(final String version, final String scalaVersion, final Provisioner provisioner, final ScalaCompiler scalaCompiler, @Nullable final File configFile) throws IOException {
this.jarState = JarState.from(String.format(MAVEN_COORDINATE, scalaVersion, version), provisioner);
this.configSignature = FileSignature.signAsList(configFile == null ? Collections.emptySet() : Collections.singleton(configFile));
this.scalaCompiler = scalaCompiler;
}

FormatterFunc.NeedsFile createFormat() throws Exception {
final ClassLoader classLoader = jarState.getClassLoader();

final Class<?> javaConversionsCls = classLoader.loadClass("scala.collection.JavaConversions");
final Class<?> absolutePathCls = classLoader.loadClass("scala.meta.io.AbsolutePath");
final Class<?> classpathCls = classLoader.loadClass("scala.meta.io.Classpath");
final Class<?> someCls = classLoader.loadClass("scala.Some");
final Class<?> argsCls = classLoader.loadClass("scalafix.internal.v1.Args");
final Class<?> mainOpsCls = classLoader.loadClass("scalafix.internal.v1.MainOps");

final Method absolutePathApply = absolutePathCls.getMethod("apply", File.class, absolutePathCls);
final Method asScalaIterator = javaConversionsCls.getMethod("asScalaIterator", Iterator.class);
final Method argsDefault = argsCls.getMethod("default", absolutePathCls, PrintStream.class);
final Object workingDirectory = absolutePathCls.getMethod("workingDirectory").invoke(null);
final Object scalacOptionsIter = asScalaIterator.invoke(null, scalaCompiler.getCompilerOptions().iterator());
final Object scalacOptions = scalacOptionsIter.getClass().getMethod("toList").invoke(scalacOptionsIter);
final Object classpath = classpathCls.getMethod("apply", String.class).invoke(null, scalaCompiler.getClasspath());

final Method someApply = someCls.getMethod("apply", Object.class);

return (String input, File source) -> {
final ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
final PrintStream out = new PrintStream(byteArrayOutputStream);
final Object args = argsDefault.invoke(null, workingDirectory, out);
setField(args, "stdout", true);
setField(args, "scalacOptions", scalacOptions);
setField(args, "classpath", classpath);
if (!configSignature.files().isEmpty()) {
final File file = configSignature.getOnlyFile();
final Object config = absolutePathApply.invoke(null, file, workingDirectory);
setField(args, "config", someApply.invoke(null, config));
}

final Object maybeValidatedArgs = argsCls.getMethod("validate").invoke(args);
final Object validatedArgs = maybeValidatedArgs.getClass().getMethod("get").invoke(maybeValidatedArgs);
final Method handleFile = mainOpsCls.getMethod("handleFile", validatedArgs.getClass(), absolutePathCls);
final Object sourcePath = absolutePathApply.invoke(null, source, workingDirectory);
handleFile.invoke(null, validatedArgs, sourcePath);
final String output = byteArrayOutputStream.toString();
// Remove the last new line added by println
return output.substring(0, output.length() - 1);
};
}
}

private static void setField(final Object obj, final String name, final Object value) throws Exception {
final Field field = obj.getClass().getDeclaredField(name);
field.setAccessible(true);
field.set(obj, value);
}
}
2 changes: 2 additions & 0 deletions plugin-gradle/CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
* Support for Scalafix ([#591](https://github.com/diffplug/spotless/pull/591))

## [5.0.0] - 2020-07-12

Expand Down
14 changes: 14 additions & 0 deletions plugin-gradle/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,7 @@ spotless {
// by default, all `.scala` and `.sc` files in the java sourcesets will be formatted

scalafmt() // has its own section below
scalafix() // has its own section below

licenseHeader '/* (C) $YEAR */', 'package ' // or licenseHeaderFile
// note the 'package ' argument - this is a regex which identifies the top
Expand All @@ -310,6 +311,19 @@ spotless {
scalafmt('2.6.1').configFile('scalafmt.conf')
```

### scalafix

[homepage](https://scalacenter.github.io/scalafix/). [changelog](https://github.com/scalacenter/scalafix/releases). [config docs](https://scalacenter.github.io/scalafix/docs/users/configuration.html).

```gradle
spotless {
scala {
scalafix()
// optional: you can specify a specific version of scalafix-cli >= 0.9.1 or Scala version suffix depended by it, or config file
// default: 0.9.16 (scalafix-cli), 2.12.11 (Scala), .scalafix.conf (config file)
scalafix('0.9.1', '2.11.12').configFile('scalafix.conf')
```

<a name="applying-to-cc-sources"></a>

## C/C++
Expand Down
Loading