Skip to content

Commit 429d90e

Browse files
committed
[PLUTO-1395] Handle multiple pmd/eslint versions
1 parent ca92c23 commit 429d90e

File tree

16 files changed

+389
-337
lines changed

16 files changed

+389
-337
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: 108 additions & 86 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 {
@@ -233,15 +224,8 @@ func configFileTemplate(tools []domain.Tool) string {
233224
sb.WriteString(fmt.Sprintf(" - %s@%s\n", runtime, runtimeVersions[runtime]))
234225
}
235226
} else {
236-
// In local mode with no tools specified, include only the necessary runtimes
237-
supportedTools, err := plugins.GetSupportedTools()
238-
if err != nil {
239-
log.Printf("Warning: failed to get supported tools: %v", err)
240-
return sb.String()
241-
}
242-
243-
// Get runtimes needed by supported tools
244-
for toolName := range supportedTools {
227+
// If no tools were specified (local mode), include all tools in sorted order
228+
for toolName := range defaultVersions {
245229
if runtime, ok := runtimeDependencies[toolName]; ok {
246230
if toolName == "dartanalyzer" {
247231
neededRuntimes["dart"] = true
@@ -250,38 +234,33 @@ func configFileTemplate(tools []domain.Tool) string {
250234
}
251235
}
252236
}
253-
254-
// Create a sorted slice of runtimes
255237
var sortedRuntimes []string
256238
for runtime := range neededRuntimes {
257239
sortedRuntimes = append(sortedRuntimes, runtime)
258240
}
259241
sort.Strings(sortedRuntimes)
260-
261-
// Write sorted runtimes
262242
for _, runtime := range sortedRuntimes {
263243
sb.WriteString(fmt.Sprintf(" - %s@%s\n", runtime, runtimeVersions[runtime]))
264244
}
265245
}
266246

267247
sb.WriteString("tools:\n")
268248

269-
// If we have tools from the API (enabled tools), use only those
270249
if len(tools) > 0 {
271250
// Create a sorted slice of tool names
272251
var sortedTools []string
273-
for uuid, name := range toolNameMap {
252+
for uuid, meta := range domain.SupportedToolsMetadata {
274253
if toolsMap[uuid] {
275-
sortedTools = append(sortedTools, name)
254+
sortedTools = append(sortedTools, meta.Name)
276255
}
277256
}
278257
sort.Strings(sortedTools)
279258

280259
// Write sorted tools
281260
for _, name := range sortedTools {
282261
// Find the UUID for this tool name to get its version
283-
for uuid, toolName := range toolNameMap {
284-
if toolName == name && toolsMap[uuid] {
262+
for uuid, meta := range domain.SupportedToolsMetadata {
263+
if meta.Name == name && toolsMap[uuid] {
285264
version := toolVersions[uuid]
286265
sb.WriteString(fmt.Sprintf(" - %s@%s\n", name, version))
287266
break
@@ -353,27 +332,31 @@ func buildRepositoryConfigurationFiles(token string) error {
353332
return err
354333
}
355334

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",
335+
toolsWithLatestVersion, uuidToName, familyToVersions := KeepToolsWithLatestVersion(apiTools)
336+
337+
for family, versions := range familyToVersions {
338+
if len(versions) > 1 {
339+
kept := ", "
340+
for _, tool := range toolsWithLatestVersion {
341+
if domain.SupportedToolsMetadata[tool.Uuid].Name == family {
342+
kept = tool.Version
343+
break
344+
}
345+
}
346+
fmt.Printf("⚠️ Multiple versions of '%s' detected: [%s], keeping %s\n", family, strings.Join(versions, ", "), kept)
347+
}
365348
}
366349

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

372355
// Filter out any tools that use configuration file
373-
configuredToolsWithUI := tools.FilterToolsByConfigUsage(apiTools)
356+
configuredToolsWithUI := tools.FilterToolsByConfigUsage(toolsWithLatestVersion)
374357

375358
// Create main config files with all enabled API tools
376-
err = createConfigurationFiles(apiTools, false)
359+
err = createConfigurationFiles(toolsWithLatestVersion, false)
377360
if err != nil {
378361
log.Fatal(err)
379362
}
@@ -398,43 +381,48 @@ func buildRepositoryConfigurationFiles(token string) error {
398381
func createToolFileConfigurations(tool domain.Tool, patternConfiguration []domain.PatternConfiguration) error {
399382
toolsConfigDir := config.Config.ToolsConfigDirectory()
400383
switch tool.Uuid {
401-
case ESLint:
384+
case domain.ESLint, domain.ESLint9:
402385
err := tools.CreateEslintConfig(toolsConfigDir, patternConfiguration)
403386
if err != nil {
404387
return fmt.Errorf("failed to write eslint config: %v", err)
405388
}
406389
fmt.Println("ESLint configuration created based on Codacy settings. Ignoring plugin rules. ESLint plugins are not supported yet.")
407-
case Trivy:
390+
case domain.Trivy:
408391
err := createTrivyConfigFile(patternConfiguration, toolsConfigDir)
409392
if err != nil {
410393
return fmt.Errorf("failed to create Trivy config: %v", err)
411394
}
412395
fmt.Println("Trivy configuration created based on Codacy settings")
413-
case PMD:
396+
case domain.PMD:
414397
err := createPMDConfigFile(patternConfiguration, toolsConfigDir)
415398
if err != nil {
416399
return fmt.Errorf("failed to create PMD config: %v", err)
417400
}
418-
fmt.Println("PMD configuration created based on Codacy settings")
419-
case PyLint:
401+
case domain.PMD7:
402+
err := createPMD7ConfigFile(patternConfiguration, toolsConfigDir)
403+
if err != nil {
404+
return fmt.Errorf("failed to create PMD7 config: %v", err)
405+
}
406+
fmt.Println("PMD7 configuration created based on Codacy settings")
407+
case domain.PyLint:
420408
err := createPylintConfigFile(patternConfiguration, toolsConfigDir)
421409
if err != nil {
422410
return fmt.Errorf("failed to create Pylint config: %v", err)
423411
}
424412
fmt.Println("Pylint configuration created based on Codacy settings")
425-
case DartAnalyzer:
413+
case domain.DartAnalyzer:
426414
err := createDartAnalyzerConfigFile(patternConfiguration, toolsConfigDir)
427415
if err != nil {
428416
return fmt.Errorf("failed to create Dart Analyzer config: %v", err)
429417
}
430418
fmt.Println("Dart configuration created based on Codacy settings")
431-
case Semgrep:
419+
case domain.Semgrep:
432420
err := createSemgrepConfigFile(patternConfiguration, toolsConfigDir)
433421
if err != nil {
434422
return fmt.Errorf("failed to create Semgrep config: %v", err)
435423
}
436424
fmt.Println("Semgrep configuration created based on Codacy settings")
437-
case Lizard:
425+
case domain.Lizard:
438426
err := createLizardConfigFile(toolsConfigDir, patternConfiguration)
439427
if err != nil {
440428
return fmt.Errorf("failed to create Lizard config: %v", err)
@@ -445,7 +433,12 @@ func createToolFileConfigurations(tool domain.Tool, patternConfiguration []domai
445433
}
446434

447435
func createPMDConfigFile(config []domain.PatternConfiguration, toolsConfigDir string) error {
448-
pmdConfigurationString := tools.CreatePmdConfig(config)
436+
pmdConfigurationString := tools.CreatePmd6Config(config)
437+
return os.WriteFile(filepath.Join(toolsConfigDir, "ruleset.xml"), []byte(pmdConfigurationString), utils.DefaultFilePerms)
438+
}
439+
440+
func createPMD7ConfigFile(config []domain.PatternConfiguration, toolsConfigDir string) error {
441+
pmdConfigurationString := tools.CreatePmd7Config(config)
449442
return os.WriteFile(filepath.Join(toolsConfigDir, "ruleset.xml"), []byte(pmdConfigurationString), utils.DefaultFilePerms)
450443
}
451444

@@ -529,37 +522,39 @@ func createLizardConfigFile(toolsConfigDir string, patternConfiguration []domain
529522

530523
// buildDefaultConfigurationFiles creates default configuration files for all tools
531524
func buildDefaultConfigurationFiles(toolsConfigDir string) error {
532-
for _, tool := range AvailableTools {
533-
patternsConfig, err := codacyclient.GetDefaultToolPatternsConfig(initFlags, tool)
525+
for uuid := range domain.SupportedToolsMetadata {
526+
patternsConfig, err := codacyclient.GetDefaultToolPatternsConfig(initFlags, uuid)
534527
if err != nil {
535528
return fmt.Errorf("failed to get default tool patterns config: %w", err)
536529
}
537-
switch tool {
538-
case ESLint:
530+
switch uuid {
531+
case domain.ESLint:
539532
if err := tools.CreateEslintConfig(toolsConfigDir, patternsConfig); err != nil {
540533
return fmt.Errorf("failed to create eslint config file: %v", err)
541534
}
542-
case Trivy:
535+
case domain.Trivy:
543536
if err := createTrivyConfigFile(patternsConfig, toolsConfigDir); err != nil {
544537
return fmt.Errorf("failed to create default Trivy configuration: %w", err)
545538
}
546-
case PMD:
539+
case domain.PMD:
547540
if err := createPMDConfigFile(patternsConfig, toolsConfigDir); err != nil {
548541
return fmt.Errorf("failed to create default PMD configuration: %w", err)
549542
}
550-
case PyLint:
543+
case domain.PMD7, domain.ESLint9:
544+
continue
545+
case domain.PyLint:
551546
if err := createPylintConfigFile(patternsConfig, toolsConfigDir); err != nil {
552547
return fmt.Errorf("failed to create default Pylint configuration: %w", err)
553548
}
554-
case DartAnalyzer:
549+
case domain.DartAnalyzer:
555550
if err := createDartAnalyzerConfigFile(patternsConfig, toolsConfigDir); err != nil {
556551
return fmt.Errorf("failed to create default Dart Analyzer configuration: %w", err)
557552
}
558-
case Semgrep:
553+
case domain.Semgrep:
559554
if err := createSemgrepConfigFile(patternsConfig, toolsConfigDir); err != nil {
560555
return fmt.Errorf("failed to create default Semgrep configuration: %w", err)
561556
}
562-
case Lizard:
557+
case domain.Lizard:
563558
if err := createLizardConfigFile(toolsConfigDir, patternsConfig); err != nil {
564559
return fmt.Errorf("failed to create default Lizard configuration: %w", err)
565560
}
@@ -568,23 +563,50 @@ func buildDefaultConfigurationFiles(toolsConfigDir string) error {
568563
return nil
569564
}
570565

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-
)
566+
// KeepToolsWithLatestVersion filters the tools to keep only the latest version of each tool family.
567+
func KeepToolsWithLatestVersion(tools []domain.Tool) (
568+
toolsWithLatestVersion []domain.Tool,
569+
uuidToName map[string]string,
570+
familyToVersions map[string][]string,
571+
) {
572+
latestTools := map[string]domain.Tool{}
573+
uuidToName = map[string]string{}
574+
seen := map[string][]domain.Tool{}
575+
familyToVersions = map[string][]string{}
576+
577+
for _, tool := range tools {
578+
meta, ok := domain.SupportedToolsMetadata[tool.Uuid]
579+
if !ok {
580+
continue
581+
}
582+
583+
// Track all tools seen per family
584+
seen[meta.Name] = append(seen[meta.Name], tool)
585+
586+
// Pick the best version
587+
current, exists := latestTools[meta.Name]
588+
if !exists || domain.SupportedToolsMetadata[current.Uuid].Priority > meta.Priority {
589+
latestTools[meta.Name] = tool
590+
uuidToName[tool.Uuid] = meta.Name
591+
}
592+
}
593+
594+
// Populate final list and version map for logging
595+
for family, tools := range seen {
596+
var versions []string
597+
for _, t := range tools {
598+
v := t.Version
599+
if v == "" {
600+
v = "(unknown)"
601+
}
602+
versions = append(versions, v)
603+
}
604+
familyToVersions[family] = versions
605+
}
606+
607+
for _, tool := range latestTools {
608+
toolsWithLatestVersion = append(toolsWithLatestVersion, tool)
609+
}
580610

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,
611+
return
590612
}

0 commit comments

Comments
 (0)