Skip to content

Commit c173aa7

Browse files
authored
Support for group of imports without blank lines between groups (#1401)
2 parents e67ece9 + 997a4b5 commit c173aa7

File tree

9 files changed

+126
-107
lines changed

9 files changed

+126
-107
lines changed

CHANGES.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ 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+
* `importOrder` now support groups of imports without blank lines ([#1401](https://github.com/diffplug/spotless/pull/1401))
1315
### Fixed
1416
* Don't treat `@Value` as a type annotation [#1367](https://github.com/diffplug/spotless/pull/1367)
1517
* Support `ktlint_disabled_rules` in `ktlint` 0.47.x [#1378](https://github.com/diffplug/spotless/pull/1378)

lib/src/main/java/com/diffplug/spotless/java/ImportSorterImpl.java

Lines changed: 76 additions & 100 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.
@@ -17,6 +17,8 @@
1717

1818
import java.io.Serializable;
1919
import java.util.*;
20+
import java.util.stream.Collectors;
21+
import java.util.stream.Stream;
2022

2123
import javax.annotation.Nullable;
2224

@@ -25,12 +27,41 @@
2527
// which itself is licensed under the Apache 2.0 license.
2628
final class ImportSorterImpl {
2729

28-
private final List<String> template = new ArrayList<>();
30+
private static final String CATCH_ALL_SUBGROUP = "";
31+
private static final String STATIC_KEYWORD = "static ";
32+
private static final String STATIC_SYMBOL = "\\#";
33+
private static final String SUBGROUP_SEPARATOR = "|";
34+
35+
private final List<ImportsGroup> importsGroups;
2936
private final Map<String, List<String>> matchingImports = new HashMap<>();
3037
private final List<String> notMatching = new ArrayList<>();
3138
private final Set<String> allImportOrderItems = new HashSet<>();
3239
private final Comparator<String> ordering;
3340

41+
// An ImportsGroup is a group of imports ; each group is separated by blank lines.
42+
// A group is composed of subgroups : imports are sorted by subgroup.
43+
private static class ImportsGroup {
44+
45+
private final List<String> subGroups;
46+
47+
public ImportsGroup(String importOrder) {
48+
this.subGroups = Stream.of(importOrder.split("\\" + SUBGROUP_SEPARATOR))
49+
.map(this::normalizeStatic)
50+
.collect(Collectors.toList());
51+
}
52+
53+
private String normalizeStatic(String subgroup) {
54+
if (subgroup.startsWith(STATIC_SYMBOL)) {
55+
return subgroup.replace(STATIC_SYMBOL, STATIC_KEYWORD);
56+
}
57+
return subgroup;
58+
}
59+
60+
public List<String> getSubGroups() {
61+
return subGroups;
62+
}
63+
}
64+
3465
static List<String> sort(List<String> imports, List<String> importsOrder, boolean wildcardsLast, String lineFormat) {
3566
ImportSorterImpl importsSorter = new ImportSorterImpl(importsOrder, wildcardsLast);
3667
return importsSorter.sort(imports, lineFormat);
@@ -40,43 +71,42 @@ private List<String> sort(List<String> imports, String lineFormat) {
4071
filterMatchingImports(imports);
4172
mergeNotMatchingItems(false);
4273
mergeNotMatchingItems(true);
43-
mergeMatchingItems();
74+
List<String> sortedImported = mergeMatchingItems();
4475

45-
return getResult(lineFormat);
76+
return getResult(sortedImported, lineFormat);
4677
}
4778

4879
private ImportSorterImpl(List<String> importOrder, boolean wildcardsLast) {
49-
List<String> importOrderCopy = new ArrayList<>(importOrder);
50-
normalizeStaticOrderItems(importOrderCopy);
51-
putStaticItemIfNotExists(importOrderCopy);
52-
template.addAll(importOrderCopy);
80+
importsGroups = importOrder.stream().filter(Objects::nonNull).map(ImportsGroup::new).collect(Collectors.toList());
81+
putStaticItemIfNotExists(importsGroups);
82+
putCatchAllGroupIfNotExists(importsGroups);
83+
5384
ordering = new OrderingComparator(wildcardsLast);
54-
this.allImportOrderItems.addAll(importOrderCopy);
85+
86+
List<String> subgroups = importsGroups.stream().map(ImportsGroup::getSubGroups).flatMap(Collection::stream).collect(Collectors.toList());
87+
this.allImportOrderItems.addAll(subgroups);
5588
}
5689

57-
private static void putStaticItemIfNotExists(List<String> allImportOrderItems) {
58-
boolean contains = false;
90+
private void putStaticItemIfNotExists(List<ImportsGroup> importsGroups) {
91+
boolean catchAllSubGroupExist = importsGroups.stream().anyMatch(group -> group.getSubGroups().contains(STATIC_KEYWORD));
92+
if (catchAllSubGroupExist) {
93+
return;
94+
}
95+
5996
int indexOfFirstStatic = 0;
60-
for (int i = 0; i < allImportOrderItems.size(); i++) {
61-
String allImportOrderItem = allImportOrderItems.get(i);
62-
if (allImportOrderItem.equals("static ")) {
63-
contains = true;
64-
}
65-
if (allImportOrderItem.startsWith("static ")) {
97+
for (int i = 0; i < importsGroups.size(); i++) {
98+
boolean subgroupMatch = importsGroups.get(i).getSubGroups().stream().anyMatch(subgroup -> subgroup.startsWith(STATIC_KEYWORD));
99+
if (subgroupMatch) {
66100
indexOfFirstStatic = i;
67101
}
68102
}
69-
if (!contains) {
70-
allImportOrderItems.add(indexOfFirstStatic, "static ");
71-
}
103+
importsGroups.add(indexOfFirstStatic, new ImportsGroup(STATIC_KEYWORD));
72104
}
73105

74-
private static void normalizeStaticOrderItems(List<String> allImportOrderItems) {
75-
for (int i = 0; i < allImportOrderItems.size(); i++) {
76-
String s = allImportOrderItems.get(i);
77-
if (s.startsWith("\\#")) {
78-
allImportOrderItems.set(i, s.replace("\\#", "static "));
79-
}
106+
private void putCatchAllGroupIfNotExists(List<ImportsGroup> importsGroups) {
107+
boolean catchAllSubGroupExist = importsGroups.stream().anyMatch(group -> group.getSubGroups().contains(CATCH_ALL_SUBGROUP));
108+
if (!catchAllSubGroupExist) {
109+
importsGroups.add(new ImportsGroup(CATCH_ALL_SUBGROUP));
80110
}
81111
}
82112

@@ -87,9 +117,7 @@ private void filterMatchingImports(List<String> imports) {
87117
for (String anImport : imports) {
88118
String orderItem = getBestMatchingImportOrderItem(anImport);
89119
if (orderItem != null) {
90-
if (!matchingImports.containsKey(orderItem)) {
91-
matchingImports.put(orderItem, new ArrayList<>());
92-
}
120+
matchingImports.computeIfAbsent(orderItem, key -> new ArrayList<>());
93121
matchingImports.get(orderItem).add(anImport);
94122
} else {
95123
notMatching.add(anImport);
@@ -116,34 +144,14 @@ private void filterMatchingImports(List<String> imports) {
116144
* not matching means it does not match any order item, so it will be appended before or after order items
117145
*/
118146
private void mergeNotMatchingItems(boolean staticItems) {
119-
sort(notMatching);
120-
121-
int firstIndexOfOrderItem = getFirstIndexOfOrderItem(notMatching, staticItems);
122-
int indexOfOrderItem = 0;
123147
for (String notMatchingItem : notMatching) {
124148
if (!matchesStatic(staticItems, notMatchingItem)) {
125149
continue;
126150
}
127151
boolean isOrderItem = isOrderItem(notMatchingItem, staticItems);
128-
if (isOrderItem) {
129-
indexOfOrderItem = template.indexOf(notMatchingItem);
130-
} else {
131-
if (indexOfOrderItem == 0 && firstIndexOfOrderItem != 0) {
132-
// insert before alphabetically first order item
133-
template.add(firstIndexOfOrderItem, notMatchingItem);
134-
firstIndexOfOrderItem++;
135-
} else if (firstIndexOfOrderItem == 0) {
136-
// no order is specified
137-
if (template.size() > 0 && (template.get(template.size() - 1).startsWith("static"))) {
138-
// insert N after last static import
139-
template.add(ImportSorter.N);
140-
}
141-
template.add(notMatchingItem);
142-
} else {
143-
// insert after the previous order item
144-
template.add(indexOfOrderItem + 1, notMatchingItem);
145-
indexOfOrderItem++;
146-
}
152+
if (!isOrderItem) {
153+
matchingImports.computeIfAbsent(CATCH_ALL_SUBGROUP, key -> new ArrayList<>());
154+
matchingImports.get(CATCH_ALL_SUBGROUP).add(notMatchingItem);
147155
}
148156
}
149157
}
@@ -153,76 +161,44 @@ private boolean isOrderItem(String notMatchingItem, boolean staticItems) {
153161
return contains && matchesStatic(staticItems, notMatchingItem);
154162
}
155163

156-
/**
157-
* gets first order item from sorted input list, and finds out it's index in template.
158-
*/
159-
private int getFirstIndexOfOrderItem(List<String> notMatching, boolean staticItems) {
160-
int firstIndexOfOrderItem = 0;
161-
for (String notMatchingItem : notMatching) {
162-
if (!matchesStatic(staticItems, notMatchingItem)) {
163-
continue;
164-
}
165-
boolean isOrderItem = isOrderItem(notMatchingItem, staticItems);
166-
if (isOrderItem) {
167-
firstIndexOfOrderItem = template.indexOf(notMatchingItem);
168-
break;
169-
}
170-
}
171-
return firstIndexOfOrderItem;
172-
}
173-
174164
private static boolean matchesStatic(boolean staticItems, String notMatchingItem) {
175-
boolean isStatic = notMatchingItem.startsWith("static ");
165+
boolean isStatic = notMatchingItem.startsWith(STATIC_KEYWORD);
176166
return (isStatic && staticItems) || (!isStatic && !staticItems);
177167
}
178168

179-
private void mergeMatchingItems() {
180-
for (int i = 0; i < template.size(); i++) {
181-
String item = template.get(i);
182-
if (allImportOrderItems.contains(item)) {
183-
// find matching items for order item
184-
List<String> strings = matchingImports.get(item);
169+
private List<String> mergeMatchingItems() {
170+
List<String> template = new ArrayList<>();
171+
for (ImportsGroup group : importsGroups) {
172+
boolean groupIsNotEmpty = false;
173+
for (String subgroup : group.getSubGroups()) {
174+
List<String> strings = matchingImports.get(subgroup);
185175
if (strings == null || strings.isEmpty()) {
186-
// if there is none, just remove order item
187-
template.remove(i);
188-
i--;
189176
continue;
190177
}
178+
groupIsNotEmpty = true;
191179
List<String> matchingItems = new ArrayList<>(strings);
192180
sort(matchingItems);
193-
194-
// replace order item by matching import statements
195-
// this is a mess and it is only a luck that it works :-]
196-
template.remove(i);
197-
if (i != 0 && !template.get(i - 1).equals(ImportSorter.N)) {
198-
template.add(i, ImportSorter.N);
199-
i++;
200-
}
201-
if (i + 1 < template.size() && !template.get(i + 1).equals(ImportSorter.N)
202-
&& !template.get(i).equals(ImportSorter.N)) {
203-
template.add(i, ImportSorter.N);
204-
}
205-
template.addAll(i, matchingItems);
206-
if (i != 0 && !template.get(i - 1).equals(ImportSorter.N)) {
207-
template.add(i, ImportSorter.N);
208-
}
209-
181+
template.addAll(matchingItems);
182+
}
183+
if (groupIsNotEmpty) {
184+
template.add(ImportSorter.N);
210185
}
211186
}
212187
// if there is \n on the end, remove it
213-
if (template.size() > 0 && template.get(template.size() - 1).equals(ImportSorter.N)) {
188+
if (!template.isEmpty() && template.get(template.size() - 1).equals(ImportSorter.N)) {
214189
template.remove(template.size() - 1);
215190
}
191+
return template;
216192
}
217193

218194
private void sort(List<String> items) {
219195
items.sort(ordering);
220196
}
221197

222-
private List<String> getResult(String lineFormat) {
198+
private List<String> getResult(List<String> sortedImported, String lineFormat) {
223199
List<String> strings = new ArrayList<>();
224200

225-
for (String s : template) {
201+
for (String s : sortedImported) {
226202
if (s.equals(ImportSorter.N)) {
227203
strings.add(s);
228204
} else {

plugin-gradle/CHANGES.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
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+
* `importOrder` now support groups of imports without blank lines ([#1401](https://github.com/diffplug/spotless/pull/1401))
68
### Fixed
79
* Don't treat `@Value` as a type annotation [#1367](https://github.com/diffplug/spotless/pull/1367)
810
* Support `ktlint_disabled_rules` in `ktlint` 0.47.x [#1378](https://github.com/diffplug/spotless/pull/1378)

plugin-gradle/README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -151,8 +151,8 @@ spotless {
151151
// Use the default importOrder configuration
152152
importOrder()
153153
// optional: you can specify import groups directly
154-
// note: you can use an empty string for all the imports you didn't specify explicitly, and '\\#` prefix for static imports
155-
importOrder('java', 'javax', 'com.acme', '', '\\#com.acme', '\\#')
154+
// note: you can use an empty string for all the imports you didn't specify explicitly, '|' to join group without blank line, and '\\#` prefix for static imports
155+
importOrder('java|javax', 'com.acme', '', '\\#com.acme', '\\#')
156156
// optional: instead of specifying import groups directly you can specify a config file
157157
// export config file: https://github.com/diffplug/spotless/blob/main/ECLIPSE_SCREENSHOTS.md#creating-spotlessimportorder
158158
importOrderFile('eclipse-import-order.txt') // import order file as exported from eclipse

plugin-maven/CHANGES.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
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+
* `importOrder` now support groups of imports without blank lines ([#1401](https://github.com/diffplug/spotless/pull/1401))
68
### Fixed
79
* Don't treat `@Value` as a type annotation [#1367](https://github.com/diffplug/spotless/pull/1367)
810
* Support `ktlint_disabled_rules` in `ktlint` 0.47.x [#1378](https://github.com/diffplug/spotless/pull/1378)

plugin-maven/README.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -181,8 +181,8 @@ any other maven phase (i.e. compile) then it can be configured as below;
181181
<importOrder /> <!-- standard import order -->
182182
<importOrder> <!-- or a custom ordering -->
183183
<wildcardsLast>false</wildcardsLast> <!-- Optional, default false. Sort wildcard import after specific imports -->
184-
<order>java,javax,org,com,com.diffplug,,\\#com.diffplug,\\#</order> <!-- or use <file>${project.basedir}/eclipse.importorder</file> -->
185-
<!-- you can use an empty string for all the imports you didn't specify explicitly, and '\\#` prefix for static imports. -->
184+
<order>java|javax,org,com,com.diffplug,,\\#com.diffplug,\\#</order> <!-- or use <file>${project.basedir}/eclipse.importorder</file> -->
185+
<!-- you can use an empty string for all the imports you didn't specify explicitly, '|' to join group without blank line, and '\\#` prefix for static imports. -->
186186
</importOrder>
187187

188188
<removeUnusedImports /> <!-- self-explanatory -->
@@ -286,8 +286,8 @@ These mechanisms already exist for the Gradle plugin.
286286

287287
<importOrder /> <!-- standard import order -->
288288
<importOrder> <!-- or a custom ordering -->
289-
<order>java,javax,org,com,com.diffplug,,\\#com.diffplug,\\#</order> <!-- or use <file>${project.basedir}/eclipse.importorder</file> -->
290-
<!-- you can use an empty string for all the imports you didn't specify explicitly, and '\\#` prefix for static imports -->
289+
<order>java|javax,org,com,com.diffplug,,\\#com.diffplug,\\#</order> <!-- or use <file>${project.basedir}/eclipse.importorder</file> -->
290+
<!-- you can use an empty string for all the imports you didn't specify explicitly, '|' to join group without blank line, and '\\#` prefix for static imports. -->
291291
</importOrder>
292292

293293
<greclipse /> <!-- has its own section below -->
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import java.awt.*;
2+
import java.lang.Runnable;
3+
import java.lang.Thread;
4+
import java.util.*;
5+
import java.util.List;
6+
import javax.annotation.Nullable;
7+
import javax.inject.Inject;
8+
9+
import org.dooda.Didoo;
10+
import static com.foo.Bar;
11+
import static com.github.tomakehurst.wiremock.client.WireMock.*;
12+
import static com.github.tomakehurst.wiremock.client.WireMock.equalTo;
13+
14+
import static java.lang.Exception.*;
15+
import static java.lang.Runnable.*;
16+
import static org.hamcrest.Matchers.*;
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import static java.lang.Exception.*;
2+
import static com.github.tomakehurst.wiremock.client.WireMock.*;
3+
import org.dooda.Didoo;
4+
import java.util.List;
5+
import javax.inject.Inject;
6+
import java.lang.Thread;
7+
import java.util.*;
8+
import java.lang.Runnable;
9+
import static org.hamcrest.Matchers.*;
10+
import javax.annotation.Nullable;
11+
12+
import static java.lang.Runnable.*;
13+
import static com.github.tomakehurst.wiremock.client.WireMock.equalTo;
14+
import static com.foo.Bar
15+
import java.awt.*;

testlib/src/test/java/com/diffplug/spotless/java/ImportOrderStepTest.java

Lines changed: 7 additions & 1 deletion
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.
@@ -34,6 +34,12 @@ void sortImportsFromArray() throws Throwable {
3434
assertOnResources(step, "java/importsorter/JavaCodeUnsortedImports.test", "java/importsorter/JavaCodeSortedImports.test");
3535
}
3636

37+
@Test
38+
void sortImportsFromArrayWithSubgroups() throws Throwable {
39+
FormatterStep step = ImportOrderStep.forJava().createFrom("java|javax", "org|\\#com", "\\#");
40+
assertOnResources(step, "java/importsorter/JavaCodeUnsortedImportsSubgroups.test", "java/importsorter/JavaCodeSortedImportsSubgroups.test");
41+
}
42+
3743
@Test
3844
void sortImportsFromFile() throws Throwable {
3945
FormatterStep step = ImportOrderStep.forJava().createFrom(createTestFile("java/importsorter/import.properties"));

0 commit comments

Comments
 (0)