Skip to content

Commit f15b538

Browse files
committed
PLUTO-1395] Handle multiple pmd/eslint versions
1 parent 8d267ba commit f15b538

File tree

16 files changed

+394
-386
lines changed

16 files changed

+394
-386
lines changed

cmd/analyze.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -402,7 +402,7 @@ func runLizardAnalysis(workDirectory string, pathsToCheck []string, outputFile s
402402
}
403403
} else {
404404
fmt.Println("No configuration file found for Lizard, using default patterns, run init with repository token to get a custom configuration")
405-
patterns, err = tools.FetchDefaultEnabledPatterns(Lizard)
405+
patterns, err = tools.FetchDefaultEnabledPatterns(domain.Lizard)
406406
if err != nil {
407407
return fmt.Errorf("failed to fetch default patterns: %v", err)
408408
}

cmd/init.go

Lines changed: 115 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,8 @@ func configFileTemplate(tools []domain.Tool) string {
123123
toolsMap := make(map[string]bool)
124124
toolVersions := make(map[string]string)
125125

126+
toolsWithLatestVersion, _, _ := KeepToolsWithLatestVersion(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 toolsWithLatestVersion {
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+
toolsWithLatestVersion, uuidToName, familyToVersions := KeepToolsWithLatestVersion(apiTools)
250+
251+
for family, versions := range familyToVersions {
252+
if len(versions) > 1 {
253+
kept := ", "
254+
for _, tool := range toolsWithLatestVersion {
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(toolsWithLatestVersion, 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(toolsWithLatestVersion)
274271

275272
// Create main config files with all enabled API tools
276-
err = createConfigurationFiles(apiTools, false)
273+
err = createConfigurationFiles(toolsWithLatestVersion, false)
277274
if err != nil {
278275
log.Fatal(err)
279276
}
@@ -298,43 +295,48 @@ func buildRepositoryConfigurationFiles(token string) error {
298295
func 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)
305302
}
306303
fmt.Println("ESLint configuration created based on Codacy settings. Ignoring plugin rules. ESLint plugins are not supported yet.")
307-
case Trivy:
304+
case domain.Trivy:
308305
err := createTrivyConfigFile(patternConfiguration, toolsConfigDir)
309306
if err != nil {
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
}
318-
fmt.Println("PMD configuration created based on Codacy settings")
319-
case PyLint:
315+
case domain.PMD7:
316+
err := createPMD7ConfigFile(patternConfiguration, toolsConfigDir)
317+
if err != nil {
318+
return fmt.Errorf("failed to create PMD7 config: %v", err)
319+
}
320+
fmt.Println("PMD7 configuration created based on Codacy settings")
321+
case domain.PyLint:
320322
err := createPylintConfigFile(patternConfiguration, toolsConfigDir)
321323
if err != nil {
322324
return fmt.Errorf("failed to create Pylint config: %v", err)
323325
}
324326
fmt.Println("Pylint configuration created based on Codacy settings")
325-
case DartAnalyzer:
327+
case domain.DartAnalyzer:
326328
err := createDartAnalyzerConfigFile(patternConfiguration, toolsConfigDir)
327329
if err != nil {
328330
return fmt.Errorf("failed to create Dart Analyzer config: %v", err)
329331
}
330332
fmt.Println("Dart configuration created based on Codacy settings")
331-
case Semgrep:
333+
case domain.Semgrep:
332334
err := createSemgrepConfigFile(patternConfiguration, toolsConfigDir)
333335
if err != nil {
334336
return fmt.Errorf("failed to create Semgrep config: %v", err)
335337
}
336338
fmt.Println("Semgrep configuration created based on Codacy settings")
337-
case Lizard:
339+
case domain.Lizard:
338340
err := createLizardConfigFile(toolsConfigDir, patternConfiguration)
339341
if err != nil {
340342
return fmt.Errorf("failed to create Lizard config: %v", err)
@@ -345,7 +347,12 @@ func createToolFileConfigurations(tool domain.Tool, patternConfiguration []domai
345347
}
346348

347349
func createPMDConfigFile(config []domain.PatternConfiguration, toolsConfigDir string) error {
348-
pmdConfigurationString := tools.CreatePmdConfig(config)
350+
pmdConfigurationString := tools.CreatePmd6Config(config)
351+
return os.WriteFile(filepath.Join(toolsConfigDir, "ruleset.xml"), []byte(pmdConfigurationString), utils.DefaultFilePerms)
352+
}
353+
354+
func createPMD7ConfigFile(config []domain.PatternConfiguration, toolsConfigDir string) error {
355+
pmdConfigurationString := tools.CreatePmd7Config(config)
349356
return os.WriteFile(filepath.Join(toolsConfigDir, "ruleset.xml"), []byte(pmdConfigurationString), utils.DefaultFilePerms)
350357
}
351358

@@ -429,37 +436,39 @@ func createLizardConfigFile(toolsConfigDir string, patternConfiguration []domain
429436

430437
// buildDefaultConfigurationFiles creates default configuration files for all tools
431438
func buildDefaultConfigurationFiles(toolsConfigDir string) error {
432-
for _, tool := range AvailableTools {
439+
for tool := range domain.SupportedToolsMetadata {
433440
patternsConfig, err := codacyclient.GetDefaultToolPatternsConfig(initFlags, tool)
434441
if err != nil {
435442
return fmt.Errorf("failed to get default tool patterns config: %w", err)
436443
}
437444
switch tool {
438-
case ESLint:
445+
case domain.ESLint:
439446
if err := tools.CreateEslintConfig(toolsConfigDir, patternsConfig); err != nil {
440447
return fmt.Errorf("failed to create eslint config file: %v", err)
441448
}
442-
case Trivy:
449+
case domain.Trivy:
443450
if err := createTrivyConfigFile(patternsConfig, toolsConfigDir); err != nil {
444451
return fmt.Errorf("failed to create default Trivy configuration: %w", err)
445452
}
446-
case PMD:
453+
case domain.PMD:
447454
if err := createPMDConfigFile(patternsConfig, toolsConfigDir); err != nil {
448455
return fmt.Errorf("failed to create default PMD configuration: %w", err)
449456
}
450-
case PyLint:
457+
case domain.PMD7, domain.ESLint9:
458+
continue
459+
case domain.PyLint:
451460
if err := createPylintConfigFile(patternsConfig, toolsConfigDir); err != nil {
452461
return fmt.Errorf("failed to create default Pylint configuration: %w", err)
453462
}
454-
case DartAnalyzer:
463+
case domain.DartAnalyzer:
455464
if err := createDartAnalyzerConfigFile(patternsConfig, toolsConfigDir); err != nil {
456465
return fmt.Errorf("failed to create default Dart Analyzer configuration: %w", err)
457466
}
458-
case Semgrep:
467+
case domain.Semgrep:
459468
if err := createSemgrepConfigFile(patternsConfig, toolsConfigDir); err != nil {
460469
return fmt.Errorf("failed to create default Semgrep configuration: %w", err)
461470
}
462-
case Lizard:
471+
case domain.Lizard:
463472
if err := createLizardConfigFile(toolsConfigDir, patternsConfig); err != nil {
464473
return fmt.Errorf("failed to create default Lizard configuration: %w", err)
465474
}
@@ -468,23 +477,50 @@ func buildDefaultConfigurationFiles(toolsConfigDir string) error {
468477
return nil
469478
}
470479

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-
)
480+
// KeepToolsWithLatestVersion filters the tools to keep only the latest version of each tool family.
481+
func KeepToolsWithLatestVersion(tools []domain.Tool) (
482+
toolsWithLatestVersion []domain.Tool,
483+
uuidToName map[string]string,
484+
familyToVersions map[string][]string,
485+
) {
486+
latestTools := map[string]domain.Tool{}
487+
uuidToName = map[string]string{}
488+
seen := map[string][]domain.Tool{}
489+
familyToVersions = map[string][]string{}
490+
491+
for _, tool := range tools {
492+
meta, ok := domain.SupportedToolsMetadata[tool.Uuid]
493+
if !ok {
494+
continue
495+
}
496+
497+
// Track all tools seen per family
498+
seen[meta.Name] = append(seen[meta.Name], tool)
499+
500+
// Pick the best version
501+
current, exists := latestTools[meta.Name]
502+
if !exists || domain.SupportedToolsMetadata[current.Uuid].Priority > meta.Priority {
503+
latestTools[meta.Name] = tool
504+
uuidToName[tool.Uuid] = meta.Name
505+
}
506+
}
507+
508+
// Populate final list and version map for logging
509+
for family, tools := range seen {
510+
var versions []string
511+
for _, t := range tools {
512+
v := t.Version
513+
if v == "" {
514+
v = "(unknown)"
515+
}
516+
versions = append(versions, v)
517+
}
518+
familyToVersions[family] = versions
519+
}
520+
521+
for _, tool := range latestTools {
522+
toolsWithLatestVersion = append(toolsWithLatestVersion, tool)
523+
}
480524

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,
525+
return
490526
}

cmd/init_test.go

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ func TestConfigFileTemplate(t *testing.T) {
3535
name: "only eslint enabled",
3636
tools: []domain.Tool{
3737
{
38-
Uuid: ESLint,
38+
Uuid: domain.ESLint,
3939
Name: "eslint",
4040
Version: "9.4.0",
4141
},
@@ -55,7 +55,7 @@ func TestConfigFileTemplate(t *testing.T) {
5555
name: "only pylint enabled",
5656
tools: []domain.Tool{
5757
{
58-
Uuid: PyLint,
58+
Uuid: domain.PyLint,
5959
Name: "pylint",
6060
Version: "3.4.0",
6161
},
@@ -75,12 +75,12 @@ func TestConfigFileTemplate(t *testing.T) {
7575
name: "eslint and trivy enabled",
7676
tools: []domain.Tool{
7777
{
78-
Uuid: ESLint,
78+
Uuid: domain.ESLint,
7979
Name: "eslint",
8080
Version: "9.4.0",
8181
},
8282
{
83-
Uuid: Trivy,
83+
Uuid: domain.Trivy,
8484
Name: "trivy",
8585
Version: "0.60.0",
8686
},
@@ -100,22 +100,22 @@ func TestConfigFileTemplate(t *testing.T) {
100100
name: "all tools enabled",
101101
tools: []domain.Tool{
102102
{
103-
Uuid: ESLint,
103+
Uuid: domain.ESLint,
104104
Name: "eslint",
105105
Version: "9.4.0",
106106
},
107107
{
108-
Uuid: Trivy,
108+
Uuid: domain.Trivy,
109109
Name: "trivy",
110110
Version: "0.60.0",
111111
},
112112
{
113-
Uuid: PyLint,
113+
Uuid: domain.PyLint,
114114
Name: "pylint",
115115
Version: "3.4.0",
116116
},
117117
{
118-
Uuid: PMD,
118+
Uuid: domain.PMD,
119119
Name: "pmd",
120120
Version: "6.56.0",
121121
},

0 commit comments

Comments
 (0)