diff --git a/CHANGES.md b/CHANGES.md index a15ee10103..998c373429 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -10,6 +10,7 @@ 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 `wildcardsLast` option for Java `ImportOrderStep` ([#954](https://github.com/diffplug/spotless/pull/954)) ## [2.18.0] - 2021-09-30 ### Added diff --git a/lib/src/main/java/com/diffplug/spotless/java/ImportOrderStep.java b/lib/src/main/java/com/diffplug/spotless/java/ImportOrderStep.java index f08ea42b5c..c6e812c74e 100644 --- a/lib/src/main/java/com/diffplug/spotless/java/ImportOrderStep.java +++ b/lib/src/main/java/com/diffplug/spotless/java/ImportOrderStep.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2020 DiffPlug + * Copyright 2016-2021 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -36,6 +36,8 @@ import com.diffplug.spotless.FormatterStep; public final class ImportOrderStep { + private static final boolean WILDCARDS_LAST_DEFAULT = false; + private final String lineFormat; public static ImportOrderStep forGroovy() { @@ -51,19 +53,27 @@ private ImportOrderStep(String lineFormat) { } public FormatterStep createFrom(String... importOrder) { + return createFrom(WILDCARDS_LAST_DEFAULT, importOrder); + } + + public FormatterStep createFrom(boolean wildcardsLast, String... importOrder) { // defensive copying and null checking List importOrderList = requireElementsNonNull(Arrays.asList(importOrder)); - return createFrom(() -> importOrderList); + return createFrom(wildcardsLast, () -> importOrderList); } public FormatterStep createFrom(File importsFile) { + return createFrom(WILDCARDS_LAST_DEFAULT, importsFile); + } + + public FormatterStep createFrom(boolean wildcardsLast, File importsFile) { Objects.requireNonNull(importsFile); - return createFrom(() -> getImportOrder(importsFile)); + return createFrom(wildcardsLast, () -> getImportOrder(importsFile)); } - private FormatterStep createFrom(Supplier> importOrder) { + private FormatterStep createFrom(boolean wildcardsLast, Supplier> importOrder) { return FormatterStep.createLazy("importOrder", - () -> new State(importOrder.get(), lineFormat), + () -> new State(importOrder.get(), lineFormat, wildcardsLast), State::toFormatter); } @@ -92,14 +102,16 @@ private static final class State implements Serializable { private final List importOrder; private final String lineFormat; + private final boolean wildcardsLast; - State(List importOrder, String lineFormat) { + State(List importOrder, String lineFormat, boolean wildcardsLast) { this.importOrder = importOrder; this.lineFormat = lineFormat; + this.wildcardsLast = wildcardsLast; } FormatterFunc toFormatter() { - return raw -> new ImportSorter(importOrder).format(raw, lineFormat); + return raw -> new ImportSorter(importOrder, wildcardsLast).format(raw, lineFormat); } } } diff --git a/lib/src/main/java/com/diffplug/spotless/java/ImportSorter.java b/lib/src/main/java/com/diffplug/spotless/java/ImportSorter.java index b522f748cc..edfc948487 100644 --- a/lib/src/main/java/com/diffplug/spotless/java/ImportSorter.java +++ b/lib/src/main/java/com/diffplug/spotless/java/ImportSorter.java @@ -1,5 +1,5 @@ /* - * Copyright 2016 DiffPlug + * Copyright 2016-2021 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -29,9 +29,11 @@ final class ImportSorter { static final String N = "\n"; private final List importsOrder; + private final boolean wildcardsLast; - ImportSorter(List importsOrder) { + ImportSorter(List importsOrder, boolean wildcardsLast) { this.importsOrder = new ArrayList<>(importsOrder); + this.wildcardsLast = wildcardsLast; } String format(String raw, String lineFormat) { @@ -79,7 +81,7 @@ String format(String raw, String lineFormat) { } scanner.close(); - List sortedImports = ImportSorterImpl.sort(imports, importsOrder, lineFormat); + List sortedImports = ImportSorterImpl.sort(imports, importsOrder, wildcardsLast, lineFormat); return applyImportsToDocument(raw, firstImportLine, lastImportLine, sortedImports); } diff --git a/lib/src/main/java/com/diffplug/spotless/java/ImportSorterImpl.java b/lib/src/main/java/com/diffplug/spotless/java/ImportSorterImpl.java index 7de2c62fa6..5c4a33ede9 100644 --- a/lib/src/main/java/com/diffplug/spotless/java/ImportSorterImpl.java +++ b/lib/src/main/java/com/diffplug/spotless/java/ImportSorterImpl.java @@ -1,5 +1,5 @@ /* - * Copyright 2016 DiffPlug + * Copyright 2016-2021 DiffPlug * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,13 +15,7 @@ */ package com.diffplug.spotless.java; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; +import java.util.*; import javax.annotation.Nullable; @@ -34,9 +28,10 @@ final class ImportSorterImpl { private final Map> matchingImports = new HashMap<>(); private final List notMatching = new ArrayList<>(); private final Set allImportOrderItems = new HashSet<>(); + private final Comparator ordering; - static List sort(List imports, List importsOrder, String lineFormat) { - ImportSorterImpl importsSorter = new ImportSorterImpl(importsOrder); + static List sort(List imports, List importsOrder, boolean wildcardsLast, String lineFormat) { + ImportSorterImpl importsSorter = new ImportSorterImpl(importsOrder, wildcardsLast); return importsSorter.sort(imports, lineFormat); } @@ -49,11 +44,12 @@ private List sort(List imports, String lineFormat) { return getResult(lineFormat); } - private ImportSorterImpl(List importOrder) { + private ImportSorterImpl(List importOrder, boolean wildcardsLast) { List importOrderCopy = new ArrayList<>(importOrder); normalizeStaticOrderItems(importOrderCopy); putStaticItemIfNotExists(importOrderCopy); template.addAll(importOrderCopy); + ordering = new OrderingComparator(wildcardsLast); this.allImportOrderItems.addAll(importOrderCopy); } @@ -119,7 +115,7 @@ private void filterMatchingImports(List imports) { * not matching means it does not match any order item, so it will be appended before or after order items */ private void mergeNotMatchingItems(boolean staticItems) { - Collections.sort(notMatching); + sort(notMatching); int firstIndexOfOrderItem = getFirstIndexOfOrderItem(notMatching, staticItems); int indexOfOrderItem = 0; @@ -192,7 +188,7 @@ private void mergeMatchingItems() { continue; } List matchingItems = new ArrayList<>(strings); - Collections.sort(matchingItems); + sort(matchingItems); // replace order item by matching import statements // this is a mess and it is only a luck that it works :-] @@ -218,6 +214,10 @@ private void mergeMatchingItems() { } } + private void sort(List items) { + items.sort(ordering); + } + private List getResult(String lineFormat) { List strings = new ArrayList<>(); @@ -256,4 +256,28 @@ private List getResult(String lineFormat) { return null; } + private static class OrderingComparator implements Comparator { + private final boolean wildcardsLast; + + private OrderingComparator(boolean wildcardsLast) { + this.wildcardsLast = wildcardsLast; + } + + @Override + public int compare(String string1, String string2) { + int string1WildcardIndex = string1.indexOf('*'); + int string2WildcardIndex = string2.indexOf('*'); + boolean string1IsWildcard = string1WildcardIndex >= 0; + boolean string2IsWildcard = string2WildcardIndex >= 0; + if (string1IsWildcard == string2IsWildcard) { + return string1.compareTo(string2); + } + int prefixLength = string1IsWildcard ? string1WildcardIndex : string2WildcardIndex; + boolean samePrefix = string1.regionMatches(0, string2, 0, prefixLength); + if (!samePrefix) { + return string1.compareTo(string2); + } + return (string1IsWildcard == wildcardsLast) ? 1 : -1; + } + } } diff --git a/plugin-gradle/CHANGES.md b/plugin-gradle/CHANGES.md index 866e5a6755..c9cb01de5d 100644 --- a/plugin-gradle/CHANGES.md +++ b/plugin-gradle/CHANGES.md @@ -3,6 +3,7 @@ We adhere to the [keepachangelog](https://keepachangelog.com/en/1.0.0/) format (starting after version `3.27.0`). ## [Unreleased] +* Added `wildcardsLast()` option for Java `importOrder` ([#954](https://github.com/diffplug/spotless/pull/954)) ## [5.15.2] - 2021-09-27 ### Changed diff --git a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/JavaExtension.java b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/JavaExtension.java index 2965895da5..eef0987c4e 100644 --- a/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/JavaExtension.java +++ b/plugin-gradle/src/main/java/com/diffplug/gradle/spotless/JavaExtension.java @@ -17,6 +17,7 @@ import static com.diffplug.gradle.spotless.PluginGradlePreconditions.requireElementsNonNull; +import java.io.File; import java.util.Objects; import javax.inject.Inject; @@ -57,13 +58,51 @@ public LicenseHeaderConfig licenseHeaderFile(Object licenseHeaderFile) { return licenseHeaderFile(licenseHeaderFile, LICENSE_HEADER_DELIMITER); } - public void importOrder(String... importOrder) { - addStep(ImportOrderStep.forJava().createFrom(importOrder)); + public ImportOrderConfig importOrder(String... importOrder) { + return new ImportOrderConfig(importOrder); } - public void importOrderFile(Object importOrderFile) { + public ImportOrderConfig importOrderFile(Object importOrderFile) { Objects.requireNonNull(importOrderFile); - addStep(ImportOrderStep.forJava().createFrom(getProject().file(importOrderFile))); + return new ImportOrderConfig(getProject().file(importOrderFile)); + } + + public class ImportOrderConfig { + final String[] importOrder; + final File importOrderFile; + + boolean wildcardsLast = false; + + ImportOrderConfig(String[] importOrder) { + this.importOrder = importOrder; + importOrderFile = null; + addStep(createStep()); + } + + ImportOrderConfig(File importOrderFile) { + importOrder = null; + this.importOrderFile = importOrderFile; + addStep(createStep()); + } + + /** Sorts wildcard imports after non-wildcard imports, instead of before. */ + public ImportOrderConfig wildcardsLast() { + return wildcardsLast(true); + } + + public ImportOrderConfig wildcardsLast(boolean wildcardsLast) { + this.wildcardsLast = wildcardsLast; + replaceStep(createStep()); + return this; + } + + private FormatterStep createStep() { + ImportOrderStep importOrderStep = ImportOrderStep.forJava(); + + return importOrderFile != null + ? importOrderStep.createFrom(wildcardsLast, getProject().file(importOrderFile)) + : importOrderStep.createFrom(wildcardsLast, importOrder); + } } /** Removes any unused imports. */ diff --git a/testlib/src/main/resources/java/importsorter/JavaCodeImportComments.test b/testlib/src/main/resources/java/importsorter/JavaCodeImportComments.test index 879e4a7e6f..bd481ae8b8 100644 --- a/testlib/src/main/resources/java/importsorter/JavaCodeImportComments.test +++ b/testlib/src/main/resources/java/importsorter/JavaCodeImportComments.test @@ -7,8 +7,14 @@ import java.lang.Runnable; import org.comments.be import org.comments.removed */ -import static java.lang.Runnable.*; +import static java.lang.Runnable.*; /* import other.multiline.comments -import will.be.removed.too */ +import will.be.removed.too */ import static com.foo.Bar +import java.awt.*; +import java.util.*; +import java.util.List; +import static com.github.tomakehurst.wiremock.client.WireMock.*; +import static com.github.tomakehurst.wiremock.client.WireMock.equalTo; +import static org.hamcrest.Matchers.*; diff --git a/testlib/src/main/resources/java/importsorter/JavaCodeSortedImports.test b/testlib/src/main/resources/java/importsorter/JavaCodeSortedImports.test index e20a555950..1d3f2be171 100644 --- a/testlib/src/main/resources/java/importsorter/JavaCodeSortedImports.test +++ b/testlib/src/main/resources/java/importsorter/JavaCodeSortedImports.test @@ -1,9 +1,15 @@ +import java.awt.*; import java.lang.Runnable; import java.lang.Thread; +import java.util.*; +import java.util.List; import org.dooda.Didoo; import static java.lang.Exception.*; import static java.lang.Runnable.*; +import static org.hamcrest.Matchers.*; import static com.foo.Bar; +import static com.github.tomakehurst.wiremock.client.WireMock.*; +import static com.github.tomakehurst.wiremock.client.WireMock.equalTo; diff --git a/testlib/src/main/resources/java/importsorter/JavaCodeSortedImportsDefault.test b/testlib/src/main/resources/java/importsorter/JavaCodeSortedImportsDefault.test index 4a42120864..b04510d2e9 100644 --- a/testlib/src/main/resources/java/importsorter/JavaCodeSortedImportsDefault.test +++ b/testlib/src/main/resources/java/importsorter/JavaCodeSortedImportsDefault.test @@ -1,7 +1,13 @@ import static com.foo.Bar; +import static com.github.tomakehurst.wiremock.client.WireMock.*; +import static com.github.tomakehurst.wiremock.client.WireMock.equalTo; import static java.lang.Exception.*; import static java.lang.Runnable.*; +import static org.hamcrest.Matchers.*; +import java.awt.*; import java.lang.Runnable; import java.lang.Thread; +import java.util.*; +import java.util.List; import org.dooda.Didoo; diff --git a/testlib/src/main/resources/java/importsorter/JavaCodeSortedImportsWildcardsLast.test b/testlib/src/main/resources/java/importsorter/JavaCodeSortedImportsWildcardsLast.test new file mode 100644 index 0000000000..9edce80252 --- /dev/null +++ b/testlib/src/main/resources/java/importsorter/JavaCodeSortedImportsWildcardsLast.test @@ -0,0 +1,13 @@ +import static com.foo.Bar; +import static com.github.tomakehurst.wiremock.client.WireMock.equalTo; +import static com.github.tomakehurst.wiremock.client.WireMock.*; +import static java.lang.Exception.*; +import static java.lang.Runnable.*; +import static org.hamcrest.Matchers.*; + +import java.awt.*; +import java.lang.Runnable; +import java.lang.Thread; +import java.util.List; +import java.util.*; +import org.dooda.Didoo; diff --git a/testlib/src/main/resources/java/importsorter/JavaCodeUnsortedImports.test b/testlib/src/main/resources/java/importsorter/JavaCodeUnsortedImports.test index 50d052d456..a0e6715b10 100644 --- a/testlib/src/main/resources/java/importsorter/JavaCodeUnsortedImports.test +++ b/testlib/src/main/resources/java/importsorter/JavaCodeUnsortedImports.test @@ -1,7 +1,13 @@ import static java.lang.Exception.*; +import static com.github.tomakehurst.wiremock.client.WireMock.*; import org.dooda.Didoo; +import java.util.List; import java.lang.Thread; +import java.util.*; import java.lang.Runnable; +import static org.hamcrest.Matchers.*; import static java.lang.Runnable.*; +import static com.github.tomakehurst.wiremock.client.WireMock.equalTo; import static com.foo.Bar +import java.awt.*; diff --git a/testlib/src/test/java/com/diffplug/spotless/java/ImportOrderStepTest.java b/testlib/src/test/java/com/diffplug/spotless/java/ImportOrderStepTest.java index 389269a3de..332b5bc979 100644 --- a/testlib/src/test/java/com/diffplug/spotless/java/ImportOrderStepTest.java +++ b/testlib/src/test/java/com/diffplug/spotless/java/ImportOrderStepTest.java @@ -46,6 +46,12 @@ void sortImportsUnmatched() throws Throwable { assertOnResources(step, "java/importsorter/JavaCodeUnsortedImportsUnmatched.test", "java/importsorter/JavaCodeSortedImportsUnmatched.test"); } + @Test + void sortImportsWildcardsLast() throws Throwable { + FormatterStep step = ImportOrderStep.forJava().createFrom(true); + assertOnResources(step, "java/importsorter/JavaCodeUnsortedImports.test", "java/importsorter/JavaCodeSortedImportsWildcardsLast.test"); + } + @Test void removeDuplicates() throws Throwable { FormatterStep step = ImportOrderStep.forJava().createFrom(createTestFile("java/importsorter/import_unmatched.properties"));