Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 59 additions & 21 deletions cmd/analyze.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ import (
"log"
"net/http"
"os"
"path/filepath"

"codacy/cli-v2/utils"

"github.com/spf13/cobra"
)
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)
}
}
58 changes: 55 additions & 3 deletions utils/sarif.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

import (
"encoding/json"
"fmt"
"os"
)

// PylintIssue represents a single issue in Pylint's JSON output
Expand Down Expand Up @@ -41,9 +43,18 @@
}

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 {

Check notice on line 51 in utils/sarif.go

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

utils/sarif.go#L51

exported type RuleProperties should have comment or be unexported
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 {
Expand Down Expand Up @@ -169,3 +180,44 @@
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
}
Loading