Skip to content

Commit d0e5667

Browse files
committed
Convert scalafmt integration to use a compile-only sourceset
1 parent b535dce commit d0e5667

File tree

11 files changed

+78
-245
lines changed

11 files changed

+78
-245
lines changed

CHANGES.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ This document is intended for Spotless developers.
1010
We adhere to the [keepachangelog](https://keepachangelog.com/en/1.0.0/) format (starting after version `1.27.0`).
1111

1212
## [Unreleased]
13+
* Converted `scalafmt` integration to use a compile-only source set. (fixes [#524](https://github.com/diffplug/spotless/issues/524))
1314

1415
## [2.28.1] - 2022-08-10
1516
### Fixed

lib/build.gradle

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@ def NEEDS_GLUE = [
1212
'ktfmt',
1313
'ktlint',
1414
'flexmark',
15-
'diktat'
15+
'diktat',
16+
'scalafmt'
1617
]
1718
for (glue in NEEDS_GLUE) {
1819
sourceSets.register(glue) {
@@ -51,6 +52,9 @@ dependencies {
5152
ktlintCompileOnly "com.pinterest.ktlint:ktlint-ruleset-experimental:$VER_KTLINT"
5253
ktlintCompileOnly "com.pinterest.ktlint:ktlint-ruleset-standard:$VER_KTLINT"
5354

55+
String VER_SCALAFMT="3.5.9"
56+
scalafmtCompileOnly "org.scalameta:scalafmt-core_2.13:$VER_SCALAFMT"
57+
5458
String VER_DIKTAT = "1.2.3"
5559
diktatCompileOnly "org.cqfn.diktat:diktat-rules:$VER_DIKTAT"
5660

Lines changed: 10 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2016-2021 DiffPlug
2+
* Copyright 2016-2022 DiffPlug
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -18,13 +18,8 @@
1818
import java.io.File;
1919
import java.io.IOException;
2020
import java.io.Serializable;
21-
import java.lang.reflect.Method;
22-
import java.nio.charset.StandardCharsets;
23-
import java.nio.file.Files;
21+
import java.lang.reflect.Constructor;
2422
import java.util.Collections;
25-
import java.util.Objects;
26-
import java.util.regex.Matcher;
27-
import java.util.regex.Pattern;
2823

2924
import javax.annotation.Nullable;
3025

@@ -39,23 +34,17 @@ public class ScalaFmtStep {
3934
// prevent direct instantiation
4035
private ScalaFmtStep() {}
4136

42-
private static final Pattern VERSION_PRE_2_0 = Pattern.compile("[10]\\.(\\d+)\\.\\d+");
43-
private static final Pattern VERSION_PRE_3_0 = Pattern.compile("2\\.(\\d+)\\.\\d+");
44-
private static final String DEFAULT_VERSION = "3.0.8";
37+
private static final String DEFAULT_VERSION = "3.5.9";
4538
static final String NAME = "scalafmt";
46-
static final String MAVEN_COORDINATE_PRE_2_0 = "com.geirsson:scalafmt-core_2.11:";
47-
static final String MAVEN_COORDINATE_PRE_3_0 = "org.scalameta:scalafmt-core_2.11:";
4839
static final String MAVEN_COORDINATE = "org.scalameta:scalafmt-core_2.13:";
4940

5041
public static FormatterStep create(Provisioner provisioner) {
5142
return create(defaultVersion(), provisioner, null);
5243
}
5344

5445
public static FormatterStep create(String version, Provisioner provisioner, @Nullable File configFile) {
55-
Objects.requireNonNull(version, "version");
56-
Objects.requireNonNull(provisioner, "provisioner");
5746
return FormatterStep.createLazy(NAME,
58-
() -> new State(version, provisioner, configFile),
47+
() -> new State(JarState.from(MAVEN_COORDINATE + version, provisioner), configFile),
5948
State::createFormat);
6049
}
6150

@@ -69,78 +58,16 @@ static final class State implements Serializable {
6958
final JarState jarState;
7059
final FileSignature configSignature;
7160

72-
State(String version, Provisioner provisioner, @Nullable File configFile) throws IOException {
73-
String mavenCoordinate;
74-
Matcher versionMatcher;
75-
if ((versionMatcher = VERSION_PRE_2_0.matcher(version)).matches()) {
76-
mavenCoordinate = MAVEN_COORDINATE_PRE_2_0;
77-
} else if ((versionMatcher = VERSION_PRE_3_0.matcher(version)).matches()) {
78-
mavenCoordinate = MAVEN_COORDINATE_PRE_3_0;
79-
} else {
80-
mavenCoordinate = MAVEN_COORDINATE;
81-
}
82-
83-
this.jarState = JarState.from(mavenCoordinate + version, provisioner);
61+
State(JarState jarState, @Nullable File configFile) throws IOException {
62+
this.jarState = jarState;
8463
this.configSignature = FileSignature.signAsList(configFile == null ? Collections.emptySet() : Collections.singleton(configFile));
8564
}
8665

8766
FormatterFunc createFormat() throws Exception {
88-
ClassLoader classLoader = jarState.getClassLoader();
89-
90-
// scalafmt returns instances of formatted, we get result by calling get()
91-
Class<?> formatted = classLoader.loadClass("org.scalafmt.Formatted");
92-
Method formattedGet = formatted.getMethod("get");
93-
94-
// this is how we actually do a format
95-
Class<?> scalafmt = classLoader.loadClass("org.scalafmt.Scalafmt");
96-
Class<?> scalaSet = classLoader.loadClass("scala.collection.immutable.Set");
97-
98-
Object defaultScalaFmtConfig = scalafmt.getMethod("format$default$2").invoke(null);
99-
Object emptyRange = scalafmt.getMethod("format$default$3").invoke(null);
100-
Method formatMethod = scalafmt.getMethod("format", String.class, defaultScalaFmtConfig.getClass(), scalaSet);
101-
102-
// now we just need to parse the config, if any
103-
Object config;
104-
if (configSignature.files().isEmpty()) {
105-
config = defaultScalaFmtConfig;
106-
} else {
107-
File file = configSignature.getOnlyFile();
108-
109-
Class<?> optionCls = classLoader.loadClass("scala.Option");
110-
Class<?> configCls = classLoader.loadClass("org.scalafmt.config.Config");
111-
Class<?> scalafmtCls = classLoader.loadClass("org.scalafmt.Scalafmt");
112-
113-
Object configured;
114-
115-
try {
116-
// scalafmt >= 1.6.0
117-
Method parseHoconConfig = scalafmtCls.getMethod("parseHoconConfig", String.class);
118-
119-
String configStr = new String(Files.readAllBytes(file.toPath()), StandardCharsets.UTF_8);
120-
121-
configured = parseHoconConfig.invoke(null, configStr);
122-
} catch (NoSuchMethodException e) {
123-
// scalafmt >= v0.7.0-RC1 && scalafmt < 1.6.0
124-
Method fromHocon = configCls.getMethod("fromHoconString", String.class, optionCls);
125-
Object fromHoconEmptyPath = configCls.getMethod("fromHoconString$default$2").invoke(null);
126-
127-
String configStr = new String(Files.readAllBytes(file.toPath()), StandardCharsets.UTF_8);
128-
129-
configured = fromHocon.invoke(null, configStr, fromHoconEmptyPath);
130-
}
131-
132-
config = invokeNoArg(configured, "get");
133-
}
134-
return input -> {
135-
Object resultInsideFormatted = formatMethod.invoke(null, input, config, emptyRange);
136-
return (String) formattedGet.invoke(resultInsideFormatted);
137-
};
67+
final ClassLoader classLoader = jarState.getClassLoader();
68+
final Class<?> formatterFunc = classLoader.loadClass("com.diffplug.spotless.glue.scalafmt.ScalafmtFormatterFunc");
69+
final Constructor<?> constructor = formatterFunc.getConstructor(FileSignature.class);
70+
return (FormatterFunc) constructor.newInstance(this.configSignature);
13871
}
13972
}
140-
141-
private static Object invokeNoArg(Object obj, String toInvoke) throws Exception {
142-
Class<?> clazz = obj.getClass();
143-
Method method = clazz.getMethod(toInvoke);
144-
return method.invoke(obj);
145-
}
14673
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
/*
2+
* Copyright 2022 DiffPlug
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package com.diffplug.spotless.glue.scalafmt;
17+
18+
import java.io.File;
19+
import java.lang.reflect.Method;
20+
import java.nio.charset.StandardCharsets;
21+
import java.nio.file.Files;
22+
23+
import org.scalafmt.Scalafmt;
24+
import org.scalafmt.config.ScalafmtConfig;
25+
import org.scalafmt.config.ScalafmtConfig$;
26+
27+
import com.diffplug.spotless.FileSignature;
28+
import com.diffplug.spotless.FormatterFunc;
29+
30+
import scala.collection.immutable.Set$;
31+
32+
public class ScalafmtFormatterFunc implements FormatterFunc {
33+
private final FileSignature configSignature;
34+
35+
public ScalafmtFormatterFunc(FileSignature configSignature) {
36+
this.configSignature = configSignature;
37+
}
38+
39+
@Override
40+
public String apply(String input) throws Exception {
41+
ScalafmtConfig config;
42+
if (configSignature.files().isEmpty()) {
43+
// Note that reflection is used here only because Scalafmt has a method called
44+
// default which happens to be a reserved Java keyword. The only way to call
45+
// such methods is by reflection, see
46+
// https://vlkan.com/blog/post/2015/11/20/scala-method-with-java-reserved-keyword/
47+
Method method = ScalafmtConfig$.MODULE$.getClass().getDeclaredMethod("default");
48+
config = (ScalafmtConfig) method.invoke(ScalafmtConfig$.MODULE$);
49+
} else {
50+
File file = configSignature.getOnlyFile();
51+
String configStr = new String(Files.readAllBytes(file.toPath()), StandardCharsets.UTF_8);
52+
config = Scalafmt.parseHoconConfig(configStr).get();
53+
}
54+
return Scalafmt.format(input, config, Set$.MODULE$.empty()).get();
55+
}
56+
}

testlib/src/main/resources/scala/scalafmt/basic.cleanWithCustomConf_1.1.0

Lines changed: 0 additions & 24 deletions
This file was deleted.

testlib/src/main/resources/scala/scalafmt/basic.cleanWithCustomConf_2.0.1

Lines changed: 0 additions & 25 deletions
This file was deleted.

testlib/src/main/resources/scala/scalafmt/basic.clean_1.1.0

Lines changed: 0 additions & 16 deletions
This file was deleted.

testlib/src/main/resources/scala/scalafmt/basic.clean_2.0.1

Lines changed: 0 additions & 18 deletions
This file was deleted.

testlib/src/main/resources/scala/scalafmt/basicPost2.0.0.clean

Lines changed: 0 additions & 18 deletions
This file was deleted.

testlib/src/main/resources/scala/scalafmt/basicPost2.0.0.cleanWithCustomConf

Lines changed: 0 additions & 25 deletions
This file was deleted.

0 commit comments

Comments
 (0)