Skip to content

Commit c5d0532

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

File tree

16 files changed

+391
-407
lines changed

16 files changed

+391
-407
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: 112 additions & 95 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 {
@@ -234,16 +225,9 @@ func configFileTemplate(tools []domain.Tool) string {
234225
}
235226
} else {
236227
// 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 {
245-
if runtime, ok := runtimeDependencies[toolName]; ok {
246-
if toolName == "dartanalyzer" {
228+
for _, meta := range domain.SupportedToolsMetadata {
229+
if runtime, ok := runtimeDependencies[meta.Name]; ok {
230+
if meta.Name == "dartanalyzer" {
247231
neededRuntimes["dart"] = true
248232
} else {
249233
neededRuntimes[runtime] = true
@@ -270,18 +254,18 @@ func configFileTemplate(tools []domain.Tool) string {
270254
if len(tools) > 0 {
271255
// Create a sorted slice of tool names
272256
var sortedTools []string
273-
for uuid, name := range toolNameMap {
257+
for uuid, meta := range domain.SupportedToolsMetadata {
274258
if toolsMap[uuid] {
275-
sortedTools = append(sortedTools, name)
259+
sortedTools = append(sortedTools, meta.Name)
276260
}
277261
}
278262
sort.Strings(sortedTools)
279263

280264
// Write sorted tools
281265
for _, name := range sortedTools {
282266
// Find the UUID for this tool name to get its version
283-
for uuid, toolName := range toolNameMap {
284-
if toolName == name && toolsMap[uuid] {
267+
for uuid, meta := range domain.SupportedToolsMetadata {
268+
if meta.Name == name && toolsMap[uuid] {
285269
version := toolVersions[uuid]
286270
sb.WriteString(fmt.Sprintf(" - %s@%s\n", name, version))
287271
break
@@ -291,20 +275,10 @@ func configFileTemplate(tools []domain.Tool) string {
291275
} else {
292276
// If no tools were specified (local mode), include all tools in sorted order
293277
var sortedTools []string
294-
295-
// Get supported tools from plugin system
296-
supportedTools, err := plugins.GetSupportedTools()
297-
if err != nil {
298-
log.Printf("Warning: failed to get supported tools: %v", err)
299-
return sb.String()
300-
}
301-
302-
// Convert map keys to slice and sort them
303-
for toolName := range supportedTools {
304-
if version, ok := defaultVersions[toolName]; ok {
305-
// Skip tools without a version
278+
for _, meta := range domain.SupportedToolsMetadata {
279+
if version, ok := defaultVersions[meta.Name]; ok {
306280
if version != "" {
307-
sortedTools = append(sortedTools, toolName)
281+
sortedTools = append(sortedTools, meta.Name)
308282
}
309283
}
310284
}
@@ -353,27 +327,31 @@ func buildRepositoryConfigurationFiles(token string) error {
353327
return err
354328
}
355329

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

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

372350
// Filter out any tools that use configuration file
373-
configuredToolsWithUI := tools.FilterToolsByConfigUsage(apiTools)
351+
configuredToolsWithUI := tools.FilterToolsByConfigUsage(toolsWithLatestVersion)
374352

375353
// Create main config files with all enabled API tools
376-
err = createConfigurationFiles(apiTools, false)
354+
err = createConfigurationFiles(toolsWithLatestVersion, false)
377355
if err != nil {
378356
log.Fatal(err)
379357
}
@@ -398,43 +376,48 @@ func buildRepositoryConfigurationFiles(token string) error {
398376
func createToolFileConfigurations(tool domain.Tool, patternConfiguration []domain.PatternConfiguration) error {
399377
toolsConfigDir := config.Config.ToolsConfigDirectory()
400378
switch tool.Uuid {
401-
case ESLint:
379+
case domain.ESLint, domain.ESLint9:
402380
err := tools.CreateEslintConfig(toolsConfigDir, patternConfiguration)
403381
if err != nil {
404382
return fmt.Errorf("failed to write eslint config: %v", err)
405383
}
406384
fmt.Println("ESLint configuration created based on Codacy settings. Ignoring plugin rules. ESLint plugins are not supported yet.")
407-
case Trivy:
385+
case domain.Trivy:
408386
err := createTrivyConfigFile(patternConfiguration, toolsConfigDir)
409387
if err != nil {
410388
return fmt.Errorf("failed to create Trivy config: %v", err)
411389
}
412390
fmt.Println("Trivy configuration created based on Codacy settings")
413-
case PMD:
391+
case domain.PMD:
414392
err := createPMDConfigFile(patternConfiguration, toolsConfigDir)
415393
if err != nil {
416394
return fmt.Errorf("failed to create PMD config: %v", err)
417395
}
418-
fmt.Println("PMD configuration created based on Codacy settings")
419-
case PyLint:
396+
case domain.PMD7:
397+
err := createPMD7ConfigFile(patternConfiguration, toolsConfigDir)
398+
if err != nil {
399+
return fmt.Errorf("failed to create PMD7 config: %v", err)
400+
}
401+
fmt.Println("PMD7 configuration created based on Codacy settings")
402+
case domain.PyLint:
420403
err := createPylintConfigFile(patternConfiguration, toolsConfigDir)
421404
if err != nil {
422405
return fmt.Errorf("failed to create Pylint config: %v", err)
423406
}
424407
fmt.Println("Pylint configuration created based on Codacy settings")
425-
case DartAnalyzer:
408+
case domain.DartAnalyzer:
426409
err := createDartAnalyzerConfigFile(patternConfiguration, toolsConfigDir)
427410
if err != nil {
428411
return fmt.Errorf("failed to create Dart Analyzer config: %v", err)
429412
}
430413
fmt.Println("Dart configuration created based on Codacy settings")
431-
case Semgrep:
414+
case domain.Semgrep:
432415
err := createSemgrepConfigFile(patternConfiguration, toolsConfigDir)
433416
if err != nil {
434417
return fmt.Errorf("failed to create Semgrep config: %v", err)
435418
}
436419
fmt.Println("Semgrep configuration created based on Codacy settings")
437-
case Lizard:
420+
case domain.Lizard:
438421
err := createLizardConfigFile(toolsConfigDir, patternConfiguration)
439422
if err != nil {
440423
return fmt.Errorf("failed to create Lizard config: %v", err)
@@ -445,7 +428,12 @@ func createToolFileConfigurations(tool domain.Tool, patternConfiguration []domai
445428
}
446429

447430
func createPMDConfigFile(config []domain.PatternConfiguration, toolsConfigDir string) error {
448-
pmdConfigurationString := tools.CreatePmdConfig(config)
431+
pmdConfigurationString := tools.CreatePmd6Config(config)
432+
return os.WriteFile(filepath.Join(toolsConfigDir, "ruleset.xml"), []byte(pmdConfigurationString), utils.DefaultFilePerms)
433+
}
434+
435+
func createPMD7ConfigFile(config []domain.PatternConfiguration, toolsConfigDir string) error {
436+
pmdConfigurationString := tools.CreatePmd7Config(config)
449437
return os.WriteFile(filepath.Join(toolsConfigDir, "ruleset.xml"), []byte(pmdConfigurationString), utils.DefaultFilePerms)
450438
}
451439

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

530518
// buildDefaultConfigurationFiles creates default configuration files for all tools
531519
func buildDefaultConfigurationFiles(toolsConfigDir string) error {
532-
for _, tool := range AvailableTools {
533-
patternsConfig, err := codacyclient.GetDefaultToolPatternsConfig(initFlags, tool)
520+
for uuid := range domain.SupportedToolsMetadata {
521+
patternsConfig, err := codacyclient.GetDefaultToolPatternsConfig(initFlags, uuid)
534522
if err != nil {
535523
return fmt.Errorf("failed to get default tool patterns config: %w", err)
536524
}
537-
switch tool {
538-
case ESLint:
525+
switch uuid {
526+
case domain.ESLint:
539527
if err := tools.CreateEslintConfig(toolsConfigDir, patternsConfig); err != nil {
540528
return fmt.Errorf("failed to create eslint config file: %v", err)
541529
}
542-
case Trivy:
530+
case domain.Trivy:
543531
if err := createTrivyConfigFile(patternsConfig, toolsConfigDir); err != nil {
544532
return fmt.Errorf("failed to create default Trivy configuration: %w", err)
545533
}
546-
case PMD:
534+
case domain.PMD:
547535
if err := createPMDConfigFile(patternsConfig, toolsConfigDir); err != nil {
548536
return fmt.Errorf("failed to create default PMD configuration: %w", err)
549537
}
550-
case PyLint:
538+
case domain.PMD7, domain.ESLint9:
539+
continue
540+
case domain.PyLint:
551541
if err := createPylintConfigFile(patternsConfig, toolsConfigDir); err != nil {
552542
return fmt.Errorf("failed to create default Pylint configuration: %w", err)
553543
}
554-
case DartAnalyzer:
544+
case domain.DartAnalyzer:
555545
if err := createDartAnalyzerConfigFile(patternsConfig, toolsConfigDir); err != nil {
556546
return fmt.Errorf("failed to create default Dart Analyzer configuration: %w", err)
557547
}
558-
case Semgrep:
548+
case domain.Semgrep:
559549
if err := createSemgrepConfigFile(patternsConfig, toolsConfigDir); err != nil {
560550
return fmt.Errorf("failed to create default Semgrep configuration: %w", err)
561551
}
562-
case Lizard:
552+
case domain.Lizard:
563553
if err := createLizardConfigFile(toolsConfigDir, patternsConfig); err != nil {
564554
return fmt.Errorf("failed to create default Lizard configuration: %w", err)
565555
}
@@ -568,23 +558,50 @@ func buildDefaultConfigurationFiles(toolsConfigDir string) error {
568558
return nil
569559
}
570560

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

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,
606+
return
590607
}

0 commit comments

Comments
 (0)