-
Notifications
You must be signed in to change notification settings - Fork 489
Support Scalafix #591
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Support Scalafix #591
Changes from all commits
a69cb7d
f805cc8
43f00d6
b09e3ef
dd7edba
4ee1408
c7a4b0b
749df07
cbf54d0
b930a91
c03942b
d447f96
1bc5d3f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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; | ||
private final String classpath; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 There was a problem hiding this comment. Choose a reason for hiding this commentThe 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; | ||
} | ||
} |
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); | ||
} | ||
} |
There was a problem hiding this comment.
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
orLinkedHashSet
which will both have predictable serialization.