Skip to content

Commit 9b1d125

Browse files
authored
[PLUTO-1395] Handle multiple pmd/eslint versions (#131)
1 parent ca92c23 commit 9b1d125

File tree

16 files changed

+394
-313
lines changed

16 files changed

+394
-313
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: 106 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -153,17 +153,6 @@ func createConfigurationFiles(tools []domain.Tool, cliLocalMode bool) error {
153153
return nil
154154
}
155155

156-
// Map tool UUIDs to their names
157-
var toolNameMap = map[string]string{
158-
ESLint: "eslint",
159-
Trivy: "trivy",
160-
PyLint: "pylint",
161-
PMD: "pmd",
162-
DartAnalyzer: "dartanalyzer",
163-
Semgrep: "semgrep",
164-
Lizard: "lizard",
165-
}
166-
167156
// RuntimePluginConfig holds the structure of the runtime plugin.yaml file
168157
type RuntimePluginConfig struct {
169158
Name string `yaml:"name"`
@@ -176,6 +165,8 @@ func configFileTemplate(tools []domain.Tool) string {
176165
toolsMap := make(map[string]bool)
177166
toolVersions := make(map[string]string)
178167

168+
toolsWithLatestVersion, _, _ := KeepToolsWithLatestVersion(tools)
169+
179170
// Track needed runtimes
180171
neededRuntimes := make(map[string]bool)
181172

@@ -189,23 +180,23 @@ func configFileTemplate(tools []domain.Tool) string {
189180
runtimeDependencies := plugins.GetToolRuntimeDependencies()
190181

191182
// Build map of enabled tools with their versions
192-
for _, tool := range tools {
183+
for _, tool := range toolsWithLatestVersion {
193184
toolsMap[tool.Uuid] = true
194185
if tool.Version != "" {
195186
toolVersions[tool.Uuid] = tool.Version
196187
} else {
197-
toolName := toolNameMap[tool.Uuid]
198-
if defaultVersion, ok := defaultVersions[toolName]; ok {
199-
toolVersions[tool.Uuid] = defaultVersion
188+
if meta, ok := domain.SupportedToolsMetadata[tool.Uuid]; ok {
189+
if defaultVersion, ok := defaultVersions[meta.Name]; ok {
190+
toolVersions[tool.Uuid] = defaultVersion
191+
}
200192
}
201193
}
202194

203195
// Get the tool's runtime dependency
204-
toolName := toolNameMap[tool.Uuid]
205-
if toolName != "" {
206-
if runtime, ok := runtimeDependencies[toolName]; ok {
196+
if meta, ok := domain.SupportedToolsMetadata[tool.Uuid]; ok {
197+
if runtime, ok := runtimeDependencies[meta.Name]; ok {
207198
// Handle special case for dartanalyzer which can use either dart or flutter
208-
if toolName == "dartanalyzer" {
199+
if meta.Name == "dartanalyzer" {
209200
// For now, default to dart runtime
210201
neededRuntimes["dart"] = true
211202
} else {
@@ -250,38 +241,33 @@ func configFileTemplate(tools []domain.Tool) string {
250241
}
251242
}
252243
}
253-
254-
// Create a sorted slice of runtimes
255244
var sortedRuntimes []string
256245
for runtime := range neededRuntimes {
257246
sortedRuntimes = append(sortedRuntimes, runtime)
258247
}
259248
sort.Strings(sortedRuntimes)
260-
261-
// Write sorted runtimes
262249
for _, runtime := range sortedRuntimes {
263250
sb.WriteString(fmt.Sprintf(" - %s@%s\n", runtime, runtimeVersions[runtime]))
264251
}
265252
}
266253

267254
sb.WriteString("tools:\n")
268255

269-
// If we have tools from the API (enabled tools), use only those
270256
if len(tools) > 0 {
271257
// Create a sorted slice of tool names
272258
var sortedTools []string
273-
for uuid, name := range toolNameMap {
259+
for uuid, meta := range domain.SupportedToolsMetadata {
274260
if toolsMap[uuid] {
275-
sortedTools = append(sortedTools, name)
261+
sortedTools = append(sortedTools, meta.Name)
276262
}
277263
}
278264
sort.Strings(sortedTools)
279265

280266
// Write sorted tools
281267
for _, name := range sortedTools {
282268
// Find the UUID for this tool name to get its version
283-
for uuid, toolName := range toolNameMap {
284-
if toolName == name && toolsMap[uuid] {
269+
for uuid, meta := range domain.SupportedToolsMetadata {
270+
if meta.Name == name && toolsMap[uuid] {
285271
version := toolVersions[uuid]
286272
sb.WriteString(fmt.Sprintf(" - %s@%s\n", name, version))
287273
break
@@ -353,27 +339,31 @@ func buildRepositoryConfigurationFiles(token string) error {
353339
return err
354340
}
355341

356-
// Map UUID to tool shortname for lookup
357-
uuidToName := map[string]string{
358-
ESLint: "eslint",
359-
Trivy: "trivy",
360-
PyLint: "pylint",
361-
PMD: "pmd",
362-
DartAnalyzer: "dartanalyzer",
363-
Lizard: "lizard",
364-
Semgrep: "semgrep",
342+
toolsWithLatestVersion, uuidToName, familyToVersions := KeepToolsWithLatestVersion(apiTools)
343+
344+
for family, versions := range familyToVersions {
345+
if len(versions) > 1 {
346+
kept := ", "
347+
for _, tool := range toolsWithLatestVersion {
348+
if domain.SupportedToolsMetadata[tool.Uuid].Name == family {
349+
kept = tool.Version
350+
break
351+
}
352+
}
353+
fmt.Printf("⚠️ Multiple versions of '%s' detected: [%s], keeping %s\n", family, strings.Join(versions, ", "), kept)
354+
}
365355
}
366356

367357
// Generate languages configuration based on API tools response
368-
if err := tools.CreateLanguagesConfigFile(apiTools, toolsConfigDir, uuidToName, initFlags); err != nil {
358+
if err := tools.CreateLanguagesConfigFile(toolsWithLatestVersion, toolsConfigDir, uuidToName, initFlags); err != nil {
369359
return fmt.Errorf("failed to create languages configuration file: %w", err)
370360
}
371361

372362
// Filter out any tools that use configuration file
373-
configuredToolsWithUI := tools.FilterToolsByConfigUsage(apiTools)
363+
configuredToolsWithUI := tools.FilterToolsByConfigUsage(toolsWithLatestVersion)
374364

375365
// Create main config files with all enabled API tools
376-
err = createConfigurationFiles(apiTools, false)
366+
err = createConfigurationFiles(toolsWithLatestVersion, false)
377367
if err != nil {
378368
log.Fatal(err)
379369
}
@@ -398,43 +388,48 @@ func buildRepositoryConfigurationFiles(token string) error {
398388
func createToolFileConfigurations(tool domain.Tool, patternConfiguration []domain.PatternConfiguration) error {
399389
toolsConfigDir := config.Config.ToolsConfigDirectory()
400390
switch tool.Uuid {
401-
case ESLint:
391+
case domain.ESLint, domain.ESLint9:
402392
err := tools.CreateEslintConfig(toolsConfigDir, patternConfiguration)
403393
if err != nil {
404394
return fmt.Errorf("failed to write eslint config: %v", err)
405395
}
406396
fmt.Println("ESLint configuration created based on Codacy settings. Ignoring plugin rules. ESLint plugins are not supported yet.")
407-
case Trivy:
397+
case domain.Trivy:
408398
err := createTrivyConfigFile(patternConfiguration, toolsConfigDir)
409399
if err != nil {
410400
return fmt.Errorf("failed to create Trivy config: %v", err)
411401
}
412402
fmt.Println("Trivy configuration created based on Codacy settings")
413-
case PMD:
403+
case domain.PMD:
414404
err := createPMDConfigFile(patternConfiguration, toolsConfigDir)
415405
if err != nil {
416406
return fmt.Errorf("failed to create PMD config: %v", err)
417407
}
418-
fmt.Println("PMD configuration created based on Codacy settings")
419-
case PyLint:
408+
case domain.PMD7:
409+
err := createPMD7ConfigFile(patternConfiguration, toolsConfigDir)
410+
if err != nil {
411+
return fmt.Errorf("failed to create PMD7 config: %v", err)
412+
}
413+
fmt.Println("PMD7 configuration created based on Codacy settings")
414+
case domain.PyLint:
420415
err := createPylintConfigFile(patternConfiguration, toolsConfigDir)
421416
if err != nil {
422417
return fmt.Errorf("failed to create Pylint config: %v", err)
423418
}
424419
fmt.Println("Pylint configuration created based on Codacy settings")
425-
case DartAnalyzer:
420+
case domain.DartAnalyzer:
426421
err := createDartAnalyzerConfigFile(patternConfiguration, toolsConfigDir)
427422
if err != nil {
428423
return fmt.Errorf("failed to create Dart Analyzer config: %v", err)
429424
}
430425
fmt.Println("Dart configuration created based on Codacy settings")
431-
case Semgrep:
426+
case domain.Semgrep:
432427
err := createSemgrepConfigFile(patternConfiguration, toolsConfigDir)
433428
if err != nil {
434429
return fmt.Errorf("failed to create Semgrep config: %v", err)
435430
}
436431
fmt.Println("Semgrep configuration created based on Codacy settings")
437-
case Lizard:
432+
case domain.Lizard:
438433
err := createLizardConfigFile(toolsConfigDir, patternConfiguration)
439434
if err != nil {
440435
return fmt.Errorf("failed to create Lizard config: %v", err)
@@ -445,7 +440,12 @@ func createToolFileConfigurations(tool domain.Tool, patternConfiguration []domai
445440
}
446441

447442
func createPMDConfigFile(config []domain.PatternConfiguration, toolsConfigDir string) error {
448-
pmdConfigurationString := tools.CreatePmdConfig(config)
443+
pmdConfigurationString := tools.CreatePmd6Config(config)
444+
return os.WriteFile(filepath.Join(toolsConfigDir, "ruleset.xml"), []byte(pmdConfigurationString), utils.DefaultFilePerms)
445+
}
446+
447+
func createPMD7ConfigFile(config []domain.PatternConfiguration, toolsConfigDir string) error {
448+
pmdConfigurationString := tools.CreatePmd7Config(config)
449449
return os.WriteFile(filepath.Join(toolsConfigDir, "ruleset.xml"), []byte(pmdConfigurationString), utils.DefaultFilePerms)
450450
}
451451

@@ -529,62 +529,91 @@ func createLizardConfigFile(toolsConfigDir string, patternConfiguration []domain
529529

530530
// buildDefaultConfigurationFiles creates default configuration files for all tools
531531
func buildDefaultConfigurationFiles(toolsConfigDir string) error {
532-
for _, tool := range AvailableTools {
533-
patternsConfig, err := codacyclient.GetDefaultToolPatternsConfig(initFlags, tool)
532+
for uuid := range domain.SupportedToolsMetadata {
533+
patternsConfig, err := codacyclient.GetDefaultToolPatternsConfig(initFlags, uuid)
534534
if err != nil {
535535
return fmt.Errorf("failed to get default tool patterns config: %w", err)
536536
}
537-
switch tool {
538-
case ESLint:
537+
switch uuid {
538+
case domain.ESLint:
539539
if err := tools.CreateEslintConfig(toolsConfigDir, patternsConfig); err != nil {
540540
return fmt.Errorf("failed to create eslint config file: %v", err)
541541
}
542-
case Trivy:
542+
case domain.Trivy:
543543
if err := createTrivyConfigFile(patternsConfig, toolsConfigDir); err != nil {
544544
return fmt.Errorf("failed to create default Trivy configuration: %w", err)
545545
}
546-
case PMD:
546+
case domain.PMD:
547547
if err := createPMDConfigFile(patternsConfig, toolsConfigDir); err != nil {
548548
return fmt.Errorf("failed to create default PMD configuration: %w", err)
549549
}
550-
case PyLint:
550+
case domain.PyLint:
551551
if err := createPylintConfigFile(patternsConfig, toolsConfigDir); err != nil {
552552
return fmt.Errorf("failed to create default Pylint configuration: %w", err)
553553
}
554-
case DartAnalyzer:
554+
case domain.DartAnalyzer:
555555
if err := createDartAnalyzerConfigFile(patternsConfig, toolsConfigDir); err != nil {
556556
return fmt.Errorf("failed to create default Dart Analyzer configuration: %w", err)
557557
}
558-
case Semgrep:
558+
case domain.Semgrep:
559559
if err := createSemgrepConfigFile(patternsConfig, toolsConfigDir); err != nil {
560560
return fmt.Errorf("failed to create default Semgrep configuration: %w", err)
561561
}
562-
case Lizard:
562+
case domain.Lizard:
563563
if err := createLizardConfigFile(toolsConfigDir, patternsConfig); err != nil {
564564
return fmt.Errorf("failed to create default Lizard configuration: %w", err)
565565
}
566+
case domain.PMD7, domain.ESLint9:
567+
continue
566568
}
567569
}
568570
return nil
569571
}
570572

571-
const (
572-
ESLint string = "f8b29663-2cb2-498d-b923-a10c6a8c05cd"
573-
Trivy string = "2fd7fbe0-33f9-4ab3-ab73-e9b62404e2cb"
574-
PMD string = "9ed24812-b6ee-4a58-9004-0ed183c45b8f"
575-
PyLint string = "31677b6d-4ae0-4f56-8041-606a8d7a8e61"
576-
DartAnalyzer string = "d203d615-6cf1-41f9-be5f-e2f660f7850f"
577-
Semgrep string = "6792c561-236d-41b7-ba5e-9d6bee0d548b"
578-
Lizard string = "76348462-84b3-409a-90d3-955e90abfb87"
579-
)
573+
// KeepToolsWithLatestVersion filters the tools to keep only the latest version of each tool family.
574+
func KeepToolsWithLatestVersion(tools []domain.Tool) (
575+
toolsWithLatestVersion []domain.Tool,
576+
uuidToName map[string]string,
577+
familyToVersions map[string][]string,
578+
) {
579+
latestTools := map[string]domain.Tool{}
580+
uuidToName = map[string]string{}
581+
seen := map[string][]domain.Tool{}
582+
familyToVersions = map[string][]string{}
583+
584+
for _, tool := range tools {
585+
meta, ok := domain.SupportedToolsMetadata[tool.Uuid]
586+
if !ok {
587+
continue
588+
}
589+
590+
// Track all tools seen per family
591+
seen[meta.Name] = append(seen[meta.Name], tool)
592+
593+
// Pick the best version
594+
current, exists := latestTools[meta.Name]
595+
if !exists || domain.SupportedToolsMetadata[current.Uuid].Priority > meta.Priority {
596+
latestTools[meta.Name] = tool
597+
uuidToName[tool.Uuid] = meta.Name
598+
}
599+
}
600+
601+
// Populate final list and version map for logging
602+
for family, tools := range seen {
603+
var versions []string
604+
for _, t := range tools {
605+
v := t.Version
606+
if v == "" {
607+
v = "(unknown)"
608+
}
609+
versions = append(versions, v)
610+
}
611+
familyToVersions[family] = versions
612+
}
613+
614+
for _, tool := range latestTools {
615+
toolsWithLatestVersion = append(toolsWithLatestVersion, tool)
616+
}
580617

581-
// AvailableTools lists all tool UUIDs supported by Codacy CLI.
582-
var AvailableTools = []string{
583-
ESLint,
584-
Trivy,
585-
PMD,
586-
PyLint,
587-
DartAnalyzer,
588-
Semgrep,
589-
Lizard,
618+
return
590619
}

0 commit comments

Comments
 (0)