@@ -3,6 +3,7 @@ package datadog.gradle.plugin.lint
33import org.gradle.api.GradleException
44import org.gradle.api.Plugin
55import org.gradle.api.Project
6+ import java.io.ByteArrayOutputStream
67
78class EmptyInstrumentationLinter : Plugin <Project > {
89 override fun apply (target : Project ) {
@@ -19,74 +20,99 @@ class EmptyInstrumentationLinter : Plugin<Project> {
1920 )
2021 }
2122
23+ // Only check files changed on this branch to avoid flagging existing stubs
24+ val changedFiles = getChangedInstrumentationFiles(target, instrumentationsDir)
25+
2226 val violations = mutableListOf<String >()
2327 val hasAdvicePattern = Regex (""" implements\s+[^{]*\b(?:HasMethodAdvice|HasAdvice)\b""" )
2428 val methodAdviceStartPattern = Regex (""" (?:public\s+)?(?:\S+\s+)?methodAdvice\s*\(""" )
2529 val transformCallPattern = Regex (""" \btransform\s*\(""" )
2630
27- instrumentationsDir.walk()
28- .filter { it.isFile && it.name.endsWith(" .java" ) }
29- .forEach { file ->
30- val lines = file.readLines()
31- val content = lines.joinToString(" \n " )
31+ val filesToCheck = if (changedFiles != null ) {
32+ changedFiles.filter { it.isFile && it.name.endsWith(" .java" ) }
33+ } else {
34+ // Fallback: check all files if git diff fails
35+ instrumentationsDir.walk()
36+ .filter { it.isFile && it.name.endsWith(" .java" ) }
37+ .toList()
38+ }
3239
33- if (! hasAdvicePattern.containsMatchIn(content)) return @forEach
40+ filesToCheck.forEach { file ->
41+ val lines = file.readLines()
42+ val content = lines.joinToString(" \n " )
3443
35- // Find methodAdvice( method and check its body for transform( calls
36- var methodAdviceLineIndex = - 1
37- for (i in lines.indices) {
38- if (methodAdviceStartPattern.containsMatchIn( lines[i]) ) {
39- methodAdviceLineIndex = i
40- break
41- }
44+ if ( ! hasAdvicePattern.containsMatchIn(content)) return @forEach
45+
46+ var methodAdviceLineIndex = - 1
47+ for (i in lines.indices ) {
48+ if (methodAdviceStartPattern.containsMatchIn(lines[i])) {
49+ methodAdviceLineIndex = i
50+ break
4251 }
52+ }
4353
44- if (methodAdviceLineIndex < 0 ) return @forEach
54+ if (methodAdviceLineIndex < 0 ) return @forEach
4555
46- // Extract method body by tracking brace depth
47- var braceDepth = 0
48- var methodBodyStart = - 1
49- val methodBodyLines = mutableListOf<String >()
56+ var braceDepth = 0
57+ var methodBodyStart = - 1
58+ val methodBodyLines = mutableListOf<String >()
5059
51- for (i in methodAdviceLineIndex until lines.size) {
52- val line = lines[i]
53- for (ch in line) {
54- when (ch) {
55- ' {' -> {
56- braceDepth++
57- if (braceDepth == 1 ) methodBodyStart = i
58- }
59- ' }' -> {
60- braceDepth--
61- if (braceDepth == 0 && methodBodyStart >= 0 ) {
62- methodBodyLines.add(line)
63- break
64- }
60+ for (i in methodAdviceLineIndex until lines.size) {
61+ val line = lines[i]
62+ for (ch in line) {
63+ when (ch) {
64+ ' {' -> {
65+ braceDepth++
66+ if (braceDepth == 1 ) methodBodyStart = i
67+ }
68+ ' }' -> {
69+ braceDepth--
70+ if (braceDepth == 0 && methodBodyStart >= 0 ) {
71+ break
6572 }
6673 }
6774 }
68- if (methodBodyStart >= 0 ) {
69- methodBodyLines.add(line)
70- }
71- if (braceDepth == 0 && methodBodyStart >= 0 ) break
7275 }
73-
74- val methodBody = methodBodyLines.joinToString(" \n " )
75- if (! transformCallPattern.containsMatchIn(methodBody)) {
76- val classNamePattern = Regex (""" class\s+(\w+)""" )
77- val className = classNamePattern.find(content)?.groupValues?.get(1 ) ? : " <unknown>"
78- val relPath = file.relativeTo(target.rootProject.projectDir).path
79- violations.add(" EMPTY STUB: $relPath :${methodAdviceLineIndex + 1 } — $className .methodAdvice() contains no transform() calls" )
76+ if (methodBodyStart >= 0 ) {
77+ methodBodyLines.add(line)
8078 }
79+ if (braceDepth == 0 && methodBodyStart >= 0 ) break
80+ }
81+
82+ val methodBody = methodBodyLines.joinToString(" \n " )
83+ if (! transformCallPattern.containsMatchIn(methodBody)) {
84+ val classNamePattern = Regex (""" class\s+(\w+)""" )
85+ val className = classNamePattern.find(content)?.groupValues?.get(1 ) ? : " <unknown>"
86+ val relPath = file.relativeTo(target.rootProject.projectDir).path
87+ violations.add(" EMPTY STUB: $relPath :${methodAdviceLineIndex + 1 } — $className .methodAdvice() contains no transform() calls" )
8188 }
89+ }
8290
8391 if (violations.isNotEmpty()) {
8492 violations.forEach { target.logger.error(it) }
85- throw GradleException (" Empty instrumentation stubs found ! See errors above." )
93+ throw GradleException (" Found ${violations.size} new empty instrumentation stub(s) ! See errors above." )
8694 } else {
87- target.logger.lifecycle(" ✓ No empty instrumentation stubs found" )
95+ target.logger.lifecycle(" checkEmptyInstrumentations: no new empty stubs found" )
8896 }
8997 }
9098 }
9199 }
100+
101+ private fun getChangedInstrumentationFiles (project : Project , instrumentationsDir : java.io.File ): List <java.io.File >? {
102+ return try {
103+ val stdout = ByteArrayOutputStream ()
104+ project.exec {
105+ commandLine(" git" , " diff" , " --name-only" , " --diff-filter=ACM" , " origin/master...HEAD" )
106+ standardOutput = stdout
107+ isIgnoreExitValue = true
108+ }
109+ stdout.toString().trim().lines()
110+ .filter { it.endsWith(" .java" ) && it.startsWith(" dd-java-agent/instrumentation/" ) }
111+ .map { project.rootProject.file(it) }
112+ .filter { it.exists() }
113+ } catch (e: Exception ) {
114+ project.logger.warn(" checkEmptyInstrumentations: could not get changed files, checking all — ${e.message} " )
115+ null
116+ }
117+ }
92118}
0 commit comments