diff --git a/cmd/analyze.go b/cmd/analyze.go index 8821d501..8aae2996 100644 --- a/cmd/analyze.go +++ b/cmd/analyze.go @@ -9,6 +9,9 @@ import ( "log" "net/http" "os" + "path/filepath" + + "codacy/cli-v2/utils" "github.com/spf13/cobra" ) @@ -92,7 +95,7 @@ type Pattern struct { func init() { analyzeCmd.Flags().StringVarP(&outputFile, "output", "o", "", "Output file for analysis results") - analyzeCmd.Flags().StringVarP(&toolToAnalyze, "tool", "t", "", "Which tool to run analysis with") + analyzeCmd.Flags().StringVarP(&toolToAnalyze, "tool", "t", "", "Optional: Specific tool to run analysis with. If not specified, all configured tools will be run") analyzeCmd.Flags().StringVar(&outputFormat, "format", "", "Output format (use 'sarif' for SARIF format)") analyzeCmd.Flags().BoolVar(&autoFix, "fix", false, "Apply auto fix to your issues when available") rootCmd.AddCommand(analyzeCmd) @@ -224,35 +227,70 @@ func runPylintAnalysis(workDirectory string, pathsToCheck []string, outputFile s var analyzeCmd = &cobra.Command{ Use: "analyze", - Short: "Runs all linters.", - Long: "Runs all tools for all runtimes.", + Short: "Runs all configured linters.", + Long: "Runs all configured tools for code analysis. Use --tool flag to run a specific tool.", Run: func(cmd *cobra.Command, args []string) { workDirectory, err := os.Getwd() if err != nil { log.Fatal(err) } - log.Printf("Running %s...\n", toolToAnalyze) - if outputFormat == "sarif" { - log.Println("Output will be in SARIF format") + // If a specific tool is specified, only run that tool + if toolToAnalyze != "" { + log.Printf("Running %s...\n", toolToAnalyze) + runTool(workDirectory, toolToAnalyze, args, outputFile) + return } - if outputFile != "" { - log.Println("Output will be available at", outputFile) + + // Run all configured tools + tools := config.Config.Tools() + if len(tools) == 0 { + log.Fatal("No tools configured. Please run 'codacy-cli init' and 'codacy-cli install' first") } - switch toolToAnalyze { - case "eslint": - runEslintAnalysis(workDirectory, args, autoFix, outputFile, outputFormat) - case "trivy": - runTrivyAnalysis(workDirectory, args, outputFile, outputFormat) - case "pmd": - runPmdAnalysis(workDirectory, args, outputFile, outputFormat) - case "pylint": - runPylintAnalysis(workDirectory, args, outputFile, outputFormat) - case "": - log.Fatal("You need to specify a tool to run analysis with, e.g., '--tool eslint'") - default: - log.Fatal("Trying to run unsupported tool: ", toolToAnalyze) + log.Println("Running all configured tools...") + + if outputFormat == "sarif" { + // Create temporary directory for individual tool outputs + tmpDir, err := os.MkdirTemp("", "codacy-analysis-*") + if err != nil { + log.Fatalf("Failed to create temporary directory: %v", err) + } + defer os.RemoveAll(tmpDir) + + var sarifOutputs []string + for toolName := range tools { + log.Printf("Running %s...\n", toolName) + tmpFile := filepath.Join(tmpDir, fmt.Sprintf("%s.sarif", toolName)) + runTool(workDirectory, toolName, args, tmpFile) + sarifOutputs = append(sarifOutputs, tmpFile) + } + + // Merge all SARIF outputs + if err := utils.MergeSarifOutputs(sarifOutputs, outputFile); err != nil { + log.Fatalf("Failed to merge SARIF outputs: %v", err) + } + } else { + // Run tools without merging outputs + for toolName := range tools { + log.Printf("Running %s...\n", toolName) + runTool(workDirectory, toolName, args, outputFile) + } } }, } + +func runTool(workDirectory string, toolName string, args []string, outputFile string) { + switch toolName { + case "eslint": + runEslintAnalysis(workDirectory, args, autoFix, outputFile, outputFormat) + case "trivy": + runTrivyAnalysis(workDirectory, args, outputFile, outputFormat) + case "pmd": + runPmdAnalysis(workDirectory, args, outputFile, outputFormat) + case "pylint": + runPylintAnalysis(workDirectory, args, outputFile, outputFormat) + default: + log.Printf("Warning: Unsupported tool: %s\n", toolName) + } +} diff --git a/utils/sarif.go b/utils/sarif.go index a2611da2..af1d2cb2 100644 --- a/utils/sarif.go +++ b/utils/sarif.go @@ -2,6 +2,8 @@ package utils import ( "encoding/json" + "fmt" + "os" ) // PylintIssue represents a single issue in Pylint's JSON output @@ -41,9 +43,18 @@ type Driver struct { } type Rule struct { - ID string `json:"id"` - ShortDescription MessageText `json:"shortDescription"` - Properties map[string]string `json:"properties"` + ID string `json:"id"` + ShortDescription MessageText `json:"shortDescription"` + Properties RuleProperties `json:"properties"` +} + +type RuleProperties struct { + Priority int `json:"priority,omitempty"` + Ruleset string `json:"ruleset,omitempty"` + Tags []string `json:"tags,omitempty"` + // Add other common properties that might be present + Precision string `json:"precision,omitempty"` + SecuritySeverity string `json:"security-severity,omitempty"` } type Result struct { @@ -169,3 +180,44 @@ func createEmptySarifReport() []byte { sarifData, _ := json.MarshalIndent(emptyReport, "", " ") return sarifData } + +// MergeSarifOutputs combines multiple SARIF files into a single output file +func MergeSarifOutputs(inputFiles []string, outputFile string) error { + var mergedSarif SarifReport + mergedSarif.Version = "2.1.0" + mergedSarif.Schema = "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json" + mergedSarif.Runs = make([]Run, 0) + + for _, file := range inputFiles { + data, err := os.ReadFile(file) + if err != nil { + if os.IsNotExist(err) { + // Skip if file doesn't exist (tool might have failed) + continue + } + return fmt.Errorf("failed to read SARIF file %s: %w", file, err) + } + + var sarif SarifReport + if err := json.Unmarshal(data, &sarif); err != nil { + return fmt.Errorf("failed to parse SARIF file %s: %w", file, err) + } + + mergedSarif.Runs = append(mergedSarif.Runs, sarif.Runs...) + } + + // Create output file + out, err := os.Create(outputFile) + if err != nil { + return fmt.Errorf("failed to create output file: %w", err) + } + defer out.Close() + + encoder := json.NewEncoder(out) + encoder.SetIndent("", " ") + if err := encoder.Encode(mergedSarif); err != nil { + return fmt.Errorf("failed to write merged SARIF: %w", err) + } + + return nil +}