Skip to content

Commit 24b8d3a

Browse files
authored
Add support for setting scala version for scalafmt, and also convert to compile-time (#1283 fixes #1273 helps with #524)
2 parents f617d0e + e9d3150 commit 24b8d3a

File tree

18 files changed

+122
-257
lines changed

18 files changed

+122
-257
lines changed

CHANGES.md

+4
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,12 @@ 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+
### Added
14+
* `scalafmt` integration now has a configuration option `majorScalaVersion` that allows you to configure the Scala version that gets resolved from the maven artifact ([#1283](https://github.com/diffplug/spotless/pull/1283))
15+
* Converted `scalafmt` integration to use a compile-only source set (fixes [#524](https://github.com/diffplug/spotless/issues/524))
1316
### Changes
1417
* Add the `ktlint` rule in error messages when `ktlint` fails to apply a fix ([#1279](https://github.com/diffplug/spotless/pull/1279))
18+
* Bump default `scalafmt` to latest `3.0.8` -> `3.5.9` (removed support for pre-`3.0.0`) ([#1283](https://github.com/diffplug/spotless/pull/1283))
1519

1620
## [2.28.1] - 2022-08-10
1721
### Fixed

lib/build.gradle

+5-1
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

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,108 +34,52 @@ 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";
38+
39+
private static final String DEFAULT_SCALA_MAJOR_VERSION = "2.13";
4540
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:";
48-
static final String MAVEN_COORDINATE = "org.scalameta:scalafmt-core_2.13:";
41+
static final String MAVEN_COORDINATE = "org.scalameta:scalafmt-core_";
4942

5043
public static FormatterStep create(Provisioner provisioner) {
51-
return create(defaultVersion(), provisioner, null);
44+
return create(defaultVersion(), defaultScalaMajorVersion(), provisioner, null);
5245
}
5346

5447
public static FormatterStep create(String version, Provisioner provisioner, @Nullable File configFile) {
55-
Objects.requireNonNull(version, "version");
56-
Objects.requireNonNull(provisioner, "provisioner");
48+
return create(version, defaultScalaMajorVersion(), provisioner, configFile);
49+
}
50+
51+
public static FormatterStep create(String version, @Nullable String scalaMajorVersion, Provisioner provisioner, @Nullable File configFile) {
52+
String finalScalaMajorVersion = scalaMajorVersion == null ? DEFAULT_SCALA_MAJOR_VERSION : scalaMajorVersion;
53+
5754
return FormatterStep.createLazy(NAME,
58-
() -> new State(version, provisioner, configFile),
55+
() -> new State(JarState.from(MAVEN_COORDINATE + finalScalaMajorVersion + ":" + version, provisioner), configFile),
5956
State::createFormat);
6057
}
6158

6259
public static String defaultVersion() {
6360
return DEFAULT_VERSION;
6461
}
6562

63+
public static String defaultScalaMajorVersion() {
64+
return DEFAULT_SCALA_MAJOR_VERSION;
65+
}
66+
6667
static final class State implements Serializable {
6768
private static final long serialVersionUID = 1L;
6869

6970
final JarState jarState;
7071
final FileSignature configSignature;
7172

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);
73+
State(JarState jarState, @Nullable File configFile) throws IOException {
74+
this.jarState = jarState;
8475
this.configSignature = FileSignature.signAsList(configFile == null ? Collections.emptySet() : Collections.singleton(configFile));
8576
}
8677

8778
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-
};
79+
final ClassLoader classLoader = jarState.getClassLoader();
80+
final Class<?> formatterFunc = classLoader.loadClass("com.diffplug.spotless.glue.scalafmt.ScalafmtFormatterFunc");
81+
final Constructor<?> constructor = formatterFunc.getConstructor(FileSignature.class);
82+
return (FormatterFunc) constructor.newInstance(this.configSignature);
13883
}
13984
}
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-
}
14685
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
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 ScalafmtConfig config;
34+
35+
public ScalafmtFormatterFunc(FileSignature configSignature) throws Exception {
36+
if (configSignature.files().isEmpty()) {
37+
// Note that reflection is used here only because Scalafmt has a method called
38+
// default which happens to be a reserved Java keyword. The only way to call
39+
// such methods is by reflection, see
40+
// https://vlkan.com/blog/post/2015/11/20/scala-method-with-java-reserved-keyword/
41+
Method method = ScalafmtConfig$.MODULE$.getClass().getDeclaredMethod("default");
42+
config = (ScalafmtConfig) method.invoke(ScalafmtConfig$.MODULE$);
43+
} else {
44+
File file = configSignature.getOnlyFile();
45+
String configStr = new String(Files.readAllBytes(file.toPath()), StandardCharsets.UTF_8);
46+
config = Scalafmt.parseHoconConfig(configStr).get();
47+
}
48+
}
49+
50+
@Override
51+
public String apply(String input) {
52+
return Scalafmt.format(input, config, Set$.MODULE$.empty()).get();
53+
}
54+
}

plugin-gradle/CHANGES.md

+3
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,11 @@
33
We adhere to the [keepachangelog](https://keepachangelog.com/en/1.0.0/) format (starting after version `3.27.0`).
44

55
## [Unreleased]
6+
### Added
7+
* `scalafmt` integration now has a configuration option `majorScalaVersion` that allows you to configure the Scala version that gets resolved from the maven artifact ([#1283](https://github.com/diffplug/spotless/pull/1283))
68
### Changes
79
* Add the `ktlint` rule in error messages when `ktlint` fails to apply a fix ([#1279](https://github.com/diffplug/spotless/pull/1279))
10+
* Bump default `scalafmt` to latest `3.0.8` -> `3.5.9` (removed support for pre-`3.0.0`) ([#1283](https://github.com/diffplug/spotless/pull/1283))
811

912
## [6.9.1] - 2022-08-10
1013
### Fixed

plugin-gradle/README.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -388,8 +388,8 @@ spotless {
388388
```gradle
389389
spotless {
390390
scala {
391-
// version and configFile are both optional
392-
scalafmt('2.6.1').configFile('scalafmt.conf')
391+
// version and configFile, majorScalaVersion are all optional
392+
scalafmt('3.5.9').configFile('scalafmt.conf').majorScalaVersion('2.13')
393393
```
394394

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

plugin-gradle/src/main/java/com/diffplug/gradle/spotless/ScalaExtension.java

+12-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2016-2020 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.
@@ -48,21 +48,30 @@ public ScalaFmtConfig scalafmt(String version) {
4848
public class ScalaFmtConfig {
4949
final String version;
5050
@Nullable
51+
String scalaMajorVersion;
52+
@Nullable
5153
Object configFile;
5254

5355
ScalaFmtConfig(String version) {
5456
this.version = Objects.requireNonNull(version);
5557
addStep(createStep());
5658
}
5759

58-
public void configFile(Object configFile) {
60+
public ScalaFmtConfig configFile(Object configFile) {
5961
this.configFile = Objects.requireNonNull(configFile);
6062
replaceStep(createStep());
63+
return this;
64+
}
65+
66+
public ScalaFmtConfig scalaMajorVersion(String scalaMajorVersion) {
67+
this.scalaMajorVersion = Objects.requireNonNull(scalaMajorVersion);
68+
replaceStep(createStep());
69+
return this;
6170
}
6271

6372
private FormatterStep createStep() {
6473
File resolvedConfigFile = configFile == null ? null : getProject().file(configFile);
65-
return ScalaFmtStep.create(version, provisioner(), resolvedConfigFile);
74+
return ScalaFmtStep.create(version, scalaMajorVersion, provisioner(), resolvedConfigFile);
6675
}
6776
}
6877

plugin-maven/CHANGES.md

+3
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,11 @@
33
We adhere to the [keepachangelog](https://keepachangelog.com/en/1.0.0/) format (starting after version `1.27.0`).
44

55
## [Unreleased]
6+
### Added
7+
* `scalafmt` integration now has a configuration option `majorScalaVersion` that allows you to configure the Scala version that gets resolved from the maven artifact ([#1283](https://github.com/diffplug/spotless/pull/1283))
68
### Changes
79
* Add the `ktlint` rule in error messages when `ktlint` fails to apply a fix ([#1279](https://github.com/diffplug/spotless/pull/1279))
10+
* Bump default `scalafmt` to latest `3.0.8` -> `3.5.9` (removed support for pre-`3.0.0`) ([#1283](https://github.com/diffplug/spotless/pull/1283))
811

912
## [2.24.1] - 2022-08-10
1013
### Fixed

plugin-maven/README.md

+2-1
Original file line numberDiff line numberDiff line change
@@ -393,8 +393,9 @@ Groovy-Eclipse formatting errors/warnings lead per default to a build failure. T
393393

394394
```xml
395395
<scalafmt>
396-
<version>2.0.1</version> <!-- optional -->
396+
<version>3.5.9</version> <!-- optional -->
397397
<file>${project.basedir}/scalafmt.conf</file> <!-- optional -->
398+
<majorScalaVersion>2.13</majorScalaVersion> <!-- optional -->
398399
</scalafmt>
399400
```
400401

plugin-maven/src/main/java/com/diffplug/spotless/maven/scala/Scalafmt.java

+6-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2016 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.
@@ -32,10 +32,14 @@ public class Scalafmt implements FormatterStepFactory {
3232
@Parameter
3333
private String version;
3434

35+
@Parameter
36+
private String scalaMajorVersion;
37+
3538
@Override
3639
public FormatterStep newFormatterStep(FormatterStepConfig config) {
3740
String scalafmtVersion = version != null ? version : ScalaFmtStep.defaultVersion();
41+
String scalafmtScalaMajorVersion = scalaMajorVersion != null ? scalaMajorVersion : ScalaFmtStep.defaultScalaMajorVersion();
3842
File configFile = config.getFileLocator().locateFile(file);
39-
return ScalaFmtStep.create(scalafmtVersion, config.getProvisioner(), configFile);
43+
return ScalaFmtStep.create(scalafmtVersion, scalafmtScalaMajorVersion, config.getProvisioner(), configFile);
4044
}
4145
}

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

-24
This file was deleted.

0 commit comments

Comments
 (0)