Skip to content

Commit 799dc21

Browse files
authored
Merge pull request #80 from rubensousa/baseline_npe
Fix NPE when baseline contains empty suppressions
2 parents eeebd78 + 229a925 commit 799dc21

File tree

10 files changed

+85
-39
lines changed

10 files changed

+85
-39
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11

22
# CHANGELOG
33

4+
## 1.0.0-rc01 (2026-03-14)
5+
6+
- Fixed NPE caused by empty module suppressions when the baseline is manually edited
7+
48
## 1.0.0-beta04 (2026-03-06)
59

610
- Added option to include `projectGuardCheck` as part of the build tasks with the following:

projectguard/src/main/kotlin/com/rubensousa/projectguard/plugin/internal/BaselineConfiguration.kt

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,5 +17,11 @@
1717
package com.rubensousa.projectguard.plugin.internal
1818

1919
internal data class BaselineConfiguration(
20-
val suppressions: Map<String, List<DependencySuppression>>
21-
)
20+
val suppressions: Map<String, List<DependencySuppression>?>
21+
) {
22+
23+
fun getModuleSuppressions(moduleId: String): List<DependencySuppression> {
24+
return suppressions[moduleId].orEmpty()
25+
}
26+
27+
}

projectguard/src/main/kotlin/com/rubensousa/projectguard/plugin/internal/YamlProcessor.kt renamed to projectguard/src/main/kotlin/com/rubensousa/projectguard/plugin/internal/BaselineProcessor.kt

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,21 +18,26 @@ package com.rubensousa.projectguard.plugin.internal
1818

1919
import com.fasterxml.jackson.databind.ObjectMapper
2020
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory
21+
import com.fasterxml.jackson.module.kotlin.KotlinFeature
2122
import com.fasterxml.jackson.module.kotlin.KotlinModule
2223
import java.io.File
2324

