11package datadog.gradle.plugin.config
22
3- import com.github.javaparser.ParserConfiguration
4- import com.github.javaparser.StaticJavaParser
5- import com.github.javaparser.ast.CompilationUnit
63import 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
124import 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
165import org.gradle.api.GradleException
176import org.gradle.api.Plugin
187import 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
238import org.gradle.api.Task
249import org.gradle.api.tasks.TaskProvider
2510import org.gradle.api.tasks.SourceSet
2611import org.gradle.api.tasks.SourceSetContainer
27- import org.gradle.api.tasks.TaskAction
2812import org.gradle.kotlin.dsl.getByType
2913import java.net.URLClassLoader
3014import 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
0 commit comments