diff --git a/CHANGES.md b/CHANGES.md index df7f0380b5..d845cc0b99 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -14,6 +14,8 @@ We adhere to the [keepachangelog](https://keepachangelog.com/en/1.0.0/) format ( * `formatAnnotations()` step to correct formatting of Java type annotations. It puts type annotations on the same line as the type that they qualify. Run it after a Java formatting step, such as `googleJavaFormat()`. ([#1275](https://github.com/diffplug/spotless/pull/1275)) ### Changes * Bump default `ktfmt` version to latest `0.39` -> `0.40` ([#1312](https://github.com/diffplug/spotless/pull/1312)) +* Bump default `ktlint` version to latest `0.46.1` -> `0.47.1` ([#1303](https://github.com/diffplug/spotless/pull/1303)) + * Also restored support for older versions of ktlint back to `0.31.0` ## [2.29.0] - 2022-08-23 ### Added diff --git a/lib/build.gradle b/lib/build.gradle index 3988a03bef..dfb2a38d73 100644 --- a/lib/build.gradle +++ b/lib/build.gradle @@ -1,5 +1,6 @@ plugins { id 'java-library' + id 'io.github.davidburstrom.version-compatibility' } ext.artifactId = project.artifactIdLib version = rootProject.spotlessChangelog.versionNext @@ -23,6 +24,22 @@ for (glue in NEEDS_GLUE) { } } +versionCompatibility { + adapters { + namespaces.register('KtLint') { + versions = [ + '0.31.0', + '0.32.0', + '0.34.2', + '0.45.2', + '0.46.0', + '0.47.0', + ] + targetSourceSetName = 'ktlint' + } + } +} + dependencies { compileOnly 'org.slf4j:slf4j-api:2.0.0' // zero runtime reqs is a hard requirements for spotless-lib @@ -52,6 +69,28 @@ dependencies { ktlintCompileOnly "com.pinterest.ktlint:ktlint-ruleset-experimental:$VER_KTLINT" ktlintCompileOnly "com.pinterest.ktlint:ktlint-ruleset-standard:$VER_KTLINT" + compatKtLint0Dot31Dot0CompileOnly 'org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.5.20' + compatKtLint0Dot31Dot0CompileOnly 'com.github.shyiko.ktlint:ktlint-core:0.31.0' + compatKtLint0Dot31Dot0CompileOnly 'com.github.shyiko.ktlint:ktlint-ruleset-experimental:0.31.0' + compatKtLint0Dot31Dot0CompileOnly 'com.github.shyiko.ktlint:ktlint-ruleset-standard:0.31.0' + compatKtLint0Dot32Dot0CompileOnly 'org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.5.20' + compatKtLint0Dot32Dot0CompileOnly 'com.pinterest.ktlint:ktlint-core:0.32.0' + compatKtLint0Dot32Dot0CompileOnly 'com.pinterest.ktlint:ktlint-ruleset-experimental:0.32.0' + compatKtLint0Dot32Dot0CompileOnly 'com.pinterest.ktlint:ktlint-ruleset-standard:0.32.0' + compatKtLint0Dot34Dot2CompileOnly 'org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.5.20' + compatKtLint0Dot34Dot2CompileOnly 'com.pinterest.ktlint:ktlint-core:0.34.2' + compatKtLint0Dot34Dot2CompileOnly 'com.pinterest.ktlint:ktlint-ruleset-experimental:0.34.2' + compatKtLint0Dot34Dot2CompileOnly 'com.pinterest.ktlint:ktlint-ruleset-standard:0.34.2' + compatKtLint0Dot45Dot2CompileOnly 'com.pinterest.ktlint:ktlint-core:0.45.2' + compatKtLint0Dot45Dot2CompileOnly 'com.pinterest.ktlint:ktlint-ruleset-experimental:0.45.2' + compatKtLint0Dot45Dot2CompileOnly 'com.pinterest.ktlint:ktlint-ruleset-standard:0.45.2' + compatKtLint0Dot46Dot0CompileOnly 'com.pinterest.ktlint:ktlint-core:0.46.0' + compatKtLint0Dot46Dot0CompileOnly 'com.pinterest.ktlint:ktlint-ruleset-experimental:0.46.0' + compatKtLint0Dot46Dot0CompileOnly 'com.pinterest.ktlint:ktlint-ruleset-standard:0.46.0' + compatKtLint0Dot47Dot0CompileOnly 'com.pinterest.ktlint:ktlint-core:0.47.0' + compatKtLint0Dot47Dot0CompileOnly 'com.pinterest.ktlint:ktlint-ruleset-experimental:0.47.0' + compatKtLint0Dot47Dot0CompileOnly 'com.pinterest.ktlint:ktlint-ruleset-standard:0.47.0' + String VER_SCALAFMT="3.5.9" scalafmtCompileOnly "org.scalameta:scalafmt-core_2.13:$VER_SCALAFMT" diff --git a/lib/src/compatKtLint0Dot31Dot0/java/com/diffplug/spotless/glue/ktlint/compat/KtLintCompat0Dot31Dot0Adapter.java b/lib/src/compatKtLint0Dot31Dot0/java/com/diffplug/spotless/glue/ktlint/compat/KtLintCompat0Dot31Dot0Adapter.java new file mode 100644 index 0000000000..56b8da2d20 --- /dev/null +++ b/lib/src/compatKtLint0Dot31Dot0/java/com/diffplug/spotless/glue/ktlint/compat/KtLintCompat0Dot31Dot0Adapter.java @@ -0,0 +1,63 @@ +/* + * Copyright 2022 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.glue.ktlint.compat; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import com.github.shyiko.ktlint.core.KtLint; +import com.github.shyiko.ktlint.core.LintError; +import com.github.shyiko.ktlint.core.RuleSet; +import com.github.shyiko.ktlint.ruleset.experimental.ExperimentalRuleSetProvider; +import com.github.shyiko.ktlint.ruleset.standard.StandardRuleSetProvider; + +import kotlin.Unit; +import kotlin.jvm.functions.Function2; + +public class KtLintCompat0Dot31Dot0Adapter implements KtLintCompatAdapter { + + static class FormatterCallback implements Function2 { + @Override + public Unit invoke(LintError lint, Boolean corrected) { + if (!corrected) { + KtLintCompatReporting.report(lint.getLine(), lint.getCol(), lint.getRuleId(), lint.getDetail()); + } + return null; + } + } + + @Override + public String format(final String text, final String name, final boolean isScript, + final boolean useExperimental, + final Map userData, + final Map editorConfigOverrideMap) { + final FormatterCallback formatterCallback = new FormatterCallback(); + + final List rulesets = new ArrayList<>(); + rulesets.add(new StandardRuleSetProvider().get()); + + if (useExperimental) { + rulesets.add(new ExperimentalRuleSetProvider().get()); + } + + return KtLint.INSTANCE.format( + text, + rulesets, + userData, + formatterCallback); + } +} diff --git a/lib/src/compatKtLint0Dot32Dot0/java/com/diffplug/spotless/glue/ktlint/compat/KtLintCompat0Dot32Dot0Adapter.java b/lib/src/compatKtLint0Dot32Dot0/java/com/diffplug/spotless/glue/ktlint/compat/KtLintCompat0Dot32Dot0Adapter.java new file mode 100644 index 0000000000..6f69fcc7ce --- /dev/null +++ b/lib/src/compatKtLint0Dot32Dot0/java/com/diffplug/spotless/glue/ktlint/compat/KtLintCompat0Dot32Dot0Adapter.java @@ -0,0 +1,63 @@ +/* + * Copyright 2022 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.glue.ktlint.compat; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import com.pinterest.ktlint.core.KtLint; +import com.pinterest.ktlint.core.LintError; +import com.pinterest.ktlint.core.RuleSet; +import com.pinterest.ktlint.ruleset.experimental.ExperimentalRuleSetProvider; +import com.pinterest.ktlint.ruleset.standard.StandardRuleSetProvider; + +import kotlin.Unit; +import kotlin.jvm.functions.Function2; + +public class KtLintCompat0Dot32Dot0Adapter implements KtLintCompatAdapter { + + static class FormatterCallback implements Function2 { + @Override + public Unit invoke(LintError lint, Boolean corrected) { + if (!corrected) { + KtLintCompatReporting.report(lint.getLine(), lint.getCol(), lint.getRuleId(), lint.getDetail()); + } + return null; + } + } + + @Override + public String format(final String text, final String name, final boolean isScript, + final boolean useExperimental, + final Map userData, + final Map editorConfigOverrideMap) { + final FormatterCallback formatterCallback = new FormatterCallback(); + + final List rulesets = new ArrayList<>(); + rulesets.add(new StandardRuleSetProvider().get()); + + if (useExperimental) { + rulesets.add(new ExperimentalRuleSetProvider().get()); + } + + return KtLint.INSTANCE.format( + text, + rulesets, + userData, + formatterCallback); + } +} diff --git a/lib/src/compatKtLint0Dot34Dot2/java/com/diffplug/spotless/glue/ktlint/compat/KtLintCompat0Dot34Dot2Adapter.java b/lib/src/compatKtLint0Dot34Dot2/java/com/diffplug/spotless/glue/ktlint/compat/KtLintCompat0Dot34Dot2Adapter.java new file mode 100644 index 0000000000..a3c8c8df3b --- /dev/null +++ b/lib/src/compatKtLint0Dot34Dot2/java/com/diffplug/spotless/glue/ktlint/compat/KtLintCompat0Dot34Dot2Adapter.java @@ -0,0 +1,67 @@ +/* + * Copyright 2022 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.glue.ktlint.compat; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import com.pinterest.ktlint.core.KtLint; +import com.pinterest.ktlint.core.LintError; +import com.pinterest.ktlint.core.RuleSet; +import com.pinterest.ktlint.ruleset.experimental.ExperimentalRuleSetProvider; +import com.pinterest.ktlint.ruleset.standard.StandardRuleSetProvider; + +import kotlin.Unit; +import kotlin.jvm.functions.Function2; + +public class KtLintCompat0Dot34Dot2Adapter implements KtLintCompatAdapter { + + static class FormatterCallback implements Function2 { + @Override + public Unit invoke(LintError lint, Boolean corrected) { + if (!corrected) { + KtLintCompatReporting.report(lint.getLine(), lint.getCol(), lint.getRuleId(), lint.getDetail()); + } + return null; + } + } + + @Override + public String format(final String text, final String name, final boolean isScript, + final boolean useExperimental, + final Map userData, + final Map editorConfigOverrideMap) { + final FormatterCallback formatterCallback = new FormatterCallback(); + + final List rulesets = new ArrayList<>(); + rulesets.add(new StandardRuleSetProvider().get()); + + if (useExperimental) { + rulesets.add(new ExperimentalRuleSetProvider().get()); + } + + return KtLint.INSTANCE.format(new KtLint.Params( + name, + text, + rulesets, + userData, + formatterCallback, + isScript, + null, + false)); + } +} diff --git a/lib/src/compatKtLint0Dot45Dot2/java/com/diffplug/spotless/glue/ktlint/compat/KtLintCompat0Dot45Dot2Adapter.java b/lib/src/compatKtLint0Dot45Dot2/java/com/diffplug/spotless/glue/ktlint/compat/KtLintCompat0Dot45Dot2Adapter.java new file mode 100644 index 0000000000..f7eadada3d --- /dev/null +++ b/lib/src/compatKtLint0Dot45Dot2/java/com/diffplug/spotless/glue/ktlint/compat/KtLintCompat0Dot45Dot2Adapter.java @@ -0,0 +1,118 @@ +/* + * Copyright 2022 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.glue.ktlint.compat; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import com.pinterest.ktlint.core.KtLint; +import com.pinterest.ktlint.core.LintError; +import com.pinterest.ktlint.core.RuleSet; +import com.pinterest.ktlint.core.api.DefaultEditorConfigProperties; +import com.pinterest.ktlint.core.api.EditorConfigOverride; +import com.pinterest.ktlint.core.api.UsesEditorConfigProperties; +import com.pinterest.ktlint.ruleset.experimental.ExperimentalRuleSetProvider; +import com.pinterest.ktlint.ruleset.standard.StandardRuleSetProvider; + +import kotlin.Pair; +import kotlin.Unit; +import kotlin.jvm.functions.Function2; + +public class KtLintCompat0Dot45Dot2Adapter implements KtLintCompatAdapter { + + static class FormatterCallback implements Function2 { + @Override + public Unit invoke(LintError lint, Boolean corrected) { + if (!corrected) { + KtLintCompatReporting.report(lint.getLine(), lint.getCol(), lint.getRuleId(), lint.getDetail()); + } + return null; + } + } + + @Override + public String format(final String text, final String name, final boolean isScript, + final boolean useExperimental, + final Map userData, + final Map editorConfigOverrideMap) { + final FormatterCallback formatterCallback = new FormatterCallback(); + + final List rulesets = new ArrayList<>(); + rulesets.add(new StandardRuleSetProvider().get()); + + if (useExperimental) { + rulesets.add(new ExperimentalRuleSetProvider().get()); + } + + EditorConfigOverride editorConfigOverride; + if (editorConfigOverrideMap.isEmpty()) { + editorConfigOverride = EditorConfigOverride.Companion.getEmptyEditorConfigOverride(); + } else { + editorConfigOverride = createEditorConfigOverride(rulesets, editorConfigOverrideMap); + } + + return KtLint.INSTANCE.format(new KtLint.ExperimentalParams( + name, + text, + rulesets, + userData, + formatterCallback, + isScript, + null, + false, + editorConfigOverride, + false)); + } + + /** + * Create EditorConfigOverride from user provided parameters. + * Calling this method requires KtLint 0.45.2. + */ + private static EditorConfigOverride createEditorConfigOverride(final List rulesets, Map editorConfigOverrideMap) { + // Get properties from rules in the rule sets + Stream> ruleProperties = rulesets.stream() + .flatMap(ruleSet -> Arrays.stream(ruleSet.getRules())) + .filter(rule -> rule instanceof UsesEditorConfigProperties) + .flatMap(rule -> ((UsesEditorConfigProperties) rule).getEditorConfigProperties().stream()); + + // Create a mapping of properties to their names based on rule properties and default properties + Map> supportedProperties = Stream + .concat(ruleProperties, DefaultEditorConfigProperties.INSTANCE.getDefaultEditorConfigProperties().stream()) + .distinct() + .collect(Collectors.toMap(property -> property.getType().getName(), property -> property)); + + // Create config properties based on provided property names and values + @SuppressWarnings("unchecked") + Pair, ?>[] properties = editorConfigOverrideMap.entrySet().stream() + .map(entry -> { + UsesEditorConfigProperties.EditorConfigProperty property = supportedProperties.get(entry.getKey()); + if (property != null) { + return new Pair<>(property, entry.getValue()); + } else { + return null; + } + }) + .filter(Objects::nonNull) + .toArray(Pair[]::new); + + return EditorConfigOverride.Companion.from(properties); + } +} diff --git a/lib/src/compatKtLint0Dot46Dot0/java/com/diffplug/spotless/glue/ktlint/compat/KtLintCompat0Dot46Dot0Adapter.java b/lib/src/compatKtLint0Dot46Dot0/java/com/diffplug/spotless/glue/ktlint/compat/KtLintCompat0Dot46Dot0Adapter.java new file mode 100644 index 0000000000..873b91af80 --- /dev/null +++ b/lib/src/compatKtLint0Dot46Dot0/java/com/diffplug/spotless/glue/ktlint/compat/KtLintCompat0Dot46Dot0Adapter.java @@ -0,0 +1,118 @@ +/* + * Copyright 2022 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.glue.ktlint.compat; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import com.pinterest.ktlint.core.KtLint; +import com.pinterest.ktlint.core.LintError; +import com.pinterest.ktlint.core.RuleSet; +import com.pinterest.ktlint.core.api.DefaultEditorConfigProperties; +import com.pinterest.ktlint.core.api.EditorConfigOverride; +import com.pinterest.ktlint.core.api.UsesEditorConfigProperties; +import com.pinterest.ktlint.ruleset.experimental.ExperimentalRuleSetProvider; +import com.pinterest.ktlint.ruleset.standard.StandardRuleSetProvider; + +import kotlin.Pair; +import kotlin.Unit; +import kotlin.jvm.functions.Function2; + +public class KtLintCompat0Dot46Dot0Adapter implements KtLintCompatAdapter { + + static class FormatterCallback implements Function2 { + @Override + public Unit invoke(LintError lint, Boolean corrected) { + if (!corrected) { + KtLintCompatReporting.report(lint.getLine(), lint.getCol(), lint.getRuleId(), lint.getDetail()); + } + return null; + } + } + + @Override + public String format(final String text, final String name, final boolean isScript, + final boolean useExperimental, + final Map userData, + final Map editorConfigOverrideMap) { + final FormatterCallback formatterCallback = new FormatterCallback(); + + final List rulesets = new ArrayList<>(); + rulesets.add(new StandardRuleSetProvider().get()); + + if (useExperimental) { + rulesets.add(new ExperimentalRuleSetProvider().get()); + } + + EditorConfigOverride editorConfigOverride; + if (editorConfigOverrideMap.isEmpty()) { + editorConfigOverride = EditorConfigOverride.Companion.getEmptyEditorConfigOverride(); + } else { + editorConfigOverride = createEditorConfigOverride(rulesets, editorConfigOverrideMap); + } + + return KtLint.INSTANCE.format(new KtLint.ExperimentalParams( + name, + text, + rulesets, + userData, + formatterCallback, + isScript, + null, + false, + editorConfigOverride, + false)); + } + + /** + * Create EditorConfigOverride from user provided parameters. + * Calling this method requires KtLint 0.45.2. + */ + private static EditorConfigOverride createEditorConfigOverride(final List rulesets, Map editorConfigOverrideMap) { + // Get properties from rules in the rule sets + Stream> ruleProperties = rulesets.stream() + .flatMap(ruleSet -> Arrays.stream(ruleSet.getRules())) + .filter(rule -> rule instanceof UsesEditorConfigProperties) + .flatMap(rule -> ((UsesEditorConfigProperties) rule).getEditorConfigProperties().stream()); + + // Create a mapping of properties to their names based on rule properties and default properties + Map> supportedProperties = Stream + .concat(ruleProperties, DefaultEditorConfigProperties.INSTANCE.getEditorConfigProperties().stream()) + .distinct() + .collect(Collectors.toMap(property -> property.getType().getName(), property -> property)); + + // Create config properties based on provided property names and values + @SuppressWarnings("unchecked") + Pair, ?>[] properties = editorConfigOverrideMap.entrySet().stream() + .map(entry -> { + UsesEditorConfigProperties.EditorConfigProperty property = supportedProperties.get(entry.getKey()); + if (property != null) { + return new Pair<>(property, entry.getValue()); + } else { + return null; + } + }) + .filter(Objects::nonNull) + .toArray(Pair[]::new); + + return EditorConfigOverride.Companion.from(properties); + } +} diff --git a/lib/src/compatKtLint0Dot47Dot0/java/com/diffplug/spotless/glue/ktlint/compat/KtLintCompat0Dot47Dot0Adapter.java b/lib/src/compatKtLint0Dot47Dot0/java/com/diffplug/spotless/glue/ktlint/compat/KtLintCompat0Dot47Dot0Adapter.java new file mode 100644 index 0000000000..842294a5cc --- /dev/null +++ b/lib/src/compatKtLint0Dot47Dot0/java/com/diffplug/spotless/glue/ktlint/compat/KtLintCompat0Dot47Dot0Adapter.java @@ -0,0 +1,125 @@ +/* + * Copyright 2022 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.glue.ktlint.compat; + +import static java.util.Collections.emptySet; + +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import com.pinterest.ktlint.core.KtLint; +import com.pinterest.ktlint.core.LintError; +import com.pinterest.ktlint.core.Rule; +import com.pinterest.ktlint.core.RuleProvider; +import com.pinterest.ktlint.core.api.DefaultEditorConfigProperties; +import com.pinterest.ktlint.core.api.EditorConfigDefaults; +import com.pinterest.ktlint.core.api.EditorConfigOverride; +import com.pinterest.ktlint.core.api.UsesEditorConfigProperties; +import com.pinterest.ktlint.ruleset.experimental.ExperimentalRuleSetProvider; +import com.pinterest.ktlint.ruleset.standard.StandardRuleSetProvider; + +import kotlin.Pair; +import kotlin.Unit; +import kotlin.jvm.functions.Function2; + +public class KtLintCompat0Dot47Dot0Adapter implements KtLintCompatAdapter { + + static class FormatterCallback implements Function2 { + @Override + public Unit invoke(LintError lint, Boolean corrected) { + if (!corrected) { + KtLintCompatReporting.report(lint.getLine(), lint.getCol(), lint.getRuleId(), lint.getDetail()); + } + return null; + } + } + + @Override + public String format(final String text, final String name, final boolean isScript, + final boolean useExperimental, + final Map userData, + final Map editorConfigOverrideMap) { + final FormatterCallback formatterCallback = new FormatterCallback(); + + Set allRuleProviders = new LinkedHashSet<>( + new StandardRuleSetProvider().getRuleProviders()); + if (useExperimental) { + allRuleProviders.addAll(new ExperimentalRuleSetProvider().getRuleProviders()); + } + + EditorConfigOverride editorConfigOverride; + if (editorConfigOverrideMap.isEmpty()) { + editorConfigOverride = EditorConfigOverride.Companion.getEmptyEditorConfigOverride(); + } else { + editorConfigOverride = createEditorConfigOverride(allRuleProviders.stream().map( + RuleProvider::createNewRuleInstance).collect( + Collectors.toList()), + editorConfigOverrideMap); + } + + return KtLint.INSTANCE.format(new KtLint.ExperimentalParams( + name, + text, + emptySet(), + allRuleProviders, + userData, + formatterCallback, + isScript, + null, + false, + EditorConfigDefaults.Companion.getEmptyEditorConfigDefaults(), + editorConfigOverride, + false)); + } + + /** + * Create EditorConfigOverride from user provided parameters. + * Calling this method requires KtLint 0.45.2. + */ + private static EditorConfigOverride createEditorConfigOverride(final List rules, Map editorConfigOverrideMap) { + // Get properties from rules in the rule sets + Stream> ruleProperties = rules.stream() + .filter(rule -> rule instanceof UsesEditorConfigProperties) + .flatMap(rule -> ((UsesEditorConfigProperties) rule).getEditorConfigProperties().stream()); + + // Create a mapping of properties to their names based on rule properties and default properties + Map> supportedProperties = Stream + .concat(ruleProperties, DefaultEditorConfigProperties.INSTANCE.getEditorConfigProperties().stream()) + .distinct() + .collect(Collectors.toMap(property -> property.getType().getName(), property -> property)); + + // Create config properties based on provided property names and values + @SuppressWarnings("unchecked") + Pair, ?>[] properties = editorConfigOverrideMap.entrySet().stream() + .map(entry -> { + UsesEditorConfigProperties.EditorConfigProperty property = supportedProperties.get(entry.getKey()); + if (property != null) { + return new Pair<>(property, entry.getValue()); + } else { + return null; + } + }) + .filter(Objects::nonNull) + .toArray(Pair[]::new); + + return EditorConfigOverride.Companion.from(properties); + } +} diff --git a/lib/src/compatKtLintApi/java/com/diffplug/spotless/glue/ktlint/compat/KtLintCompatAdapter.java b/lib/src/compatKtLintApi/java/com/diffplug/spotless/glue/ktlint/compat/KtLintCompatAdapter.java new file mode 100644 index 0000000000..5097cac135 --- /dev/null +++ b/lib/src/compatKtLintApi/java/com/diffplug/spotless/glue/ktlint/compat/KtLintCompatAdapter.java @@ -0,0 +1,24 @@ +/* + * Copyright 2022 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.glue.ktlint.compat; + +import java.util.Map; + +public interface KtLintCompatAdapter { + + String format(String text, String name, boolean isScript, boolean useExperimental, Map userData, + Map editorConfigOverrideMap); +} diff --git a/lib/src/compatKtLintApi/java/com/diffplug/spotless/glue/ktlint/compat/KtLintCompatReporting.java b/lib/src/compatKtLintApi/java/com/diffplug/spotless/glue/ktlint/compat/KtLintCompatReporting.java new file mode 100644 index 0000000000..7c1e0f6dd8 --- /dev/null +++ b/lib/src/compatKtLintApi/java/com/diffplug/spotless/glue/ktlint/compat/KtLintCompatReporting.java @@ -0,0 +1,25 @@ +/* + * Copyright 2022 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.glue.ktlint.compat; + +final class KtLintCompatReporting { + + private KtLintCompatReporting() {} + + static void report(int line, int column, String ruleId, String detail) { + throw new AssertionError("Error on line: " + line + ", column: " + column + "\nrule: " + ruleId + "\n" + detail); + } +} diff --git a/lib/src/ktlint/java/com/diffplug/spotless/glue/ktlint/KtlintFormatterFunc.java b/lib/src/ktlint/java/com/diffplug/spotless/glue/ktlint/KtlintFormatterFunc.java index e7af558fb5..cc64eb9d66 100644 --- a/lib/src/ktlint/java/com/diffplug/spotless/glue/ktlint/KtlintFormatterFunc.java +++ b/lib/src/ktlint/java/com/diffplug/spotless/glue/ktlint/KtlintFormatterFunc.java @@ -16,119 +16,58 @@ package com.diffplug.spotless.glue.ktlint; import java.io.File; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; import java.util.Map; -import java.util.Objects; -import java.util.stream.Collectors; -import java.util.stream.Stream; import org.jetbrains.annotations.NotNull; -import com.pinterest.ktlint.core.KtLint; -import com.pinterest.ktlint.core.KtLint.ExperimentalParams; -import com.pinterest.ktlint.core.LintError; -import com.pinterest.ktlint.core.RuleSet; -import com.pinterest.ktlint.core.api.DefaultEditorConfigProperties; -import com.pinterest.ktlint.core.api.EditorConfigOverride; -import com.pinterest.ktlint.core.api.UsesEditorConfigProperties; -import com.pinterest.ktlint.ruleset.experimental.ExperimentalRuleSetProvider; -import com.pinterest.ktlint.ruleset.standard.StandardRuleSetProvider; - import com.diffplug.spotless.FormatterFunc; - -import kotlin.Pair; -import kotlin.Unit; -import kotlin.jvm.functions.Function2; +import com.diffplug.spotless.glue.ktlint.compat.KtLintCompat0Dot31Dot0Adapter; +import com.diffplug.spotless.glue.ktlint.compat.KtLintCompat0Dot32Dot0Adapter; +import com.diffplug.spotless.glue.ktlint.compat.KtLintCompat0Dot34Dot2Adapter; +import com.diffplug.spotless.glue.ktlint.compat.KtLintCompat0Dot45Dot2Adapter; +import com.diffplug.spotless.glue.ktlint.compat.KtLintCompat0Dot46Dot0Adapter; +import com.diffplug.spotless.glue.ktlint.compat.KtLintCompat0Dot47Dot0Adapter; +import com.diffplug.spotless.glue.ktlint.compat.KtLintCompatAdapter; public class KtlintFormatterFunc implements FormatterFunc.NeedsFile { - private final List rulesets; private final Map userData; - private final Function2 formatterCallback; private final boolean isScript; @NotNull - private final EditorConfigOverride editorConfigOverride; + private final KtLintCompatAdapter adapter; + private final boolean useExperimental; + private final Map editorConfigOverrideMap; - /** - * Non-empty editorConfigOverrideMap requires KtLint 0.45.2. - */ - public KtlintFormatterFunc(boolean isScript, boolean useExperimental, Map userData, + public KtlintFormatterFunc(String version, boolean isScript, boolean useExperimental, Map userData, Map editorConfigOverrideMap) { - rulesets = new ArrayList<>(); - rulesets.add(new StandardRuleSetProvider().get()); - - if (useExperimental) { - rulesets.add(new ExperimentalRuleSetProvider().get()); + int minorVersion = Integer.parseInt(version.split("\\.")[1]); + if (minorVersion >= 47) { + // rename RuleSet to RuleProvider + this.adapter = new KtLintCompat0Dot47Dot0Adapter(); + } else if (minorVersion >= 46) { + // DefaultEditorConfigProperties.INSTANCE.getDefaultEditorConfigProperties() renamed to .getEditorConfigProperties() + this.adapter = new KtLintCompat0Dot46Dot0Adapter(); + } else if (version.equals("0.45.2")) { + // add editorConfigOverride + this.adapter = new KtLintCompat0Dot45Dot2Adapter(); + } else if (minorVersion >= 34) { + // KtLint.INSTANCE.format() now needs more parameters + this.adapter = new KtLintCompat0Dot34Dot2Adapter(); + } else if (minorVersion >= 32) { + // rename packages from `com.github.shyiko` to `com.pinterest` + this.adapter = new KtLintCompat0Dot32Dot0Adapter(); + } else { + // the OG + this.adapter = new KtLintCompat0Dot31Dot0Adapter(); } + this.useExperimental = useExperimental; + this.editorConfigOverrideMap = editorConfigOverrideMap; this.userData = userData; - formatterCallback = new FormatterCallback(); this.isScript = isScript; - - if (editorConfigOverrideMap.isEmpty()) { - this.editorConfigOverride = EditorConfigOverride.Companion.getEmptyEditorConfigOverride(); - } else { - this.editorConfigOverride = createEditorConfigOverride(editorConfigOverrideMap); - } - } - - /** - * Create EditorConfigOverride from user provided parameters. - * Calling this method requires KtLint 0.45.2. - */ - private EditorConfigOverride createEditorConfigOverride(Map editorConfigOverrideMap) { - // Get properties from rules in the rule sets - Stream> ruleProperties = rulesets.stream() - .flatMap(ruleSet -> Arrays.stream(ruleSet.getRules())) - .filter(rule -> rule instanceof UsesEditorConfigProperties) - .flatMap(rule -> ((UsesEditorConfigProperties) rule).getEditorConfigProperties().stream()); - - // Create a mapping of properties to their names based on rule properties and default properties - Map> supportedProperties = Stream - .concat(ruleProperties, DefaultEditorConfigProperties.INSTANCE.getEditorConfigProperties().stream()) - .distinct() - .collect(Collectors.toMap(property -> property.getType().getName(), property -> property)); - - // Create config properties based on provided property names and values - @SuppressWarnings("unchecked") - Pair, ?>[] properties = editorConfigOverrideMap.entrySet().stream() - .map(entry -> { - UsesEditorConfigProperties.EditorConfigProperty property = supportedProperties.get(entry.getKey()); - if (property != null) { - return new Pair<>(property, entry.getValue()); - } else { - return null; - } - }) - .filter(Objects::nonNull) - .toArray(Pair[]::new); - - return EditorConfigOverride.Companion.from(properties); - } - - static class FormatterCallback implements Function2 { - @Override - public Unit invoke(LintError lint, Boolean corrected) { - if (!corrected) { - throw new AssertionError("Error on line: " + lint.getLine() + ", column: " + lint.getCol() + "\nrule: " + lint.getRuleId() + "\n" + lint.getDetail()); - } - return null; - } } @Override public String applyWithFile(String unix, File file) throws Exception { - return KtLint.INSTANCE.format(new ExperimentalParams( - file.getName(), - unix, - rulesets, - userData, - formatterCallback, - isScript, - null, - false, - editorConfigOverride, - false)); + return adapter.format(unix, file.getName(), isScript, useExperimental, userData, editorConfigOverrideMap); } } diff --git a/lib/src/main/java/com/diffplug/spotless/kotlin/KtLintStep.java b/lib/src/main/java/com/diffplug/spotless/kotlin/KtLintStep.java index f57ef1e997..5627913e4a 100644 --- a/lib/src/main/java/com/diffplug/spotless/kotlin/KtLintStep.java +++ b/lib/src/main/java/com/diffplug/spotless/kotlin/KtLintStep.java @@ -33,9 +33,11 @@ public class KtLintStep { // prevent direct instantiation private KtLintStep() {} - private static final String DEFAULT_VERSION = "0.46.1"; + private static final String DEFAULT_VERSION = "0.47.1"; static final String NAME = "ktlint"; + static final String PACKAGE_PRE_0_32 = "com.github.shyiko"; static final String PACKAGE = "com.pinterest"; + static final String MAVEN_COORDINATE_PRE_0_32 = PACKAGE_PRE_0_32 + ":ktlint:"; static final String MAVEN_COORDINATE = PACKAGE + ":ktlint:"; public static FormatterStep create(Provisioner provisioner) { @@ -83,25 +85,34 @@ static final class State implements Serializable { private final boolean useExperimental; private final TreeMap userData; private final TreeMap editorConfigOverride; + private final String version; State(String version, Provisioner provisioner, boolean isScript, boolean useExperimental, Map userData, Map editorConfigOverride) throws IOException { + this.version = version; - if (BadSemver.version(version) < BadSemver.version(0, 46, 0)) { - throw new IllegalStateException("KtLint versions < 0.46.0 not supported!"); + String coordinate; + if (BadSemver.version(version) < BadSemver.version(0, 32)) { + coordinate = MAVEN_COORDINATE_PRE_0_32; + } else { + coordinate = MAVEN_COORDINATE; + } + if (BadSemver.version(version) < BadSemver.version(0, 31, 0)) { + throw new IllegalStateException("KtLint versions < 0.31.0 not supported!"); } - this.useExperimental = useExperimental; this.userData = new TreeMap<>(userData); this.editorConfigOverride = new TreeMap<>(editorConfigOverride); - this.jarState = JarState.from(MAVEN_COORDINATE + version, provisioner); + this.jarState = JarState.from(coordinate + version, provisioner); this.isScript = isScript; } FormatterFunc createFormat() throws Exception { - Class formatterFunc = jarState.getClassLoader().loadClass("com.diffplug.spotless.glue.ktlint.KtlintFormatterFunc"); - Constructor constructor = formatterFunc.getConstructor(boolean.class, boolean.class, Map.class, Map.class); - return (FormatterFunc.NeedsFile) constructor.newInstance(isScript, useExperimental, userData, editorConfigOverride); + final ClassLoader classLoader = jarState.getClassLoader(); + Class formatterFunc = classLoader.loadClass("com.diffplug.spotless.glue.ktlint.KtlintFormatterFunc"); + Constructor constructor = formatterFunc.getConstructor( + String.class, boolean.class, boolean.class, Map.class, Map.class); + return (FormatterFunc.NeedsFile) constructor.newInstance(version, isScript, useExperimental, userData, editorConfigOverride); } } } diff --git a/plugin-gradle/CHANGES.md b/plugin-gradle/CHANGES.md index aed5f04ec5..78a9b46693 100644 --- a/plugin-gradle/CHANGES.md +++ b/plugin-gradle/CHANGES.md @@ -7,6 +7,8 @@ We adhere to the [keepachangelog](https://keepachangelog.com/en/1.0.0/) format ( * `formatAnnotations()` step to correct formatting of Java type annotations. It puts type annotations on the same line as the type that they qualify. Run it after a Java formatting step, such as `googleJavaFormat()`. ([#1275](https://github.com/diffplug/spotless/pull/1275)) ### Changes * Bump default `ktfmt` version to latest `0.39` -> `0.40` ([#1312](https://github.com/diffplug/spotless/pull/1312)) +* Bump default `ktlint` version to latest `0.46.1` -> `0.47.1` ([#1303](https://github.com/diffplug/spotless/pull/1303)) + * Also restored support for older versions of ktlint back to `0.31.0` ## [6.10.0] - 2022-08-23 ### Added diff --git a/plugin-maven/CHANGES.md b/plugin-maven/CHANGES.md index aa54842d27..5d47dc4f4f 100644 --- a/plugin-maven/CHANGES.md +++ b/plugin-maven/CHANGES.md @@ -7,6 +7,8 @@ We adhere to the [keepachangelog](https://keepachangelog.com/en/1.0.0/) format ( * `formatAnnotations` step to correct formatting of Java type annotations. It puts type annotations on the same line as the type that they qualify. Run it after a Java formatting step, such as `googleJavaFormat`. ([#1275](https://github.com/diffplug/spotless/pull/1275)) ### Changes * Bump default `ktfmt` version to latest `0.39` -> `0.40` ([#1312](https://github.com/diffplug/spotless/pull/1312)) +* Bump default `ktlint` version to latest `0.46.1` -> `0.47.1` ([#1303](https://github.com/diffplug/spotless/pull/1303)) + * Also restored support for older versions of ktlint back to `0.31.0` ## [2.25.0] - 2022-08-23 ### Added diff --git a/settings.gradle b/settings.gradle index 1706b5fa7f..91de4f9e48 100644 --- a/settings.gradle +++ b/settings.gradle @@ -15,6 +15,8 @@ pluginManagement { id 'org.gradle.test-retry' version '1.4.1' // https://github.com/radarsh/gradle-test-logger-plugin/blob/develop/CHANGELOG.md id 'com.adarshr.test-logger' version '3.2.0' + // https://github.com/davidburstrom/version-compatibility-gradle-plugin/tags + id 'io.github.davidburstrom.version-compatibility' version '0.1.0' } } plugins { @@ -26,6 +28,7 @@ plugins { id 'com.diffplug.p2.asmaven' apply false id 'org.gradle.test-retry' apply false id 'com.adarshr.test-logger' apply false + id 'io.github.davidburstrom.version-compatibility' apply false } if (System.env['CI'] != null) { // use the remote buildcache on all CI builds diff --git a/testlib/src/test/java/com/diffplug/spotless/kotlin/KtLintStepTest.java b/testlib/src/test/java/com/diffplug/spotless/kotlin/KtLintStepTest.java index d9e6042c23..71a0979422 100644 --- a/testlib/src/test/java/com/diffplug/spotless/kotlin/KtLintStepTest.java +++ b/testlib/src/test/java/com/diffplug/spotless/kotlin/KtLintStepTest.java @@ -15,6 +15,7 @@ */ package com.diffplug.spotless.kotlin; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import com.diffplug.spotless.FormatterStep; @@ -38,7 +39,74 @@ void behavior() throws Exception { } @Test - void worksPre0_46_1() throws Exception { + void worksShyiko() throws Exception { + FormatterStep step = KtLintStep.create("0.31.0", TestProvisioner.mavenCentral()); + StepHarness.forStep(step) + .testResource("kotlin/ktlint/basic.dirty", "kotlin/ktlint/basic.clean") + .testResourceException("kotlin/ktlint/unsolvable.dirty", assertion -> { + assertion.isInstanceOf(AssertionError.class); + assertion.hasMessage("Error on line: 1, column: 1\n" + + "rule: no-wildcard-imports\n" + + "Wildcard import"); + }); + } + + // Regression test to ensure it works on the version it switched to Pinterest (version 0.32.0) + // but before 0.34. + // https://github.com/diffplug/spotless/issues/419 + @Test + void worksPinterestAndPre034() throws Exception { + FormatterStep step = KtLintStep.create("0.32.0", TestProvisioner.mavenCentral()); + StepHarness.forStep(step) + .testResource("kotlin/ktlint/basic.dirty", "kotlin/ktlint/basic.clean") + .testResourceException("kotlin/ktlint/unsolvable.dirty", assertion -> { + assertion.isInstanceOf(AssertionError.class); + assertion.hasMessage("Error on line: 1, column: 1\n" + + "rule: no-wildcard-imports\n" + + "Wildcard import"); + }); + } + + // Regression test to handle alpha and 1.x version numbers + // https://github.com/diffplug/spotless/issues/668 + @Test + void worksAlpha1() throws Exception { + FormatterStep step = KtLintStep.create("0.38.0-alpha01", TestProvisioner.mavenCentral()); + StepHarness.forStep(step) + .testResource("kotlin/ktlint/basic.dirty", "kotlin/ktlint/basic.clean"); + } + + @Test + void works0_44_0() throws Exception { + FormatterStep step = KtLintStep.create("0.44.0", TestProvisioner.mavenCentral()); + StepHarness.forStep(step) + .testResource("kotlin/ktlint/basic.dirty", "kotlin/ktlint/basic.clean"); + } + + @Disabled("https://github.com/pinterest/ktlint/issues/1421") + @Test + void works0_45_0() throws Exception { + FormatterStep step = KtLintStep.create("0.45.0", TestProvisioner.mavenCentral()); + StepHarness.forStep(step) + .testResource("kotlin/ktlint/basic.dirty", "kotlin/ktlint/basic.clean"); + } + + @Test + void works0_45_1() throws Exception { + FormatterStep step = KtLintStep.create("0.45.1", TestProvisioner.mavenCentral()); + StepHarness.forStep(step) + .testResource("kotlin/ktlint/basic.dirty", "kotlin/ktlint/basic.clean"); + } + + @Test + void works0_45_2() throws Exception { + FormatterStep step = KtLintStep.create("0.45.2", TestProvisioner.mavenCentral()); + StepHarness.forStep(step) + .testResource("kotlin/ktlint/basic.dirty", "kotlin/ktlint/basic.clean"); + } + + @Test + void works0_46_0() throws Exception { FormatterStep step = KtLintStep.create("0.46.0", TestProvisioner.mavenCentral()); StepHarness.forStep(step) .testResource("kotlin/ktlint/basic.dirty", "kotlin/ktlint/basic.clean") @@ -50,17 +118,43 @@ void worksPre0_46_1() throws Exception { }); } + @Test + void works0_47_0() throws Exception { + FormatterStep step = KtLintStep.create("0.47.0", TestProvisioner.mavenCentral()); + StepHarness.forStep(step) + .testResource("kotlin/ktlint/basic.dirty", "kotlin/ktlint/basic.clean") + .testResourceException("kotlin/ktlint/unsolvable.dirty", assertion -> { + assertion.isInstanceOf(AssertionError.class); + assertion.hasMessage("Error on line: 1, column: 1\n" + + "rule: no-wildcard-imports\n" + + "Wildcard import"); + }); + } + + @Test + void works0_47_1() throws Exception { + FormatterStep step = KtLintStep.create("0.47.1", TestProvisioner.mavenCentral()); + StepHarness.forStep(step) + .testResource("kotlin/ktlint/basic.dirty", "kotlin/ktlint/basic.clean") + .testResourceException("kotlin/ktlint/unsolvable.dirty", assertion -> { + assertion.isInstanceOf(AssertionError.class); + assertion.hasMessage("Error on line: 1, column: 1\n" + + "rule: no-wildcard-imports\n" + + "Wildcard import"); + }); + } + @Test void equality() throws Exception { new SerializableEqualityTester() { - String version = "0.46.0"; + String version = "0.32.0"; @Override protected void setupTest(API api) { // same version == same api.areDifferentThan(); // change the version, and it's different - version = "0.46.1"; + version = "0.38.0-alpha01"; api.areDifferentThan(); }