Skip to content

Commit bd5b869

Browse files
authored
Allow priority override (#48)
1 parent 010bf26 commit bd5b869

File tree

9 files changed

+188
-13
lines changed

9 files changed

+188
-13
lines changed

.editorconfig

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
root = true
2+
3+
[*]
4+
charset = utf-8
5+
indent_size = 4
6+
indent_style = space
7+
insert_final_newline = true
8+
max_line_length = 120
9+
ij_continuation_indent_size = 8
10+
tab_width = 4
11+
trim_trailing_whitespace = true
12+
13+
[{*.kt,*.kts}]
14+
ij_kotlin_allow_trailing_comma = false
15+
ij_kotlin_allow_trailing_comma_on_call_site = false
16+
ij_kotlin_name_count_to_use_star_import_for_members = 20
17+
ij_kotlin_name_count_to_use_star_import = 20
18+
ij_kotlin_continuation_indent_size = 4
19+
ktlint_code_style=intellij_idea
20+
21+
[*.java]
22+
ij_java_names_count_to_use_import_on_demand = 20
23+
ij_java_class_count_to_use_import_on_demand = 20
24+
ij_java_packages_to_use_import_on_demand = 20

README.md

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -93,19 +93,19 @@ public class LibraryArchRulesTest {
9393
public void aMethod() {
9494
LibraryClass.newApi();
9595
}
96-
}
96+
}
9797
static class FailingCode {
9898
public void aMethod() {
9999
LibraryClass.deprecatedApi();
100100
}
101101
}
102-
102+
103103
@Test
104104
public void test_pass() {
105105
EvaluationResult result = Runner.check(LibraryArchRules.noDeprecated, PassingCode.class);
106106
Assertions.assertFalse(result.hasViolation());
107107
}
108-
108+
109109
@Test
110110
public void test_fail() {
111111
EvaluationResult result = Runner.check(LibraryArchRules.noDeprecated, FailingCode.class);
@@ -140,7 +140,7 @@ dependencies {
140140
}
141141
```
142142

143-
#### Report Configuration
143+
#### Report Configuration
144144

145145
The plugin can generate JSON and console reports. Both are enabled by default. The console report can be disabled:
146146
```kotlin
@@ -164,6 +164,17 @@ archRules {
164164
```
165165
The default threshold is MEDIUM.
166166

167+
#### Overriding rule priority
168+
169+
You can override the default priority of a rule using the `ruleName` and priority as `LOW`, `MEDIUM`, or `HIGH`:
170+
```kotlin
171+
archRules {
172+
rule("deprecated") {
173+
priority("HIGH")
174+
}
175+
}
176+
```
177+
167178
#### Configuring which code is tested
168179

169180
You can skip running rules on a specific source set:
@@ -200,4 +211,4 @@ http://www.apache.org/licenses/LICENSE-2.0
200211

201212
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "
202213
AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific
203-
language governing permissions and limitations under the License.
214+
language governing permissions and limitations under the License.

nebula-archrules-gradle-plugin/src/main/java/com/netflix/nebula/archrules/gradle/RunRulesParams.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package com.netflix.nebula.archrules.gradle;
22

3+
import com.tngtech.archunit.lang.Priority;
34
import org.gradle.api.file.ConfigurableFileCollection;
5+
import org.gradle.api.provider.MapProperty;
46
import org.gradle.api.provider.Property;
57
import org.gradle.workers.WorkParameters;
68
import org.jspecify.annotations.NonNull;
@@ -11,4 +13,6 @@ public interface RunRulesParams extends WorkParameters {
1113
ConfigurableFileCollection getClassesToCheck();
1214

1315
Property<@NonNull File> getDataOutputFile();
16+
17+
MapProperty<@NonNull String, @NonNull Priority> getPriorityOverrides();
1418
}

nebula-archrules-gradle-plugin/src/main/java/com/netflix/nebula/archrules/gradle/RunRulesWorkAction.java

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import com.netflix.nebula.archrules.core.ArchRulesService;
44
import com.netflix.nebula.archrules.core.Runner;
55
import com.tngtech.archunit.core.importer.ClassFileImporter;
6+
import com.tngtech.archunit.lang.Priority;
67
import org.gradle.workers.WorkAction;
78
import org.slf4j.Logger;
89
import org.slf4j.LoggerFactory;
@@ -13,6 +14,7 @@
1314
import java.io.ObjectOutputStream;
1415
import java.util.ArrayList;
1516
import java.util.List;
17+
import java.util.Map;
1618
import java.util.ServiceLoader;
1719
import java.util.stream.Collectors;
1820

@@ -32,10 +34,17 @@ public void execute() {
3234
final var classesToCheck = new ClassFileImporter()
3335
.importPaths(getParameters().getClassesToCheck().getFiles().stream().map(File::toPath).toList());
3436
final List<RuleResult> violationList = new ArrayList<>();
37+
final var overrides = getParameters().getPriorityOverrides().getOrElse(Map.of());
3538
ruleClasses.forEach(ruleClass -> ruleClass.getRules().forEach((id, archRule) -> {
3639
final var result = Runner.check(archRule, classesToCheck);
37-
//TODO: allow priority overrides by rule name
38-
final var rule = new Rule(ruleClass.getClass().getCanonicalName(), id, archRule.getDescription(), result.getPriority());
40+
41+
// check if there is a priority override
42+
var priority = result.getPriority();
43+
if (overrides.containsKey(id)) {
44+
priority = overrides.get(id);
45+
}
46+
47+
final var rule = new Rule(ruleClass.getClass().getCanonicalName(), id, archRule.getDescription(), priority);
3948
if (result.hasViolation()) {
4049
result.getFailureReport().getDetails().forEach(detail -> {
4150
if (detail.equals(NO_MATCH_MESSAGE)) {
@@ -62,4 +71,4 @@ public void execute() {
6271
throw new RuntimeException(e);
6372
}
6473
}
65-
}
74+
}

nebula-archrules-gradle-plugin/src/main/kotlin/com/netflix/nebula/archrules/gradle/ArchrulesExtension.kt

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
package com.netflix.nebula.archrules.gradle
22

33
import com.tngtech.archunit.lang.Priority
4+
import org.gradle.api.Action
45
import org.gradle.api.provider.ListProperty
6+
import org.gradle.api.provider.MapProperty
57
import org.gradle.api.provider.Property
68

79
abstract class ArchrulesExtension {
@@ -19,6 +21,11 @@ abstract class ArchrulesExtension {
1921
abstract val failureThreshold: Property<Priority>
2022
abstract val consoleDetailsThreshold: Property<Priority>
2123

24+
/**
25+
* Allow priority overrides
26+
*/
27+
abstract val priorityOverrides: MapProperty<String, Priority>
28+
2229
/**
2330
* Add a source set to the list of sourcesets to skip
2431
*/
@@ -41,4 +48,13 @@ abstract class ArchrulesExtension {
4148
fun consoleDetailsThreshold(priority: String) {
4249
consoleDetailsThreshold.set(Priority.valueOf(priority))
4350
}
44-
}
51+
52+
fun rule(ruleName: String, action: Action<RuleConfig>) {
53+
val config = RuleConfig()
54+
action.execute(config)
55+
56+
config.priority?.let { priority ->
57+
priorityOverrides.put(ruleName, priority)
58+
}
59+
}
60+
}

nebula-archrules-gradle-plugin/src/main/kotlin/com/netflix/nebula/archrules/gradle/ArchrulesRunnerPlugin.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ class ArchrulesRunnerPlugin : Plugin<Project> {
9090
tasks.register<CheckRulesTask>("checkArchRules" + sourceSet.name.capitalized()) {
9191
description = "Checks ArchRules on ${sourceSet.name}"
9292
rulesClasspath.setFrom(sourceSetArchRulesRuntime)
93+
priorityOverrides.set(ext.priorityOverrides)
9394
dataFile.set(archRulesReportDir.map {
9495
it.file(sourceSet.name + ".data").asFile
9596
})

nebula-archrules-gradle-plugin/src/main/kotlin/com/netflix/nebula/archrules/gradle/CheckRulesTask.kt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
package com.netflix.nebula.archrules.gradle;
22

3+
import com.tngtech.archunit.lang.Priority
34
import org.gradle.api.DefaultTask
45
import org.gradle.api.file.ConfigurableFileCollection
6+
import org.gradle.api.provider.MapProperty
57
import org.gradle.api.provider.Property
68
import org.gradle.api.tasks.*
79
import org.gradle.kotlin.dsl.submit
@@ -24,6 +26,9 @@ abstract class CheckRulesTask @Inject constructor(private val workerExecutor: Wo
2426
@get:OutputFile
2527
abstract val dataFile: Property<File>
2628

29+
@get:Input
30+
abstract val priorityOverrides: MapProperty<String, Priority>
31+
2732
@TaskAction
2833
fun checkRules() {
2934
val workQueue: WorkQueue = workerExecutor.classLoaderIsolation {
@@ -32,6 +37,7 @@ abstract class CheckRulesTask @Inject constructor(private val workerExecutor: Wo
3237
workQueue.submit(RunRulesWorkAction::class) {
3338
getClassesToCheck().from(sourcesToCheck)
3439
getDataOutputFile().set(dataFile)
40+
getPriorityOverrides().set(this@CheckRulesTask.priorityOverrides)
3541
}
3642
}
3743
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package com.netflix.nebula.archrules.gradle
2+
3+
import com.tngtech.archunit.lang.Priority
4+
import org.slf4j.Logger
5+
import org.slf4j.LoggerFactory
6+
import java.util.EnumSet
7+
8+
class RuleConfig {
9+
companion object {
10+
@JvmStatic
11+
val LOGGER: Logger = LoggerFactory.getLogger(RuleConfig::class.java)
12+
}
13+
14+
var priority: Priority? = null
15+
16+
fun priority(priority: String) {
17+
val validStrings = EnumSet.allOf(Priority::class.java).map { it.asString() }.toSet()
18+
if (validStrings.contains(priority)) {
19+
priority(Priority.valueOf(priority))
20+
} else {
21+
LOGGER.warn(
22+
"Invalid ArchRule priority '$priority'. " +
23+
"Must be one of the following (case-sensitive): ${validStrings.joinToString(", ")}"
24+
)
25+
}
26+
}
27+
28+
fun priority(priority: Priority) {
29+
this.priority = priority
30+
}
31+
}

nebula-archrules-gradle-plugin/src/test/kotlin/com/netflix/nebula/archrules/gradle/ArchrulesRunnerPluginTest.kt

Lines changed: 77 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -298,7 +298,7 @@ archRules {
298298
"""
299299
archRules {
300300
skipPassingSummaries = true
301-
}
301+
}
302302
"""
303303
)
304304
src {
@@ -341,7 +341,7 @@ archRules {
341341
"""
342342
archRules {
343343
consoleDetailsThreshold("MEDIUM")
344-
}
344+
}
345345
"""
346346
)
347347
}
@@ -370,7 +370,7 @@ archRules {
370370
"""
371371
archRules {
372372
consoleDetailsThreshold("LOW")
373-
}
373+
}
374374
"""
375375
)
376376
}
@@ -427,7 +427,7 @@ archRules {
427427
"""
428428
archRules {
429429
skipSourceSet("test")
430-
}
430+
}
431431
"""
432432
)
433433
}
@@ -464,4 +464,77 @@ archRules {
464464
}
465465
return list
466466
}
467+
468+
@Test
469+
fun `can override priority of a rule`() {
470+
val runner = testProject(projectDir) {
471+
setupConsumerProject {
472+
rawBuildScript(
473+
"""
474+
archRules {
475+
rule("deprecated") {
476+
priority("HIGH")
477+
}
478+
}
479+
"""
480+
)
481+
}
482+
}
483+
484+
val result = runner.run("checkArchRulesMain", "--stacktrace", "-x", "test")
485+
486+
assertThat(result.task(":checkArchRulesMain"))
487+
.`as`("archRules run for main source set")
488+
.hasOutcome(TaskOutcome.SUCCESS, TaskOutcome.FROM_CACHE)
489+
490+
val mainReport = projectDir.resolve("build/reports/archrules/main.data")
491+
val results = readDetails(mainReport)
492+
493+
// assert deprecated (LOW) is overridden
494+
val deprecatedResults = results.filter { it.rule.ruleName.equals("deprecated") }
495+
assertThat(deprecatedResults).hasSize(2)
496+
deprecatedResults.forEach { result ->
497+
assertThat(result.rule.priority).isEqualTo(Priority.HIGH)
498+
}
499+
500+
// assert deprecatedForRemoval (MEDIUM), which is in the same class but not the same rule, is not overridden
501+
val deprecatedForRemovalResult = results.firstOrNull { it.rule.ruleName.equals("deprecatedForRemoval") }
502+
assertThat(deprecatedForRemovalResult).isNotNull
503+
assertThat(deprecatedForRemovalResult!!.rule.priority).isEqualTo(Priority.MEDIUM)
504+
}
505+
506+
@Test
507+
fun `invalid priority string logs warning and does not override`() {
508+
val runner = testProject(projectDir) {
509+
setupConsumerProject {
510+
rawBuildScript(
511+
"""
512+
archRules {
513+
rule("deprecatedForRemoval") {
514+
priority("NONE")
515+
}
516+
}
517+
"""
518+
)
519+
}
520+
}
521+
522+
val result = runner.run("checkArchRulesMain", "--stacktrace", "-x", "test")
523+
524+
assertThat(result.output)
525+
.contains("Invalid ArchRule priority 'NONE'")
526+
.contains("Must be one of the following (case-sensitive): HIGH, MEDIUM, LOW")
527+
528+
assertThat(result.task(":checkArchRulesMain"))
529+
.`as`("archRules run for main source set")
530+
.hasOutcome(TaskOutcome.SUCCESS, TaskOutcome.FROM_CACHE)
531+
532+
val mainReport = projectDir.resolve("build/reports/archrules/main.data")
533+
val results = readDetails(mainReport)
534+
535+
// assert priority stays default
536+
val deprecationResult = results.firstOrNull { it.rule.ruleName.equals("deprecated") }
537+
assertThat(deprecationResult).isNotNull
538+
assertThat(deprecationResult!!.rule.priority).isEqualTo(Priority.LOW)
539+
}
467540
}

0 commit comments

Comments
 (0)