24-
internal class YamlProcessor {
25+
internal class BaselineProcessor {
2526

2627
private val objectMapper = ObjectMapper(YAMLFactory())
2728
.apply {
28-
registerModule(KotlinModule.Builder().build())
29+
registerModule(
30+
KotlinModule.Builder()
31+
.enable(KotlinFeature.NullToEmptyMap)
32+
.build()
33+
)
2934
}
3035

31-
fun <T> parse(file: File, clazz: Class<T>): T {
32-
return objectMapper.readValue(file, clazz)
36+
fun parse(file: File): BaselineConfiguration {
37+
return objectMapper.readValue(file, BaselineConfiguration::class.java)
3338
}
3439

35-
fun <T> write(file: File, content: T) {
40+
fun write(file: File, content: BaselineConfiguration) {
3641
return objectMapper.writeValue(file, content)
3742
}
3843

projectguard/src/main/kotlin/com/rubensousa/projectguard/plugin/internal/SuppressionMap.kt

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,7 @@ internal class SuppressionMap {
2424
suppressions.clear()
2525
configuration.suppressions.forEach { entry ->
2626
val moduleId = entry.key
27-
val suppressions = entry.value
28-
suppressions.forEach { suppression ->
27+
configuration.getModuleSuppressions(moduleId).forEach { suppression ->
2928
add(moduleId, suppression)
3029
}
3130
}

projectguard/src/main/kotlin/com/rubensousa/projectguard/plugin/internal/task/TaskBaseline.kt

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,8 @@
1616

1717
package com.rubensousa.projectguard.plugin.internal.task
1818

19-
import com.rubensousa.projectguard.plugin.internal.BaselineConfiguration
19+
import com.rubensousa.projectguard.plugin.internal.BaselineProcessor
2020
import com.rubensousa.projectguard.plugin.internal.SuppressionMap
21-
import com.rubensousa.projectguard.plugin.internal.YamlProcessor
2221
import com.rubensousa.projectguard.plugin.internal.report.RestrictionDump
2322
import kotlinx.serialization.json.Json
2423
import org.gradle.api.DefaultTask
@@ -58,14 +57,14 @@ internal class BaselineExecutor(
5857

5958
fun execute() {
6059
val restrictionDump = Json.decodeFromString<RestrictionDump>(inputFile.readText())
61-
val yamlProcessor = YamlProcessor()
60+
val baselineProcessor = BaselineProcessor()
6261
val currentReasons = mutableMapOf<String, MutableMap<String, String>>()
6362
runCatching {
64-
val currentConfiguration = yamlProcessor.parse(outputFile, BaselineConfiguration::class.java)
65-
currentConfiguration.suppressions.forEach { (module, dependencies) ->
66-
dependencies.forEach { (dependency, reason) ->
63+
val currentConfiguration = baselineProcessor.parse(outputFile)
64+
currentConfiguration.suppressions.keys.forEach { module ->
65+
currentConfiguration.getModuleSuppressions(module).forEach { suppression ->
6766
val dependencyReasons = currentReasons.getOrPut(module) { mutableMapOf() }
68-
dependencyReasons[dependency] = reason
67+
dependencyReasons[suppression.dependency] = suppression.reason
6968
}
7069
}
7170
}
@@ -81,7 +80,7 @@ internal class BaselineExecutor(
8180
)
8281
}
8382
}
84-
yamlProcessor.write(outputFile, suppressionMap.getBaseline())
83+
baselineProcessor.write(outputFile, suppressionMap.getBaseline())
8584
}
8685

8786
}

projectguard/src/main/kotlin/com/rubensousa/projectguard/plugin/internal/task/TaskCheck.kt

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,11 @@
1616

1717
package com.rubensousa.projectguard.plugin.internal.task
1818

19-
import com.rubensousa.projectguard.plugin.internal.BaselineConfiguration
19+
import com.rubensousa.projectguard.plugin.internal.BaselineProcessor
2020
import com.rubensousa.projectguard.plugin.internal.DependencyGraphBuilder
2121
import com.rubensousa.projectguard.plugin.internal.ProjectGuardSpec
2222
import com.rubensousa.projectguard.plugin.internal.ReportSpec
2323
import com.rubensousa.projectguard.plugin.internal.SuppressionMap
24-
import com.rubensousa.projectguard.plugin.internal.YamlProcessor
2524
import com.rubensousa.projectguard.plugin.internal.report.DependencyGraphDump
2625
import com.rubensousa.projectguard.plugin.internal.report.HtmlReportGenerator
2726
import com.rubensousa.projectguard.plugin.internal.report.RestrictionDump
@@ -87,10 +86,10 @@ internal class CheckExecutor(
8786
fun execute(): Result<Unit> = runCatching {
8887
val dependencyGraphDump = Json.decodeFromString<DependencyGraphDump>(dependenciesFile.readText())
8988
val graph = DependencyGraphBuilder().buildFromDump(dependencyGraphDump)
90-
val yamlProcessor = YamlProcessor()
89+
val baselineProcessor = BaselineProcessor()
9190
val suppressionMap = SuppressionMap()
9291
runCatching {
93-
yamlProcessor.parse(baselineFile, BaselineConfiguration::class.java)
92+
baselineProcessor.parse(baselineFile)
9493
}.onSuccess { config ->
9594
suppressionMap.set(config)
9695
}.onFailure {

projectguard/src/test/kotlin/com/rubensousa/projectguard/plugin/TaskBaselineTest.kt

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,8 @@ package com.rubensousa.projectguard.plugin
1818

1919
import com.google.common.truth.Truth.assertThat
2020
import com.rubensousa.projectguard.plugin.internal.BaselineConfiguration
21+
import com.rubensousa.projectguard.plugin.internal.BaselineProcessor
2122
import com.rubensousa.projectguard.plugin.internal.DependencySuppression
22-
import com.rubensousa.projectguard.plugin.internal.YamlProcessor
2323
import org.junit.Rule
2424
import org.junit.Test
2525
import org.junit.rules.TemporaryFolder
@@ -58,8 +58,8 @@ class TaskBaselineTest {
5858
val outputFile = plugin.generateBaseline()
5959

6060
// then
61-
val yamlProcessor = YamlProcessor()
62-
val baseline = yamlProcessor.parse(outputFile, BaselineConfiguration::class.java)
61+
val baselineProcessor = BaselineProcessor()
62+
val baseline = baselineProcessor.parse(outputFile)
6363
assertThat(baseline.suppressions).isEqualTo(
6464
mapOf(
6565
moduleId to listOf(
@@ -81,7 +81,7 @@ class TaskBaselineTest {
8181
@Test
8282
fun `baseline generation keeps the previous suppresion reasons`() {
8383
// given
84-
val yamlProcessor = YamlProcessor()
84+
val baselineProcessor = BaselineProcessor()
8585
val moduleId = ":domain"
8686
val secondModuleId = ":data"
8787
val fatalModuleId = ":legacy"
@@ -104,7 +104,7 @@ class TaskBaselineTest {
104104
)
105105
)
106106
)
107-
yamlProcessor.write(baselineFile, currentBaseline)
107+
baselineProcessor.write(baselineFile, currentBaseline)
108108
plugin.dumpDependencies(moduleId) {
109109
addInternalDependency(moduleId, fatalModuleId)
110110
}
@@ -121,7 +121,7 @@ class TaskBaselineTest {
121121
val outputFile = plugin.generateBaseline()
122122

123123
// then
124-
val baseline = yamlProcessor.parse(outputFile, BaselineConfiguration::class.java)
124+
val baseline = baselineProcessor.parse(outputFile)
125125
assertThat(baseline.suppressions).isEqualTo(
126126
mapOf(
127127
moduleId to listOf(

projectguard/src/test/kotlin/com/rubensousa/projectguard/plugin/TaskCheckTest.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,8 @@ package com.rubensousa.projectguard.plugin
1818

1919
import com.google.common.truth.Truth.assertThat
2020
import com.rubensousa.projectguard.plugin.internal.BaselineConfiguration
21+
import com.rubensousa.projectguard.plugin.internal.BaselineProcessor
2122
import com.rubensousa.projectguard.plugin.internal.DependencySuppression
22-
import com.rubensousa.projectguard.plugin.internal.YamlProcessor
2323
import org.junit.Rule
2424
import org.junit.Test
2525
import org.junit.rules.TemporaryFolder
@@ -155,7 +155,7 @@ class TaskCheckTest {
155155
@Test
156156
fun `task fails if baseline contains outdated references no longer part of the graph`() {
157157
// given
158-
val yamlProcessor = YamlProcessor()
158+
val baselineProcessor = BaselineProcessor()
159159
val moduleId = ":domain"
160160
val fatalModuleId = ":legacy"
161161
val baselineFile = plugin.getBaselineFile()
@@ -164,7 +164,7 @@ class TaskCheckTest {
164164
moduleId to listOf(DependencySuppression(dependency = fatalModuleId)),
165165
)
166166
)
167-
yamlProcessor.write(baselineFile, currentBaseline)
167+
baselineProcessor.write(baselineFile, currentBaseline)
168168
plugin.dumpDependencies(moduleId) {
169169
addInternalDependency(moduleId, fatalModuleId)
170170
}

projectguard/src/test/kotlin/com/rubensousa/projectguard/plugin/internal/YamlProcessorTest.kt renamed to projectguard/src/test/kotlin/com/rubensousa/projectguard/plugin/internal/BaselineProcessorTest.kt

Lines changed: 34 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,12 @@ import org.junit.Rule
2121
import org.junit.rules.TemporaryFolder
2222
import kotlin.test.Test
2323

24-
class YamlProcessorTest {
24+
class BaselineProcessorTest {
2525

2626
@get:Rule
2727
val temporaryFolder = TemporaryFolder()
2828

29-
private val yamlProcessor = YamlProcessor()
29+
private val baselineProcessor = BaselineProcessor()
3030

3131
@Test
3232
fun `suppression list is written and parsed back`() {
@@ -38,12 +38,42 @@ class YamlProcessorTest {
3838
val file = temporaryFolder.newFile("suppressions.yml")
3939

4040
// when
41-
yamlProcessor.write(file, configuration)
41+
baselineProcessor.write(file, configuration)
4242

4343
// then
44-
val parsedConfiguration = yamlProcessor.parse(file, BaselineConfiguration::class.java)
44+
val parsedConfiguration = baselineProcessor.parse(file)
4545
assertThat(parsedConfiguration).isEqualTo(configuration)
4646
}
4747

48+
@Test
49+
fun `suppression without any module dependency is parsed correctly`() {
50+
// given
51+
val file = temporaryFolder.newFile("suppressions.yml")
52+
file.writeText("""
53+
suppressions:
54+
module:
55+
""".trimIndent())
56+
57+
// when
58+
val parsedConfiguration = baselineProcessor.parse(file)
59+
60+
// then
61+
assertThat(parsedConfiguration.getModuleSuppressions("module")).isEmpty()
62+
}
63+
64+
@Test
65+
fun `suppression without any module is parsed correctly`() {
66+
// given
67+
val file = temporaryFolder.newFile("suppressions.yml")
68+
file.writeText("""
69+
suppressions:
70+
""".trimIndent())
71+
72+
// when
73+
val parsedConfiguration = baselineProcessor.parse(file)
74+
75+
// then
76+
assertThat(parsedConfiguration.suppressions).isEmpty()
77+
}
4878

4979
}

projectguard/src/test/kotlin/com/rubensousa/projectguard/plugin/internal/SuppressionMapTest.kt

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -89,11 +89,15 @@ class SuppressionMapTest {
8989

9090
// then
9191
val entries = baseline.suppressions.entries.toList()
92-
assertThat(entries[0].key).isEqualTo(":app")
93-
assertThat(entries[0].value[0].dependency).isEqualTo(":a")
94-
assertThat(entries[0].value[1].dependency).isEqualTo(":b")
95-
assertThat(entries[1].key).isEqualTo(":lib")
96-
assertThat(entries[1].value[0].dependency).isEqualTo(":c")
92+
val firstEntry = entries[0]
93+
val firstEntryDependencies = baseline.getModuleSuppressions(firstEntry.key)
94+
val secondEntry = entries[1]
95+
val secondEntryDependencies = baseline.getModuleSuppressions(secondEntry.key)
96+
assertThat(firstEntry.key).isEqualTo(":app")
97+
assertThat(firstEntryDependencies[0].dependency).isEqualTo(":a")
98+
assertThat(firstEntryDependencies[1].dependency).isEqualTo(":b")
99+
assertThat(secondEntry.key).isEqualTo(":lib")
100+
assertThat(secondEntryDependencies[0].dependency).isEqualTo(":c")
97101
}
98102

99103
@Test

0 commit comments

Comments
 (0)