Skip to content

Commit 579d30e

Browse files
committed
addressing pr comments pt2
1 parent ad2897d commit 579d30e

2 files changed

Lines changed: 177 additions & 173 deletions

File tree

buildSrc/src/main/kotlin/datadog/gradle/plugin/config/ConfigInversionLinter.kt

Lines changed: 7 additions & 173 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,14 @@
11
package datadog.gradle.plugin.config
22

3-
import com.github.javaparser.ParserConfiguration
4-
import com.github.javaparser.StaticJavaParser
5-
import com.github.javaparser.ast.CompilationUnit
63
import com.github.javaparser.ast.Modifier
7-
import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration
8-
import com.github.javaparser.ast.body.FieldDeclaration
9-
import com.github.javaparser.ast.body.MethodDeclaration
10-
import com.github.javaparser.ast.body.VariableDeclarator
11-
import com.github.javaparser.ast.expr.StringLiteralExpr
124
import com.github.javaparser.ast.nodeTypes.NodeWithModifiers
13-
import com.github.javaparser.ast.stmt.ExplicitConstructorInvocationStmt
14-
import com.github.javaparser.ast.stmt.ReturnStmt
15-
import org.gradle.api.DefaultTask
165
import org.gradle.api.GradleException
176
import org.gradle.api.Plugin
187
import org.gradle.api.Project
19-
import org.gradle.api.file.ConfigurableFileCollection
20-
import org.gradle.api.provider.Property
21-
import org.gradle.api.tasks.Input
22-
import org.gradle.api.tasks.InputFiles
238
import org.gradle.api.Task
249
import org.gradle.api.tasks.TaskProvider
2510
import org.gradle.api.tasks.SourceSet
2611
import org.gradle.api.tasks.SourceSetContainer
27-
import org.gradle.api.tasks.TaskAction
2812
import org.gradle.kotlin.dsl.getByType
2913
import java.net.URLClassLoader
3014
import java.nio.file.Path
@@ -35,11 +19,13 @@ class ConfigInversionLinter : Plugin<Project> {
3519
val logEnvVarUsages = registerLogEnvVarUsages(target, extension)
3620
val checkEnvVarUsage = registerCheckEnvironmentVariablesUsage(target)
3721
val checkConfigStrings = registerCheckConfigStringsTask(target, extension)
22+
val checkInstrumenterModule = registerCheckInstrumenterModuleConfigurations(target, extension)
23+
val checkDecoratorAnalytics = registerCheckDecoratorAnalyticsConfigurations(target, extension)
3824

3925
target.tasks.register("checkConfigurations") {
4026
group = "verification"
4127
description = "Runs all config inversion validation checks"
42-
dependsOn(logEnvVarUsages, checkEnvVarUsage, checkConfigStrings)
28+
dependsOn(logEnvVarUsages, checkEnvVarUsage, checkConfigStrings, checkInstrumenterModule, checkDecoratorAnalytics)
4329
}
4430
}
4531
}
@@ -216,165 +202,13 @@ private fun registerCheckConfigStringsTask(project: Project, extension: Supporte
216202
}
217203
}
218204

