Skip to content

Commit 44c9eff

Browse files
feat(spotless): sort module-info export statements and place exports before requires (#493)
Signed-off-by: Suvrat Acharya <suvrat1629@gmail.com> Signed-off-by: Jendrik Johannes <jendrik@onepiece.software> Co-authored-by: Jendrik Johannes <jendrik@onepiece.software>
1 parent 95aa6fb commit 44c9eff

3 files changed

Lines changed: 112 additions & 35 deletions

File tree

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
// SPDX-License-Identifier: Apache-2.0
22
module org.hiero.product.module.lib {
3-
requires com.fasterxml.jackson.databind;
4-
53
exports org.hiero.product.module.lib;
4+
5+
requires com.fasterxml.jackson.databind;
66
}

src/main/kotlin/org/hiero/gradle/spotless/SortModuleInfoRequiresStep.kt

Lines changed: 77 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -19,33 +19,65 @@ class SortModuleInfoRequiresStep {
1919
fun toFormatter(): FormatterFunc {
2020
return FormatterFunc { unixStr ->
2121
val lines = unixStr.split('\n')
22-
val blockStartIndex = lines.indexOfFirst { it.trim().startsWith("requires") }
23-
val blockEndIndex = lines.indexOfLast { it.trim().startsWith("requires") }
2422

25-
if (blockStartIndex == -1) {
26-
unixStr // not a module-info.java or no 'requires'
23+
// Only process module-info.java files (not package-info.java)
24+
val openBraceIndex = lines.indexOfFirst { it.contains("{") }
25+
val closeBraceIndex = lines.indexOfLast { it.trim().startsWith("}") }
26+
27+
if (
28+
openBraceIndex == -1 ||
29+
closeBraceIndex == -1 ||
30+
lines.none { it.trim().startsWith("module ") }
31+
) {
32+
unixStr
2733
} else {
28-
val nonRequiresLines = mutableListOf<String>()
34+
val beforeBody = lines.subList(0, openBraceIndex + 1)
35+
val afterBody = lines.subList(closeBraceIndex, lines.size)
36+
val bodyLines = lines.subList(openBraceIndex + 1, closeBraceIndex)
2937

38+
val exports = mutableListOf<List<String>>()
3039
val requiresTransitive = mutableListOf<String>()
3140
val requires = mutableListOf<String>()
3241
val requiresStaticTransitive = mutableListOf<String>()
3342
val requiresStatic = mutableListOf<String>()
43+
val others = mutableListOf<List<String>>()
44+
45+
val current = mutableListOf<String>()
3446

35-
lines.subList(blockStartIndex, blockEndIndex + 1).forEach { line ->
47+
fun flushCurrent() {
48+
if (current.isEmpty()) return
49+
val first = current.first().trim()
3650
when {
37-
line.trim().startsWith("requires static transitive") ->
38-
requiresStaticTransitive.add(line)
39-
line.trim().startsWith("requires static") -> requiresStatic.add(line)
40-
line.trim().startsWith("requires transitive") ->
41-
requiresTransitive.add(line)
42-
line.trim().startsWith("requires") -> requires.add(line)
43-
line.isNotBlank() && !line.trim().startsWith("requires") ->
44-
nonRequiresLines.add(line)
51+
first.startsWith("exports") -> exports.add(current.toList())
52+
first.startsWith("requires static transitive") ->
53+
requiresStaticTransitive.add(current.first())
54+
first.startsWith("requires static") ->
55+
requiresStatic.add(current.first())
56+
first.startsWith("requires transitive") ->
57+
requiresTransitive.add(current.first())
58+
first.startsWith("requires") -> requires.add(current.first())
59+
else -> others.add(current.toList())
60+
}
61+
current.clear()
62+
}
63+
64+
for (line in bodyLines) {
65+
if (line.isBlank()) {
66+
flushCurrent()
67+
continue
68+
}
69+
current.add(line)
70+
if (
71+
line.trimEnd().endsWith(";") ||
72+
(line.contains(";") &&
73+
line.substringAfter(";").trim().startsWith("//"))
74+
) {
75+
flushCurrent()
4576
}
4677
}
78+
flushCurrent()
4779

48-
val comparator =
80+
val requiresComparator =
4981
Comparator<String> { a, b ->
5082
val nameA = a.split(" ").first { it.endsWith(";") }
5183
val nameB = b.split(" ").first { it.endsWith(";") }
@@ -64,22 +96,36 @@ class SortModuleInfoRequiresStep {
6496
}
6597
}
6698

67-
requiresTransitive.sortWith(comparator)
68-
requires.sortWith(comparator)
69-
requiresStaticTransitive.sortWith(comparator)
70-
requiresStatic.sortWith(comparator)
71-
72-
val blockStart = lines.subList(0, blockStartIndex)
73-
val blockEnd = lines.subList(blockEndIndex + 1, lines.size)
74-
75-
(blockStart +
76-
nonRequiresLines +
77-
requiresTransitive +
78-
requires +
79-
requiresStaticTransitive +
80-
requiresStatic +
81-
blockEnd)
82-
.joinToString("\n")
99+
// Sort exports alphabetically by the exported package name
100+
exports.sortBy { it.first() }
101+
requiresTransitive.sortWith(requiresComparator)
102+
requires.sortWith(requiresComparator)
103+
requiresStaticTransitive.sortWith(requiresComparator)
104+
requiresStatic.sortWith(requiresComparator)
105+
106+
val allRequires =
107+
requiresTransitive + requires + requiresStaticTransitive + requiresStatic
108+
109+
val result = mutableListOf<String>()
110+
result.addAll(beforeBody)
111+
112+
if (exports.isNotEmpty()) {
113+
exports.forEach { result.addAll(it) }
114+
}
115+
if (exports.isNotEmpty() && allRequires.isNotEmpty()) {
116+
result.add("")
117+
}
118+
if (allRequires.isNotEmpty()) {
119+
result.addAll(allRequires)
120+
}
121+
if ((exports.isNotEmpty() || allRequires.isNotEmpty()) && others.isNotEmpty()) {
122+
result.add("")
123+
}
124+
others.forEach { result.addAll(it) }
125+
126+
result.addAll(afterBody)
127+
128+
result.joinToString("\n")
83129
}
84130
}
85131
}

src/test/kotlin/org/hiero/gradle/test/QualityGateTest.kt

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -107,10 +107,10 @@ class QualityGateTest {
107107
"""
108108
// SPDX-License-Identifier: Apache-2.0
109109
module org.hiero.product.module.a {
110+
exports org.hiero.product.module.a;
111+
110112
requires com.fasterxml.jackson.databind;
111113
requires org.apache.commons.lang3;
112-
113-
exports org.hiero.product.module.a;
114114
}
115115
"""
116116
.trimIndent()
@@ -210,6 +210,37 @@ class QualityGateTest {
210210
assertThat(result.task(":qualityGate")?.outcome).isEqualTo(TaskOutcome.SUCCESS)
211211
}
212212

213+
@Test
214+
fun `spotlessApply preserves inline comments in module-info directives`() {
215+
val p = GradleProject().withMinimalStructure()
216+
p.moduleBuildFile("""plugins { id("org.hiero.gradle.module.library") }""")
217+
val moduleInfo =
218+
p.moduleInfoFile(
219+
"""
220+
module org.hiero.product.module.a {
221+
requires transitive javax.inject;
222+
requires transitive java.compiler; // javax.annotation.processing.Generated
223+
}
224+
"""
225+
.trimIndent()
226+
)
227+
228+
val result = p.run("spotlessApply")
229+
230+
assertThat(moduleInfo)
231+
.hasContent(
232+
"""
233+
// SPDX-License-Identifier: Apache-2.0
234+
module org.hiero.product.module.a {
235+
requires transitive java.compiler; // javax.annotation.processing.Generated
236+
requires transitive javax.inject;
237+
}
238+
"""
239+
.trimIndent()
240+
)
241+
assertThat(result.task(":spotlessApply")?.outcome).isEqualTo(TaskOutcome.SUCCESS)
242+
}
243+
213244
@Test
214245
fun `spotlessApply formats rust files`() {
215246
val p = GradleProject().withMinimalStructure()

0 commit comments

Comments
 (0)