Skip to content

Commit 703c641

Browse files
Analyze run all configured tools (#64) [PLUTO-1385]
1 parent 4d692e4 commit 703c641

File tree

2 files changed

+137
-25
lines changed

2 files changed

+137
-25
lines changed

cmd/analyze.go

Lines changed: 82 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,23 @@ package cmd
22

33
import (
44
"codacy/cli-v2/config"
5+
"codacy/cli-v2/plugins"
56
"codacy/cli-v2/tools"
67
"encoding/json"
78
"fmt"
89
"io"
910
"log"
1011
"net/http"
1112
"os"
13+
"path/filepath"
14+
15+
"codacy/cli-v2/utils"
1216

1317
"github.com/spf13/cobra"
1418
)
1519

1620
var outputFile string
17-
var toolToAnalyze string
21+
var toolsToAnalyzeParam string
1822
var autoFix bool
1923
var outputFormat string
2024
var sarifPath string
@@ -92,7 +96,7 @@ type Pattern struct {
9296

9397
func init() {
9498
analyzeCmd.Flags().StringVarP(&outputFile, "output", "o", "", "Output file for analysis results")
95-
analyzeCmd.Flags().StringVarP(&toolToAnalyze, "tool", "t", "", "Which tool to run analysis with")
99+
analyzeCmd.Flags().StringVarP(&toolsToAnalyzeParam, "tool", "t", "", "Which tool to run analysis with. If not specified, all configured tools will be run")
96100
analyzeCmd.Flags().StringVar(&outputFormat, "format", "", "Output format (use 'sarif' for SARIF format)")
97101
analyzeCmd.Flags().BoolVar(&autoFix, "fix", false, "Apply auto fix to your issues when available")
98102
rootCmd.AddCommand(analyzeCmd)
@@ -224,35 +228,91 @@ func runPylintAnalysis(workDirectory string, pathsToCheck []string, outputFile s
224228

225229
var analyzeCmd = &cobra.Command{
226230
Use: "analyze",
227-
Short: "Runs all linters.",
228-
Long: "Runs all tools for all runtimes.",
231+
Short: "Runs all configured linters.",
232+
Long: "Runs all configured tools for code analysis. Use --tool flag to run a specific tool.",
229233
Run: func(cmd *cobra.Command, args []string) {
230234
workDirectory, err := os.Getwd()
231235
if err != nil {
232236
log.Fatal(err)
233237
}
238+
var toolsToRun map[string]*plugins.ToolInfo
234239

235-
log.Printf("Running %s...\n", toolToAnalyze)
236-
if outputFormat == "sarif" {
237-
log.Println("Output will be in SARIF format")
240+
if toolsToAnalyzeParam != "" {
241+
// If a specific tool is specified, only run that tool
242+
toolsToRun = map[string]*plugins.ToolInfo{
243+
toolsToAnalyzeParam: config.Config.Tools()[toolsToAnalyzeParam],
244+
}
245+
} else {
246+
// Run all configured tools
247+
toolsToRun = config.Config.Tools()
238248
}
239-
if outputFile != "" {
240-
log.Println("Output will be available at", outputFile)
249+
250+
if len(toolsToRun) == 0 {
251+
log.Fatal("No tools configured. Please run 'codacy-cli init' and 'codacy-cli install' first")
241252
}
242253

243-
switch toolToAnalyze {
244-
case "eslint":
245-
runEslintAnalysis(workDirectory, args, autoFix, outputFile, outputFormat)
246-
case "trivy":
247-
runTrivyAnalysis(workDirectory, args, outputFile, outputFormat)
248-
case "pmd":
249-
runPmdAnalysis(workDirectory, args, outputFile, outputFormat)
250-
case "pylint":
251-
runPylintAnalysis(workDirectory, args, outputFile, outputFormat)
252-
case "":
253-
log.Fatal("You need to specify a tool to run analysis with, e.g., '--tool eslint'")
254-
default:
255-
log.Fatal("Trying to run unsupported tool: ", toolToAnalyze)
254+
log.Println("Running all configured tools...")
255+
256+
if outputFormat == "sarif" {
257+
// Create temporary directory for individual tool outputs
258+
tmpDir, err := os.MkdirTemp("", "codacy-analysis-*")
259+
if err != nil {
260+
log.Fatalf("Failed to create temporary directory: %v", err)
261+
}
262+
defer os.RemoveAll(tmpDir)
263+
264+
var sarifOutputs []string
265+
for toolName := range toolsToRun {
266+
log.Printf("Running %s...\n", toolName)
267+
tmpFile := filepath.Join(tmpDir, fmt.Sprintf("%s.sarif", toolName))
268+
runTool(workDirectory, toolName, args, tmpFile)
269+
sarifOutputs = append(sarifOutputs, tmpFile)
270+
}
271+
272+
// create output file tmp file
273+
tmpOutputFile := filepath.Join(tmpDir, "merged.sarif")
274+
275+
// Merge all SARIF outputs
276+
if err := utils.MergeSarifOutputs(sarifOutputs, tmpOutputFile); err != nil {
277+
log.Fatalf("Failed to merge SARIF outputs: %v", err)
278+
}
279+
280+
if outputFile != "" {
281+
// copy tmpOutputFile to outputFile
282+
content, err := os.ReadFile(tmpOutputFile)
283+
if err != nil {
284+
log.Fatalf("Failed to read merged SARIF output: %v", err)
285+
}
286+
os.WriteFile(outputFile, content, utils.DefaultRW)
287+
} else {
288+
// println the output file content
289+
content, err := os.ReadFile(tmpOutputFile)
290+
if err != nil {
291+
log.Fatalf("Failed to read merged SARIF output: %v", err)
292+
}
293+
fmt.Println(string(content))
294+
}
295+
} else {
296+
// Run tools without merging outputs
297+
for toolName := range toolsToRun {
298+
log.Printf("Running %s...\n", toolName)
299+
runTool(workDirectory, toolName, args, outputFile)
300+
}
256301
}
257302
},
258303
}
304+
305+
func runTool(workDirectory string, toolName string, args []string, outputFile string) {
306+
switch toolName {
307+
case "eslint":
308+
runEslintAnalysis(workDirectory, args, autoFix, outputFile, outputFormat)
309+
case "trivy":
310+
runTrivyAnalysis(workDirectory, args, outputFile, outputFormat)
311+
case "pmd":
312+
runPmdAnalysis(workDirectory, args, outputFile, outputFormat)
313+
case "pylint":
314+
runPylintAnalysis(workDirectory, args, outputFile, outputFormat)
315+
default:
316+
log.Printf("Warning: Unsupported tool: %s\n", toolName)
317+
}
318+
}

utils/sarif.go

Lines changed: 55 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ package utils
22

33
import (
44
"encoding/json"
5+
"fmt"
6+
"os"
57
)
68

79
// PylintIssue represents a single issue in Pylint's JSON output
@@ -41,9 +43,18 @@ type Driver struct {
4143
}
4244

4345
type Rule struct {
44-
ID string `json:"id"`
45-
ShortDescription MessageText `json:"shortDescription"`
46-
Properties map[string]string `json:"properties"`
46+
ID string `json:"id"`
47+
ShortDescription MessageText `json:"shortDescription"`
48+
Properties RuleProperties `json:"properties"`
49+
}
50+
51+
type RuleProperties struct {
52+
Priority int `json:"priority,omitempty"`
53+
Ruleset string `json:"ruleset,omitempty"`
54+
Tags []string `json:"tags,omitempty"`
55+
// Add other common properties that might be present
56+
Precision string `json:"precision,omitempty"`
57+
SecuritySeverity string `json:"security-severity,omitempty"`
4758
}
4859

4960
type Result struct {
@@ -169,3 +180,44 @@ func createEmptySarifReport() []byte {
169180
sarifData, _ := json.MarshalIndent(emptyReport, "", " ")
170181
return sarifData
171182
}
183+
184+
// MergeSarifOutputs combines multiple SARIF files into a single output file
185+
func MergeSarifOutputs(inputFiles []string, outputFile string) error {
186+
var mergedSarif SarifReport
187+
mergedSarif.Version = "2.1.0"
188+
mergedSarif.Schema = "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json"
189+
mergedSarif.Runs = make([]Run, 0)
190+
191+
for _, file := range inputFiles {
192+
data, err := os.ReadFile(file)
193+
if err != nil {
194+
if os.IsNotExist(err) {
195+
// Skip if file doesn't exist (tool might have failed)
196+
continue
197+
}
198+
return fmt.Errorf("failed to read SARIF file %s: %w", file, err)
199+
}
200+
201+
var sarif SarifReport
202+
if err := json.Unmarshal(data, &sarif); err != nil {
203+
return fmt.Errorf("failed to parse SARIF file %s: %w", file, err)
204+
}
205+
206+
mergedSarif.Runs = append(mergedSarif.Runs, sarif.Runs...)
207+
}
208+
209+
// Create output file
210+
out, err := os.Create(outputFile)
211+
if err != nil {
212+
return fmt.Errorf("failed to create output file: %w", err)
213+
}
214+
defer out.Close()
215+
216+
encoder := json.NewEncoder(out)
217+
encoder.SetIndent("", " ")
218+
if err := encoder.Encode(mergedSarif); err != nil {
219+
return fmt.Errorf("failed to write merged SARIF: %w", err)
220+
}
221+
222+
return nil
223+
}

0 commit comments

Comments
 (0)