219-
/** Collects violations for [key] against [supported] and [aliases], checking that all [expectedAliases] are values of that alias entry. */
220-
private fun collectMissingKeysAndAliases(
221-
key: String,
222-
expectedAliases: List<String>,
223-
supported: Set<String>,
224-
aliases: Map<String, List<String>>,
225-
location: String,
226-
context: String
227-
): List<String> = buildList {
228-
if (key !in supported) {
229-
add("$location -> $context: '$key' is missing from SUPPORTED")
230-
}
231-
if (key !in aliases) {
232-
add("$location -> $context: '$key' is missing from ALIASES")
233-
} else {
234-
val aliasValues = aliases[key] ?: emptyList()
235-
for (expected in expectedAliases) {
236-
if (expected !in aliasValues) {
237-
add("$location -> $context: '$expected' is missing from ALIASES['$key']")
238-
}
239-
}
240-
}
241-
}
242-
243-
/** Abstract base for tasks that scan instrumentation source files against the generated config class. */
244-
abstract class InstrumentationConfigCheckTask : DefaultTask() {
245-
@get:InputFiles
246-
abstract val mainSourceSetOutput: ConfigurableFileCollection
247-
248-
@get:InputFiles
249-
abstract val instrumentationFiles: ConfigurableFileCollection
250-
251-
@get:Input
252-
abstract val generatedClassName: Property<String>
253-
254-
@get:Input
255-
abstract val errorHeader: Property<String>
256-
257-
@get:Input
258-
abstract val errorMessage: Property<String>
259-
260-
@get:Input
261-
abstract val successMessage: Property<String>
262-
263-
@TaskAction
264-
fun execute() {
265-
val configFields = loadConfigFields(mainSourceSetOutput, generatedClassName.get())
266-
267-
val parserConfig = ParserConfiguration()
268-
parserConfig.setLanguageLevel(ParserConfiguration.LanguageLevel.JAVA_8)
269-
StaticJavaParser.setConfiguration(parserConfig)
270-
271-
val repoRoot = project.rootProject.projectDir.toPath()
272-
val violations = instrumentationFiles.files.flatMap { file ->
273-
val rel = repoRoot.relativize(file.toPath()).toString()
274-
val cu: CompilationUnit = try {
275-
StaticJavaParser.parse(file)
276-
} catch (_: Exception) {
277-
return@flatMap emptyList()
278-
}
279-
collectPropertyViolations(configFields, rel, cu)
280-
}
281-
282-
if (violations.isNotEmpty()) {
283-
logger.error(errorHeader.get())
284-
violations.forEach { logger.lifecycle(it) }
285-
throw GradleException(errorMessage.get())
286-
} else {
287-
logger.info(successMessage.get())
288-
}
289-
}
290-
291-
protected abstract fun collectPropertyViolations(
292-
configFields: LoadedConfigFields, relativePath: String, cu: CompilationUnit
293-
): List<String>
294-
}
295-
296-
/** Checks that InstrumenterModule integration names have proper entries in SUPPORTED and ALIASES. */
297-
abstract class CheckInstrumenterModuleConfigTask : InstrumentationConfigCheckTask() {
298-
override fun collectPropertyViolations(
299-
configFields: LoadedConfigFields, relativePath: String, cu: CompilationUnit
300-
): List<String> {
301-
val violations = mutableListOf<String>()
302-
303-
cu.findAll(ClassOrInterfaceDeclaration::class.java).forEach classLoop@{ classDecl ->
304-
val extendsModule = classDecl.extendedTypes.any { it.toString().startsWith("InstrumenterModule") }
305-
if (!extendsModule) return@classLoop
306-
307-
classDecl.findAll(ExplicitConstructorInvocationStmt::class.java)
308-
.filter { !it.isThis }
309-
.forEach { superCall ->
310-
val names = superCall.arguments
311-
.filterIsInstance<StringLiteralExpr>()
312-
.map { it.value }
313-
val line = superCall.range.map { it.begin.line }.orElse(1)
314-
315-
for (name in names) {
316-
val normalized = name.uppercase().replace("-", "_").replace(".", "_")
317-
val enabledKey = "DD_TRACE_${normalized}_ENABLED"
318-
val context = "Integration '$name' (super arg)"
319-
val location = "$relativePath:$line"
320-
321-
violations.addAll(collectMissingKeysAndAliases(
322-
enabledKey,
323-
listOf("DD_TRACE_INTEGRATION_${normalized}_ENABLED", "DD_INTEGRATION_${normalized}_ENABLED"),
324-
configFields.supported, configFields.aliases, location, context
325-
))
326-
}
327-
}
328-
}
329-
330-
return violations
331-
}
332-
}
333-
334-
/** Checks that Decorator instrumentationNames have proper analytics entries in SUPPORTED and ALIASES. */
335-
abstract class CheckDecoratorAnalyticsConfigTask : InstrumentationConfigCheckTask() {
336-
override fun collectPropertyViolations(
337-
configFields: LoadedConfigFields, relativePath: String, cu: CompilationUnit
338-
): List<String> {
339-
val violations = mutableListOf<String>()
340-
341-
cu.findAll(MethodDeclaration::class.java)
342-
.filter { it.nameAsString == "instrumentationNames" && it.parameters.isEmpty() }
343-
.forEach { method ->
344-
val names = method.findAll(ReturnStmt::class.java).flatMap { ret ->
345-
ret.expression.map { it.findAll(StringLiteralExpr::class.java).map { s -> s.value } }
346-
.orElse(emptyList())
347-
}
348-
val line = method.range.map { it.begin.line }.orElse(1)
349-
350-
for (name in names) {
351-
val normalized = name.uppercase().replace("-", "_").replace(".", "_")
352-
val context = "Decorator instrumentationName '$name'"
353-
val location = "$relativePath:$line"
354-
355-
violations.addAll(collectMissingKeysAndAliases(
356-
"DD_TRACE_${normalized}_ANALYTICS_ENABLED",
357-
listOf("DD_${normalized}_ANALYTICS_ENABLED"),
358-
configFields.supported, configFields.aliases, location, context
359-
))
360-
violations.addAll(collectMissingKeysAndAliases(
361-
"DD_TRACE_${normalized}_ANALYTICS_SAMPLE_RATE",
362-
listOf("DD_${normalized}_ANALYTICS_SAMPLE_RATE"),
363-
configFields.supported, configFields.aliases, location, context
364-
))
365-
}
366-
}
367-
368-
return violations
369-
}
370-
}
371205

