@@ -20,10 +20,31 @@ import (
2020)
2121
2222type IssuesOptions struct {
23- commitOid string
24- jsonOutput bool
25- outputFile string
26- runWithIssues * runs.RunWithIssues
23+ commitOid string
24+ jsonOutput bool
25+ outputFile string
26+ analyzerFilters []string
27+ categoryFilters []string
28+ severityFilters []string
29+ codeFilters []string
30+ pathFilters []string
31+ runWithIssues * runs.RunWithIssues
32+ }
33+
34+ // AddRunIssueFlags registers flags for filtering and output options.
35+ func AddRunIssueFlags (cmd * cobra.Command , opts * IssuesOptions ) {
36+ // --json flag
37+ cmd .Flags ().BoolVar (& opts .jsonOutput , "json" , false , "Output issues in JSON format" )
38+
39+ // --output-file, -o flag
40+ cmd .Flags ().StringVarP (& opts .outputFile , "output-file" , "o" , "" , "Output file to write the issues to" )
41+
42+ // filter flags
43+ cmd .Flags ().StringArrayVar (& opts .analyzerFilters , "analyzer" , nil , "Filter issues by analyzer shortcode (repeatable)" )
44+ cmd .Flags ().StringArrayVar (& opts .categoryFilters , "category" , nil , "Filter issues by category (repeatable)" )
45+ cmd .Flags ().StringArrayVar (& opts .severityFilters , "severity" , nil , "Filter issues by severity (repeatable)" )
46+ cmd .Flags ().StringArrayVar (& opts .codeFilters , "code" , nil , "Filter issues by issue code (repeatable)" )
47+ cmd .Flags ().StringArrayVar (& opts .pathFilters , "path" , nil , "Filter issues by path substring (repeatable)" )
2748}
2849
2950// NewCmdRunsIssues shows the issues in a specific analysis run.
@@ -32,7 +53,8 @@ func NewCmdRunsIssues() *cobra.Command {
3253 View issues in a specific analysis run.
3354
3455 Use %[1]s to view the issues found in a specific run.
35- ` , style .Cyan ("deepsource runs issues <commit-oid>" ))
56+ Filter issues by analyzer, category, severity, issue code, or path when needed.
57+ ` , style .Cyan ("deepsource runs <commit-oid>" ))
3658
3759 opts := IssuesOptions {}
3860
@@ -47,15 +69,17 @@ func NewCmdRunsIssues() *cobra.Command {
4769 },
4870 }
4971
50- // --json flag
51- cmd .Flags ().BoolVar (& opts .jsonOutput , "json" , false , "Output issues in JSON format" )
52-
53- // --output-file, -o flag
54- cmd .Flags ().StringVarP (& opts .outputFile , "output-file" , "o" , "" , "Output file to write the issues to" )
72+ AddRunIssueFlags (cmd , & opts )
5573
5674 return cmd
5775}
5876
77+ // RunWithCommit runs the command for the provided commit OID.
78+ func (opts * IssuesOptions ) RunWithCommit (ctx context.Context , commitOid string ) error {
79+ opts .commitOid = commitOid
80+ return opts .Run (ctx )
81+ }
82+
5983func (opts * IssuesOptions ) Run (ctx context.Context ) error {
6084 // Load configuration
6185 cfg , err := config .DefaultManager ().Load ()
@@ -87,6 +111,9 @@ func (opts *IssuesOptions) Run(ctx context.Context) error {
87111 return fmt .Errorf ("failed to fetch run issues: %w" , err )
88112 }
89113
114+ // Apply filters, if any
115+ runWithIssues .Issues = opts .filterIssues (runWithIssues .Issues )
116+
90117 opts .runWithIssues = runWithIssues
91118
92119 // If JSON output is requested, export and return
@@ -113,7 +140,11 @@ func (opts *IssuesOptions) Run(ctx context.Context) error {
113140
114141 if len (runWithIssues .Issues ) == 0 {
115142 pterm .Println ("" )
116- pterm .Success .Println ("No issues found in this run" )
143+ if opts .hasFilters () {
144+ pterm .Success .Println ("No issues matched the provided filters" )
145+ } else {
146+ pterm .Success .Println ("No issues found in this run" )
147+ }
117148 return nil
118149 }
119150
@@ -125,6 +156,93 @@ func (opts *IssuesOptions) Run(ctx context.Context) error {
125156 return nil
126157}
127158
159+ func (opts * IssuesOptions ) hasFilters () bool {
160+ return len (opts .analyzerFilters ) > 0 ||
161+ len (opts .categoryFilters ) > 0 ||
162+ len (opts .severityFilters ) > 0 ||
163+ len (opts .codeFilters ) > 0 ||
164+ len (opts .pathFilters ) > 0
165+ }
166+
167+ func (opts * IssuesOptions ) filterIssues (issues []runs.RunIssue ) []runs.RunIssue {
168+ if ! opts .hasFilters () {
169+ return issues
170+ }
171+
172+ analyzerSet := makeStringSet (opts .analyzerFilters )
173+ categorySet := makeStringSet (opts .categoryFilters )
174+ severitySet := makeStringSet (opts .severityFilters )
175+ codeSet := makeStringSet (opts .codeFilters )
176+ pathFilters := makeLowerStrings (opts .pathFilters )
177+
178+ filtered := make ([]runs.RunIssue , 0 , len (issues ))
179+ for _ , issue := range issues {
180+ if len (analyzerSet ) > 0 && ! setContainsFold (analyzerSet , issue .AnalyzerShortcode ) {
181+ continue
182+ }
183+ if len (categorySet ) > 0 && ! setContainsFold (categorySet , issue .Category ) {
184+ continue
185+ }
186+ if len (severitySet ) > 0 && ! setContainsFold (severitySet , issue .Severity ) {
187+ continue
188+ }
189+ if len (codeSet ) > 0 && ! setContainsFold (codeSet , issue .IssueCode ) {
190+ continue
191+ }
192+ if len (pathFilters ) > 0 && ! matchesPathFilters (issue .Path , pathFilters ) {
193+ continue
194+ }
195+ filtered = append (filtered , issue )
196+ }
197+
198+ return filtered
199+ }
200+
201+ func makeStringSet (values []string ) map [string ]struct {} {
202+ set := make (map [string ]struct {})
203+ for _ , value := range values {
204+ normalized := strings .ToLower (strings .TrimSpace (value ))
205+ if normalized == "" {
206+ continue
207+ }
208+ set [normalized ] = struct {}{}
209+ }
210+ return set
211+ }
212+
213+ func makeLowerStrings (values []string ) []string {
214+ normalized := make ([]string , 0 , len (values ))
215+ for _ , value := range values {
216+ trimmed := strings .TrimSpace (value )
217+ if trimmed == "" {
218+ continue
219+ }
220+ normalized = append (normalized , strings .ToLower (trimmed ))
221+ }
222+ return normalized
223+ }
224+
225+ func setContainsFold (set map [string ]struct {}, value string ) bool {
226+ _ , ok := set [strings .ToLower (strings .TrimSpace (value ))]
227+ return ok
228+ }
229+
230+ func matchesPathFilters (path string , filters []string ) bool {
231+ if path == "" {
232+ return false
233+ }
234+ lowerPath := strings .ToLower (path )
235+ for _ , filter := range filters {
236+ if filter == "" {
237+ continue
238+ }
239+ if strings .Contains (lowerPath , filter ) {
240+ return true
241+ }
242+ }
243+ return false
244+ }
245+
128246func (opts * IssuesOptions ) showIssues (issues []runs.RunIssue ) {
129247 header := []string {"LOCATION" , "ANALYZER" , "CODE" , "TITLE" , "CATEGORY" , "SEVERITY" }
130248 data := [][]string {header }
0 commit comments