Skip to content

Commit d7077a6

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

File tree

10 files changed

+349
-306
lines changed

10 files changed

+349
-306
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: 113 additions & 75 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+
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 {
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)
@@ -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+
352360
func 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
431439
func 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
}

domain/tool.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,3 +16,32 @@ type Tool struct {
1616
UsesConfigurationFile bool `json:"usesConfigurationFile"`
1717
} `json:"settings"`
1818
}
19+
20+
const (
21+
ESLint string = "f8b29663-2cb2-498d-b923-a10c6a8c05cd"
22+
ESLint9 string = "2a30ab97-477f-4769-8b88-af596ce7a94c"
23+
Trivy string = "2fd7fbe0-33f9-4ab3-ab73-e9b62404e2cb"
24+
PMD string = "9ed24812-b6ee-4a58-9004-0ed183c45b8f"
25+
PMD7 string = "ed7e8287-707d-485a-a0cb-e211004432c2"
26+
PyLint string = "31677b6d-4ae0-4f56-8041-606a8d7a8e61"
27+
DartAnalyzer string = "d203d615-6cf1-41f9-be5f-e2f660f7850f"
28+
Semgrep string = "6792c561-236d-41b7-ba5e-9d6bee0d548b"
29+
Lizard string = "76348462-84b3-409a-90d3-955e90abfb87"
30+
)
31+
32+
type ToolInfo struct {
33+
Name string
34+
Priority int // lower means newer
35+
}
36+
37+
var SupportedToolsMetadata = map[string]ToolInfo{
38+
ESLint9: {Name: "eslint", Priority: 0},
39+
ESLint: {Name: "eslint", Priority: 1},
40+
PMD7: {Name: "pmd", Priority: 0},
41+
PMD: {Name: "pmd", Priority: 1},
42+
PyLint: {Name: "pylint", Priority: 0},
43+
Trivy: {Name: "trivy", Priority: 0},
44+
DartAnalyzer: {Name: "dartanalyzer", Priority: 0},
45+
Lizard: {Name: "lizard", Priority: 0},
46+
Semgrep: {Name: "semgrep", Priority: 0},
47+
}

plugins/tool-utils.go

Lines changed: 0 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -347,35 +347,3 @@ func getDownloadURL(urlTemplate string, fileName string, version string, mappedA
347347

348348
return buf.String()
349349
}
350-
351-
// GetSupportedTools returns a map of supported tool names based on the tools folder
352-
func GetSupportedTools() (map[string]struct{}, error) {
353-
supportedTools := make(map[string]struct{})
354-
355-
// Read all directories in the tools folder
356-
entries, err := toolsFS.ReadDir("tools")
357-
if err != nil {
358-
return nil, fmt.Errorf("failed to read tools directory: %w", err)
359-
}
360-
361-
// For each directory, check if it has a plugin.yaml file
362-
for _, entry := range entries {
363-
if !entry.IsDir() {
364-
continue
365-
}
366-
367-
toolName := entry.Name()
368-
// Always use forward slashes for embedded filesystem paths
369-
pluginPath := fmt.Sprintf("tools/%s/plugin.yaml", toolName)
370-
371-
// Check if plugin.yaml exists
372-
_, err := toolsFS.ReadFile(pluginPath)
373-
if err != nil {
374-
continue // Skip if no plugin.yaml
375-
}
376-
377-
supportedTools[toolName] = struct{}{}
378-
}
379-
380-
return supportedTools, nil
381-
}

0 commit comments

Comments
 (0)