@@ -123,6 +123,8 @@ func configFileTemplate(tools []domain.Tool) string {
123123 toolsMap := make (map [string ]bool )
124124 toolVersions := make (map [string ]string )
125125
126+ dedupedTools , _ , _ := DeduplicateTools (tools )
127+
126128 // Track needed runtimes
127129 needsNode := false
128130 needsPython := false
@@ -131,17 +133,17 @@ func configFileTemplate(tools []domain.Tool) string {
131133
132134 // Default versions
133135 defaultVersions := map [string ]string {
134- ESLint : "8.57.0" ,
135- Trivy : "0.59.1" ,
136- PyLint : "3.3.6" ,
137- PMD : "6.55.0" ,
138- DartAnalyzer : "3.7.2" ,
139- Semgrep : "1.78.0" ,
140- Lizard : "1.17.19" ,
136+ domain . ESLint : "8.57.0" ,
137+ domain . Trivy : "0.59.1" ,
138+ domain . PyLint : "3.3.6" ,
139+ domain . PMD : "6.55.0" ,
140+ domain . DartAnalyzer : "3.7.2" ,
141+ domain . Semgrep : "1.78.0" ,
142+ domain . Lizard : "1.17.19" ,
141143 }
142144
143145 // Build map of enabled tools with their versions
144- for _ , tool := range tools {
146+ for _ , tool := range dedupedTools {
145147 toolsMap [tool .Uuid ] = true
146148 if tool .Version != "" {
147149 toolVersions [tool .Uuid ] = tool .Version
@@ -150,13 +152,14 @@ func configFileTemplate(tools []domain.Tool) string {
150152 }
151153
152154 // Check if tool needs a runtime
153- if tool .Uuid == ESLint {
155+ switch tool .Uuid {
156+ case domain .ESLint , domain .ESLint9 :
154157 needsNode = true
155- } else if tool . Uuid == PyLint || tool . Uuid == Lizard {
158+ case domain . PyLint , domain . Lizard :
156159 needsPython = true
157- } else if tool . Uuid == DartAnalyzer {
160+ case domain . DartAnalyzer :
158161 needsDart = true
159- } else if tool . Uuid == PMD {
162+ case domain . PMD , domain . PMD7 :
160163 needsJava = true
161164 }
162165 }
@@ -191,31 +194,21 @@ func configFileTemplate(tools []domain.Tool) string {
191194
192195 // If we have tools from the API (enabled tools), use only those
193196 if len (tools ) > 0 {
194- // Add only the tools that are in the API response (enabled tools)
195- uuidToName := map [string ]string {
196- ESLint : "eslint" ,
197- Trivy : "trivy" ,
198- PyLint : "pylint" ,
199- PMD : "pmd" ,
200- DartAnalyzer : "dartanalyzer" ,
201- Semgrep : "semgrep" ,
202- Lizard : "lizard" ,
203- }
204-
205- for uuid , name := range uuidToName {
197+ for uuid , meta := range domain .SupportedToolsMetadata {
206198 if toolsMap [uuid ] {
207- sb .WriteString (fmt .Sprintf (" - %s@%s\n " , name , toolVersions [uuid ]))
199+ sb .WriteString (fmt .Sprintf (" - %s@%s\n " , meta . Name , toolVersions [uuid ]))
208200 }
209201 }
202+
210203 } else {
211204 // If no tools were specified (local mode), include all defaults
212- sb .WriteString (fmt .Sprintf (" - eslint@%s\n " , defaultVersions [ESLint ]))
213- sb .WriteString (fmt .Sprintf (" - trivy@%s\n " , defaultVersions [Trivy ]))
214- sb .WriteString (fmt .Sprintf (" - pylint@%s\n " , defaultVersions [PyLint ]))
215- sb .WriteString (fmt .Sprintf (" - pmd@%s\n " , defaultVersions [PMD ]))
216- sb .WriteString (fmt .Sprintf (" - dartanalyzer@%s\n " , defaultVersions [DartAnalyzer ]))
217- sb .WriteString (fmt .Sprintf (" - semgrep@%s\n " , defaultVersions [Semgrep ]))
218- sb .WriteString (fmt .Sprintf (" - lizard@%s\n " , defaultVersions [Lizard ]))
205+ sb .WriteString (fmt .Sprintf (" - eslint@%s\n " , defaultVersions [domain . ESLint ]))
206+ sb .WriteString (fmt .Sprintf (" - trivy@%s\n " , defaultVersions [domain . Trivy ]))
207+ sb .WriteString (fmt .Sprintf (" - pylint@%s\n " , defaultVersions [domain . PyLint ]))
208+ sb .WriteString (fmt .Sprintf (" - pmd@%s\n " , defaultVersions [domain . PMD ]))
209+ sb .WriteString (fmt .Sprintf (" - dartanalyzer@%s\n " , defaultVersions [domain . DartAnalyzer ]))
210+ sb .WriteString (fmt .Sprintf (" - semgrep@%s\n " , defaultVersions [domain . Semgrep ]))
211+ sb .WriteString (fmt .Sprintf (" - lizard@%s\n " , defaultVersions [domain . Lizard ]))
219212 }
220213
221214 return sb .String ()
@@ -253,27 +246,31 @@ func buildRepositoryConfigurationFiles(token string) error {
253246 return err
254247 }
255248
256- // Map UUID to tool shortname for lookup
257- uuidToName := map [string ]string {
258- ESLint : "eslint" ,
259- Trivy : "trivy" ,
260- PyLint : "pylint" ,
261- PMD : "pmd" ,
262- DartAnalyzer : "dartanalyzer" ,
263- Lizard : "lizard" ,
264- Semgrep : "semgrep" ,
249+ dedupedTools , uuidToName , familyToVersions := DeduplicateTools (apiTools )
250+
251+ for family , versions := range familyToVersions {
252+ if len (versions ) > 1 {
253+ kept := ", "
254+ for _ , tool := range dedupedTools {
255+ if domain .SupportedToolsMetadata [tool .Uuid ].Name == family {
256+ kept = tool .Version
257+ break
258+ }
259+ }
260+ fmt .Printf ("⚠️ Multiple versions of '%s' detected: [%s], keeping %s\n " , family , strings .Join (versions , ", " ), kept )
261+ }
265262 }
266263
267264 // Generate languages configuration based on API tools response
268- if err := tools .CreateLanguagesConfigFile (apiTools , toolsConfigDir , uuidToName , initFlags ); err != nil {
265+ if err := tools .CreateLanguagesConfigFile (dedupedTools , toolsConfigDir , uuidToName , initFlags ); err != nil {
269266 return fmt .Errorf ("failed to create languages configuration file: %w" , err )
270267 }
271268
272269 // Filter out any tools that use configuration file
273- configuredToolsWithUI := tools .FilterToolsByConfigUsage (apiTools )
270+ configuredToolsWithUI := tools .FilterToolsByConfigUsage (dedupedTools )
274271
275272 // Create main config files with all enabled API tools
276- err = createConfigurationFiles (apiTools , false )
273+ err = createConfigurationFiles (dedupedTools , false )
277274 if err != nil {
278275 log .Fatal (err )
279276 }
@@ -298,7 +295,7 @@ func buildRepositoryConfigurationFiles(token string) error {
298295func createToolFileConfigurations (tool domain.Tool , patternConfiguration []domain.PatternConfiguration ) error {
299296 toolsConfigDir := config .Config .ToolsConfigDirectory ()
300297 switch tool .Uuid {
301- case ESLint :
298+ case domain . ESLint , domain . ESLint9 :
302299 err := tools .CreateEslintConfig (toolsConfigDir , patternConfiguration )
303300 if err != nil {
304301 return fmt .Errorf ("failed to write eslint config: %v" , err )
@@ -310,31 +307,32 @@ func createToolFileConfigurations(tool domain.Tool, patternConfiguration []domai
310307 return fmt .Errorf ("failed to create Trivy config: %v" , err )
311308 }
312309 fmt .Println ("Trivy configuration created based on Codacy settings" )
313- case PMD :
310+ case domain . PMD :
314311 err := createPMDConfigFile (patternConfiguration , toolsConfigDir )
315312 if err != nil {
316313 return fmt .Errorf ("failed to create PMD config: %v" , err )
317314 }
315+
318316 fmt .Println ("PMD configuration created based on Codacy settings" )
319317 case PyLint :
320318 err := createPylintConfigFile (patternConfiguration , toolsConfigDir )
321319 if err != nil {
322320 return fmt .Errorf ("failed to create Pylint config: %v" , err )
323321 }
324322 fmt .Println ("Pylint configuration created based on Codacy settings" )
325- case DartAnalyzer :
323+ case domain . DartAnalyzer :
326324 err := createDartAnalyzerConfigFile (patternConfiguration , toolsConfigDir )
327325 if err != nil {
328326 return fmt .Errorf ("failed to create Dart Analyzer config: %v" , err )
329327 }
330328 fmt .Println ("Dart configuration created based on Codacy settings" )
331- case Semgrep :
329+ case domain . Semgrep :
332330 err := createSemgrepConfigFile (patternConfiguration , toolsConfigDir )
333331 if err != nil {
334332 return fmt .Errorf ("failed to create Semgrep config: %v" , err )
335333 }
336334 fmt .Println ("Semgrep configuration created based on Codacy settings" )
337- case Lizard :
335+ case domain . Lizard :
338336 err := createLizardConfigFile (toolsConfigDir , patternConfiguration )
339337 if err != nil {
340338 return fmt .Errorf ("failed to create Lizard config: %v" , err )
@@ -349,6 +347,16 @@ func createPMDConfigFile(config []domain.PatternConfiguration, toolsConfigDir st
349347 return os .WriteFile (filepath .Join (toolsConfigDir , "ruleset.xml" ), []byte (pmdConfigurationString ), utils .DefaultFilePerms )
350348}
351349
350+ func createPMD7ConfigFile (config []domain.PatternConfiguration , toolsConfigDir string ) error {
351+ pmdConfigurationString := tools .CreatePmd7Config (config )
352+ return os .WriteFile (filepath .Join (toolsConfigDir , "ruleset.xml" ), []byte (pmdConfigurationString ), utils .DefaultFilePerms )
353+ }
354+
355+ func createDefaultPMD7ConfigFile (toolsConfigDir string ) error {
356+ pmdConfigurationString := tools .CreatePmd7Config ([]domain.PatternConfiguration {})
357+ return os .WriteFile (filepath .Join (toolsConfigDir , "ruleset.xml" ), []byte (pmdConfigurationString ), utils .DefaultFilePerms )
358+ }
359+
352360func createPylintConfigFile (config []domain.PatternConfiguration , toolsConfigDir string ) error {
353361 pylintConfigurationString := pylint .GeneratePylintRC (config )
354362 return os .WriteFile (filepath .Join (toolsConfigDir , "pylint.rc" ), []byte (pylintConfigurationString ), utils .DefaultFilePerms )
@@ -429,37 +437,41 @@ func createLizardConfigFile(toolsConfigDir string, patternConfiguration []domain
429437
430438// buildDefaultConfigurationFiles creates default configuration files for all tools
431439func buildDefaultConfigurationFiles (toolsConfigDir string ) error {
432- for _ , tool := range AvailableTools {
440+ for tool := range domain . SupportedToolsMetadata {
433441 patternsConfig , err := codacyclient .GetDefaultToolPatternsConfig (initFlags , tool )
434442 if err != nil {
435443 return fmt .Errorf ("failed to get default tool patterns config: %w" , err )
436444 }
437445 switch tool {
438- case ESLint :
446+ case domain . ESLint , domain . ESLint9 :
439447 if err := tools .CreateEslintConfig (toolsConfigDir , patternsConfig ); err != nil {
440448 return fmt .Errorf ("failed to create eslint config file: %v" , err )
441449 }
442- case Trivy :
450+ case domain . Trivy :
443451 if err := createTrivyConfigFile (patternsConfig , toolsConfigDir ); err != nil {
444452 return fmt .Errorf ("failed to create default Trivy configuration: %w" , err )
445453 }
446- case PMD :
454+ case domain . PMD :
447455 if err := createPMDConfigFile (patternsConfig , toolsConfigDir ); err != nil {
448456 return fmt .Errorf ("failed to create default PMD configuration: %w" , err )
449457 }
450- case PyLint :
458+ case domain .PMD7 :
459+ if err := createPMD7ConfigFile (patternsConfig , toolsConfigDir ); err != nil {
460+ return fmt .Errorf ("failed to create default PMD7 configuration: %w" , err )
461+ }
462+ case domain .PyLint :
451463 if err := createPylintConfigFile (patternsConfig , toolsConfigDir ); err != nil {
452464 return fmt .Errorf ("failed to create default Pylint configuration: %w" , err )
453465 }
454- case DartAnalyzer :
466+ case domain . DartAnalyzer :
455467 if err := createDartAnalyzerConfigFile (patternsConfig , toolsConfigDir ); err != nil {
456468 return fmt .Errorf ("failed to create default Dart Analyzer configuration: %w" , err )
457469 }
458- case Semgrep :
470+ case domain . Semgrep :
459471 if err := createSemgrepConfigFile (patternsConfig , toolsConfigDir ); err != nil {
460472 return fmt .Errorf ("failed to create default Semgrep configuration: %w" , err )
461473 }
462- case Lizard :
474+ case domain . Lizard :
463475 if err := createLizardConfigFile (toolsConfigDir , patternsConfig ); err != nil {
464476 return fmt .Errorf ("failed to create default Lizard configuration: %w" , err )
465477 }
@@ -468,23 +480,49 @@ func buildDefaultConfigurationFiles(toolsConfigDir string) error {
468480 return nil
469481}
470482
471- const (
472- ESLint string = "f8b29663-2cb2-498d-b923-a10c6a8c05cd"
473- Trivy string = "2fd7fbe0-33f9-4ab3-ab73-e9b62404e2cb"
474- PMD string = "9ed24812-b6ee-4a58-9004-0ed183c45b8f"
475- PyLint string = "31677b6d-4ae0-4f56-8041-606a8d7a8e61"
476- DartAnalyzer string = "d203d615-6cf1-41f9-be5f-e2f660f7850f"
477- Semgrep string = "6792c561-236d-41b7-ba5e-9d6bee0d548b"
478- Lizard string = "76348462-84b3-409a-90d3-955e90abfb87"
479- )
483+ func DeduplicateTools (tools []domain.Tool ) (
484+ deduped []domain.Tool ,
485+ uuidToName map [string ]string ,
486+ familyToVersions map [string ][]string ,
487+ ) {
488+ dedupedMap := map [string ]domain.Tool {}
489+ uuidToName = map [string ]string {}
490+ seen := map [string ][]domain.Tool {}
491+ familyToVersions = map [string ][]string {}
492+
493+ for _ , tool := range tools {
494+ meta , ok := domain .SupportedToolsMetadata [tool .Uuid ]
495+ if ! ok {
496+ continue
497+ }
498+
499+ // Track all tools seen per family
500+ seen [meta .Name ] = append (seen [meta .Name ], tool )
501+
502+ // Pick the best version
503+ current , exists := dedupedMap [meta .Name ]
504+ if ! exists || domain .SupportedToolsMetadata [current .Uuid ].Priority > meta .Priority {
505+ dedupedMap [meta .Name ] = tool
506+ uuidToName [tool .Uuid ] = meta .Name
507+ }
508+ }
509+
510+ // Populate final deduped list and version map for logging
511+ for family , tools := range seen {
512+ var versions []string
513+ for _ , t := range tools {
514+ v := t .Version
515+ if v == "" {
516+ v = "(unknown)"
517+ }
518+ versions = append (versions , v )
519+ }
520+ familyToVersions [family ] = versions
521+ }
522+
523+ for _ , tool := range dedupedMap {
524+ deduped = append (deduped , tool )
525+ }
480526
481- // AvailableTools lists all tool UUIDs supported by Codacy CLI.
482- var AvailableTools = []string {
483- ESLint ,
484- Trivy ,
485- PMD ,
486- PyLint ,
487- DartAnalyzer ,
488- Semgrep ,
489- Lizard ,
527+ return
490528}
0 commit comments