Skip to content

Commit ed5148a

Browse files
committed
Merge task-007: add unnecessary else linter (resolved conflict)
2 parents 4502e1f + 2cd8220 commit ed5148a

2 files changed

Lines changed: 116 additions & 0 deletions

File tree

buildSrc/build.gradle.kts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,11 @@ gradlePlugin {
6464
id = "dd-trace-java.empty-instrumentation-linter"
6565
implementationClass = "datadog.gradle.plugin.lint.EmptyInstrumentationLinter"
6666
}
67+
68+
create("unnecessary-else-linter") {
69+
id = "dd-trace-java.unnecessary-else-linter"
70+
implementationClass = "datadog.gradle.plugin.lint.UnnecessaryElseLinter"
71+
}
6772
}
6873
}
6974

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
package datadog.gradle.plugin.lint
2+
3+
import org.gradle.api.Plugin
4+
import org.gradle.api.Project
5+
6+
class UnnecessaryElseLinter : Plugin<Project> {
7+
override fun apply(target: Project) {
8+
target.tasks.register("checkUnnecessaryElse") {
9+
group = "verification"
10+
description = "Scan changed Java files for unnecessary else blocks after return/throw/continue/break"
11+
12+
doLast {
13+
val baseSha = "9b933669729ea8a7af00f5cf3c36b6720ec433bd"
14+
val changedFiles = getChangedJavaFiles(target, baseSha)
15+
val repoRoot = target.rootProject.projectDir.toPath()
16+
val warnings = mutableListOf<String>()
17+
18+
changedFiles.forEach { file ->
19+
if (file.exists()) {
20+
val relPath = repoRoot.relativize(file.toPath()).toString()
21+
checkFile(file, relPath, warnings)
22+
}
23+
}
24+
25+
if (warnings.isNotEmpty()) {
26+
warnings.forEach { target.logger.warn(it) }
27+
target.logger.warn("Found ${warnings.size} unnecessary else block(s) — advisory only.")
28+
} else {
29+
target.logger.info("No unnecessary else blocks found in changed files.")
30+
}
31+
}
32+
}
33+
}
34+
}
35+
36+
private fun getChangedJavaFiles(project: Project, baseSha: String): List<java.io.File> {
37+
return try {
38+
val process = ProcessBuilder("git", "diff", "--name-only", baseSha, "HEAD")
39+
.directory(project.rootProject.projectDir)
40+
.redirectErrorStream(true)
41+
.start()
42+
val output = process.inputStream.bufferedReader().readText()
43+
process.waitFor()
44+
output.trim().lines()
45+
.filter { it.isNotBlank() && it.endsWith(".java") }
46+
.map { project.rootProject.projectDir.resolve(it) }
47+
.filter { it.exists() }
48+
} catch (e: Exception) {
49+
project.logger.warn("checkUnnecessaryElse: could not get changed files — ${e.message}")
50+
emptyList()
51+
}
52+
}
53+
54+
private fun checkFile(file: java.io.File, relPath: String, warnings: MutableList<String>) {
55+
val lines = file.readLines()
56+
val elsePattern = Regex("""^\s*\}\s*else\s*(\{.*)?$""")
57+
58+
for (i in lines.indices) {
59+
if (!elsePattern.containsMatchIn(lines[i])) continue
60+
if (isUnnecessaryElse(lines, i)) {
61+
warnings.add("STYLE: $relPath:${i + 1} — unnecessary else after return/throw/continue/break")
62+
}
63+
}
64+
}
65+
66+
private fun isUnnecessaryElse(lines: List<String>, elseLineIdx: Int): Boolean {
67+
// Walk backwards from "} else {" to find the last meaningful statement
68+
var j = elseLineIdx - 1
69+
while (j >= 0) {
70+
val trimmed = lines[j].trim()
71+
if (trimmed.isEmpty() || isCommentLine(trimmed)) {
72+
j--
73+
continue
74+
}
75+
// The last non-empty line before "} else {" must end with ";" to be a statement boundary
76+
if (!trimmed.endsWith(";")) return false
77+
78+
// Check if this single line is itself an exit statement
79+
if (isExitStatement(trimmed)) return true
80+
81+
// For multi-line statements (e.g. multi-line return/throw), walk further back
82+
// looking for the start of the statement (a line not ending with a continuation)
83+
var k = j - 1
84+
while (k >= 0) {
85+
val prev = lines[k].trim()
86+
if (prev.isEmpty() || isCommentLine(prev)) {
87+
k--
88+
continue
89+
}
90+
// Another complete statement or block boundary — stop
91+
if (prev.endsWith(";") || prev.endsWith("{") || prev.endsWith("}")) return false
92+
// This line is a continuation of the same statement
93+
if (startsWithExitKeyword(prev)) return true
94+
k--
95+
}
96+
return false
97+
}
98+
return false
99+
}
100+
101+
private fun isCommentLine(trimmed: String) =
102+
trimmed.startsWith("//") || trimmed.startsWith("*") || trimmed.startsWith("/*")
103+
104+
private fun isExitStatement(trimmed: String) =
105+
startsWithExitKeyword(trimmed)
106+
107+
private fun startsWithExitKeyword(trimmed: String) =
108+
trimmed.startsWith("return ") || trimmed == "return;" ||
109+
trimmed.startsWith("throw ") ||
110+
trimmed == "continue;" || trimmed.startsWith("continue ") ||
111+
trimmed == "break;" || trimmed.startsWith("break ")

0 commit comments

Comments
 (0)