372206
/** Registers `checkInstrumenterModuleConfigurations` to verify each InstrumenterModule's integration name has proper entries in SUPPORTED and ALIASES. */
373-
private fun registerCheckInstrumenterModuleConfigurations(project: Project, extension: SupportedTracerConfigurations) {
207+
private fun registerCheckInstrumenterModuleConfigurations(project: Project, extension: SupportedTracerConfigurations): TaskProvider<CheckInstrumenterModuleConfigTask> {
374208
val ownerPath = extension.configOwnerPath
375209
val generatedFile = extension.className
376210

377-
project.tasks.register("checkInstrumenterModuleConfigurations", CheckInstrumenterModuleConfigTask::class.java) {
211+
return project.tasks.register("checkInstrumenterModuleConfigurations", CheckInstrumenterModuleConfigTask::class.java) {
378212
group = "verification"
379213
description = "Validates that InstrumenterModule integration names have corresponding entries in SUPPORTED and ALIASES"
380214

@@ -395,11 +229,11 @@ private fun registerCheckInstrumenterModuleConfigurations(project: Project, exte
395229
}
396230

397231
/** Registers `checkDecoratorAnalyticsConfigurations` to verify each BaseDecorator subclass's instrumentationNames have proper analytics entries in SUPPORTED and ALIASES. */
398-
private fun registerCheckDecoratorAnalyticsConfigurations(project: Project, extension: SupportedTracerConfigurations) {
232+
private fun registerCheckDecoratorAnalyticsConfigurations(project: Project, extension: SupportedTracerConfigurations): TaskProvider<CheckDecoratorAnalyticsConfigTask> {
399233
val ownerPath = extension.configOwnerPath
400234
val generatedFile = extension.className
401235

402-
project.tasks.register("checkDecoratorAnalyticsConfigurations", CheckDecoratorAnalyticsConfigTask::class.java) {
236+
return project.tasks.register("checkDecoratorAnalyticsConfigurations", CheckDecoratorAnalyticsConfigTask::class.java) {
403237
group = "verification"
404238
description = "Validates that Decorator instrumentationNames have corresponding analytics entries in SUPPORTED and ALIASES"
405239

Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
package datadog.gradle.plugin.config
2+
3+
import com.github.javaparser.ParserConfiguration
4+
import com.github.javaparser.StaticJavaParser
5+
import com.github.javaparser.ast.CompilationUnit
6+
import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration
7+
import com.github.javaparser.ast.body.MethodDeclaration
8+
import com.github.javaparser.ast.expr.StringLiteralExpr
9+
import com.github.javaparser.ast.stmt.ExplicitConstructorInvocationStmt
10+
import com.github.javaparser.ast.stmt.ReturnStmt
11+
import org.gradle.api.DefaultTask
12+
import org.gradle.api.GradleException
13+
import org.gradle.api.file.ConfigurableFileCollection
14+
import org.gradle.api.provider.Property
15+
import org.gradle.api.tasks.Input
16+
import org.gradle.api.tasks.InputFiles
17+
import org.gradle.api.tasks.TaskAction
18+
19+
/** Abstract base for tasks that scan instrumentation source files against the generated config class. */
20+
abstract class InstrumentationConfigCheckTask : DefaultTask() {
21+
@get:InputFiles
22+
abstract val mainSourceSetOutput: ConfigurableFileCollection
23+
24+
@get:InputFiles
25+
abstract val instrumentationFiles: ConfigurableFileCollection
26+
27+
@get:Input
28+
abstract val generatedClassName: Property<String>
29+
30+
@get:Input
31+
abstract val errorHeader: Property<String>
32+
33+
@get:Input
34+
abstract val errorMessage: Property<String>
35+
36+
@get:Input
37+
abstract val successMessage: Property<String>
38+
39+
@TaskAction
40+
fun execute() {
41+
val configFields = loadConfigFields(mainSourceSetOutput, generatedClassName.get())
42+
43+
val parserConfig = ParserConfiguration()
44+
parserConfig.setLanguageLevel(ParserConfiguration.LanguageLevel.JAVA_8)
45+
StaticJavaParser.setConfiguration(parserConfig)
46+
47+
val repoRoot = project.rootProject.projectDir.toPath()
48+
val violations = instrumentationFiles.files.flatMap { file ->
49+
val rel = repoRoot.relativize(file.toPath()).toString()
50+
val cu: CompilationUnit = try {
51+
StaticJavaParser.parse(file)
52+
} catch (_: Exception) {
53+
return@flatMap emptyList()
54+
}
55+
collectPropertyViolations(configFields, rel, cu)
56+
}
57+
58+
if (violations.isNotEmpty()) {
59+
logger.error(errorHeader.get())
60+
violations.forEach { logger.lifecycle(it) }
61+
throw GradleException(errorMessage.get())
62+
} else {
63+
logger.info(successMessage.get())
64+
}
65+
}
66+
67+
protected abstract fun collectPropertyViolations(
68+
configFields: LoadedConfigFields, relativePath: String, cu: CompilationUnit
69+
): List<String>
70+
71+
/** Collects violations for [key] against [supported] and [aliases], checking that all [expectedAliases] are values of that alias entry. */
72+
protected fun collectMissingKeysAndAliases(
73+
key: String,
74+
expectedAliases: List<String>,
75+
supported: Set<String>,
76+
aliases: Map<String, List<String>>,
77+
location: String,
78+
context: String
79+
): List<String> = buildList {
80+
if (key !in supported) {
81+
add("$location -> $context: '$key' is missing from SUPPORTED")
82+
}
83+
if (key !in aliases) {
84+
add("$location -> $context: '$key' is missing from ALIASES")
85+
} else {
86+
val aliasValues = aliases[key] ?: emptyList()
87+
for (expected in expectedAliases) {
88+
if (expected !in aliasValues) {
89+
add("$location -> $context: '$expected' is missing from ALIASES['$key']")
90+
}
91+
}
92+
}
93+
}
94+
}
95+
96+
/** Checks that InstrumenterModule integration names have proper entries in SUPPORTED and ALIASES. */
97+
abstract class CheckInstrumenterModuleConfigTask : InstrumentationConfigCheckTask() {
98+
override fun collectPropertyViolations(
99+
configFields: LoadedConfigFields, relativePath: String, cu: CompilationUnit
100+
): List<String> {
101+
val violations = mutableListOf<String>()
102+
103+
cu.findAll(ClassOrInterfaceDeclaration::class.java).forEach classLoop@{ classDecl ->
104+
val extendsModule = classDecl.extendedTypes.any { it.toString().startsWith("InstrumenterModule") }
105+
if (!extendsModule) return@classLoop
106+
107+
classDecl.findAll(ExplicitConstructorInvocationStmt::class.java)
108+
.filter { !it.isThis }
109+
.forEach { superCall ->
110+
val names = superCall.arguments
111+
.filterIsInstance<StringLiteralExpr>()
112+
.map { it.value }
113+
val line = superCall.range.map { it.begin.line }.orElse(1)
114+
115+
for (name in names) {
116+
val normalized = name.uppercase().replace("-", "_").replace(".", "_")
117+
val enabledKey = "DD_TRACE_${normalized}_ENABLED"
118+
val context = "Integration '$name' (super arg)"
119+
val location = "$relativePath:$line"
120+
121+
violations.addAll(collectMissingKeysAndAliases(
122+
enabledKey,
123+
listOf("DD_TRACE_INTEGRATION_${normalized}_ENABLED", "DD_INTEGRATION_${normalized}_ENABLED"),
124+
configFields.supported, configFields.aliases, location, context
125+
))
126+
}
127+
}
128+
}
129+
130+
return violations
131+
}
132+
}
133+
134+
/** Checks that Decorator instrumentationNames have proper analytics entries in SUPPORTED and ALIASES. */
135+
abstract class CheckDecoratorAnalyticsConfigTask : InstrumentationConfigCheckTask() {
136+
override fun collectPropertyViolations(
137+
configFields: LoadedConfigFields, relativePath: String, cu: CompilationUnit
138+
): List<String> {
139+
val violations = mutableListOf<String>()
140+
141+
cu.findAll(MethodDeclaration::class.java)
142+
.filter { it.nameAsString == "instrumentationNames" && it.parameters.isEmpty() }
143+
.forEach { method ->
144+
val names = method.findAll(ReturnStmt::class.java).flatMap { ret ->
145+
ret.expression.map { it.findAll(StringLiteralExpr::class.java).map { s -> s.value } }
146+
.orElse(emptyList())
147+
}
148+
val line = method.range.map { it.begin.line }.orElse(1)
149+
150+
for (name in names) {
151+
val normalized = name.uppercase().replace("-", "_").replace(".", "_")
152+
val context = "Decorator instrumentationName '$name'"
153+
val location = "$relativePath:$line"
154+
155+
violations.addAll(collectMissingKeysAndAliases(
156+
"DD_TRACE_${normalized}_ANALYTICS_ENABLED",
157+
listOf("DD_${normalized}_ANALYTICS_ENABLED"),
158+
configFields.supported, configFields.aliases, location, context
159+
))
160+
violations.addAll(collectMissingKeysAndAliases(
161+
"DD_TRACE_${normalized}_ANALYTICS_SAMPLE_RATE",
162+
listOf("DD_${normalized}_ANALYTICS_SAMPLE_RATE"),
163+
configFields.supported, configFields.aliases, location, context
164+
))
165+
}
166+
}
167+
168+
return violations
169+
}
170+
}

0 commit comments

Comments
 (0)