diff --git a/.gitignore b/.gitignore index 45c0feef..d4463251 100644 --- a/.gitignore +++ b/.gitignore @@ -16,3 +16,5 @@ cover.out completions .vscode + +*.gql \ No newline at end of file diff --git a/command/config/validate/validate.go b/command/config/validate/validate.go index 500cb50a..c3a8b6d1 100644 --- a/command/config/validate/validate.go +++ b/command/config/validate/validate.go @@ -4,7 +4,6 @@ import ( "context" "errors" "fmt" - "io/ioutil" "os" "os/exec" "path/filepath" @@ -60,7 +59,7 @@ func (o *Options) Run() error { } // Read the config in the form of string and send it - content, err := ioutil.ReadFile(configPath) + content, err := os.ReadFile(configPath) if err != nil { return errors.New("Error occured while reading DeepSource config file. Exiting...") } diff --git a/command/issues/list/list.go b/command/issues/list/list.go index fe89d117..772dd4e5 100644 --- a/command/issues/list/list.go +++ b/command/issues/list/list.go @@ -5,7 +5,6 @@ import ( "encoding/csv" "encoding/json" "fmt" - "io/ioutil" "os" "github.com/MakeNowJust/heredoc" @@ -20,17 +19,21 @@ import ( const MAX_ISSUE_LIMIT = 100 type IssuesListOptions struct { - FileArg []string - RepoArg string - AnalyzerArg []string - LimitArg int - OutputFilenameArg string - JSONArg bool - CSVArg bool - SARIFArg bool - SelectedRemote *utils.RemoteData - issuesData []issues.Issue - ptermTable [][]string + FileArg []string + RepoArg string + AnalyzerArg []string + LimitArg int + OutputFilenameArg string + JSONArg bool + CSVArg bool + SeverityArg string + AutofixAvailableArg bool + AutofixAiAvailableArg bool + IsRecommendedArg bool + SARIFArg bool + SelectedRemote *utils.RemoteData + issuesData []issues.Issue + ptermTable [][]string } func NewCmdIssuesList() *cobra.Command { @@ -84,6 +87,9 @@ func NewCmdIssuesList() *cobra.Command { // --analyzer, -a flag cmd.Flags().StringArrayVarP(&opts.AnalyzerArg, "analyzer", "a", nil, "List the issues for the specified analyzer") + // --severity, -s flag + cmd.Flags().StringVarP(&opts.SeverityArg, "severity", "s", "", "List the issues for the specified severity") + // --limit, -l flag cmd.Flags().IntVarP(&opts.LimitArg, "limit", "l", 30, "Fetch the issues upto the specified limit") @@ -99,6 +105,15 @@ func NewCmdIssuesList() *cobra.Command { // --sarif flag cmd.Flags().BoolVar(&opts.SARIFArg, "sarif", false, "Output reported issues in SARIF format") + // --recommended flag + cmd.Flags().BoolVar(&opts.IsRecommendedArg, "recommended", false, "Filter issues by recommendation") + + // --autofixAvailable flag + cmd.Flags().BoolVar(&opts.AutofixAvailableArg, "autofixAvailable", false, "Filter issues by autofix available") + + // --aiAutofixAvailable flag + cmd.Flags().BoolVar(&opts.AutofixAiAvailableArg, "aiAutofixAvailable", false, "Filter issues by autofix AI available") + return cmd } @@ -198,6 +213,26 @@ func (opts *IssuesListOptions) getIssuesData(ctx context.Context) (err error) { opts.issuesData = getUniqueIssues(fetchedIssues) } + if len(opts.SeverityArg) != 0 { + filteredIssues := utils.Filter(opts.issuesData, func(i issues.Issue) bool { return i.IssueSeverity == opts.SeverityArg }) + opts.issuesData = getUniqueIssues(filteredIssues) + } + + if opts.IsRecommendedArg { + filteredIssues := utils.Filter(opts.issuesData, func(i issues.Issue) bool { return i.IsRecommended }) + opts.issuesData = getUniqueIssues(filteredIssues) + } + + if opts.AutofixAvailableArg { + filteredIssues := utils.Filter(opts.issuesData, func(i issues.Issue) bool { return i.AutofixAvailable }) + opts.issuesData = getUniqueIssues(filteredIssues) + } + + if opts.AutofixAiAvailableArg { + filteredIssues := utils.Filter(opts.issuesData, func(i issues.Issue) bool { return i.AutofixAiAvailable }) + opts.issuesData = getUniqueIssues(filteredIssues) + } + return nil } @@ -215,10 +250,13 @@ func (opts *IssuesListOptions) showIssues() { analyzerShortcode := issue.Analyzer.Shortcode issueCategory := issue.IssueCategory issueSeverity := issue.IssueSeverity + issueIsRecommended := fmt.Sprint(issue.IsRecommended) + issueAutofixAvailable := fmt.Sprint(issue.AutofixAvailable) + issueAutofixAiAvailable := fmt.Sprint(issue.AutofixAiAvailable) issueCode := issue.IssueCode issueTitle := issue.IssueText - opts.ptermTable[index] = []string{issueLocation, analyzerShortcode, issueCode, issueTitle, issueCategory, issueSeverity} + opts.ptermTable[index] = []string{issueLocation, analyzerShortcode, issueCode, issueTitle, issueCategory, issueSeverity, issueIsRecommended, issueAutofixAvailable, issueAutofixAiAvailable} } // Using pterm to render the list of list pterm.DefaultTable.WithSeparator("\t").WithData(opts.ptermTable).Render() @@ -237,7 +275,7 @@ func (opts *IssuesListOptions) exportJSON(filename string) (err error) { return nil } - if err = ioutil.WriteFile(filename, data, 0o644); err != nil { + if err = os.WriteFile(filename, data, 0o644); err != nil { return err } diff --git a/command/issues/list/list_test.go b/command/issues/list/list_test.go index b3fe79dd..f2a2ca27 100644 --- a/command/issues/list/list_test.go +++ b/command/issues/list/list_test.go @@ -2,7 +2,6 @@ package list import ( "encoding/json" - "io/ioutil" "os" "reflect" "strings" @@ -13,7 +12,7 @@ import ( // Helper function to read issues from a file. func ReadIssues(path string) []issues.Issue { - raw, _ := ioutil.ReadFile(path) + raw, _ := os.ReadFile(path) var fetchedIssues []issues.Issue _ = json.Unmarshal(raw, &fetchedIssues) @@ -26,8 +25,8 @@ func TestListCSV(t *testing.T) { opts.exportCSV("./testdata/exported.csv") // read exported and test CSV files - exported, _ := ioutil.ReadFile("./testdata/exported.csv") - test, _ := ioutil.ReadFile("./testdata/csv/test.csv") + exported, _ := os.ReadFile("./testdata/exported.csv") + test, _ := os.ReadFile("./testdata/csv/test.csv") // trim carriage returns got := strings.TrimSuffix(string(exported), "\n") @@ -47,8 +46,8 @@ func TestListJSON(t *testing.T) { opts.exportJSON("./testdata/exported.json") // read exported and test JSON files - exported, _ := ioutil.ReadFile("./testdata/exported.json") - test, _ := ioutil.ReadFile("./testdata/json/test.json") + exported, _ := os.ReadFile("./testdata/exported.json") + test, _ := os.ReadFile("./testdata/json/test.json") // trim carriage returns got := strings.TrimSuffix(string(exported), "\n") @@ -71,8 +70,8 @@ func TestListSARIF(t *testing.T) { opts.exportSARIF("./testdata/exported.sarif") // read exported and test SARIF files - exported, _ := ioutil.ReadFile("./testdata/exported.sarif") - test, _ := ioutil.ReadFile("./testdata/sarif/test.sarif") + exported, _ := os.ReadFile("./testdata/exported.sarif") + test, _ := os.ReadFile("./testdata/sarif/test.sarif") // trim carriage returns got := strings.TrimSuffix(string(exported), "\n") @@ -94,8 +93,8 @@ func TestListSARIF(t *testing.T) { opts.exportSARIF("./testdata/exported_multi.sarif") // read exported and test SARIF files - exported, _ := ioutil.ReadFile("./testdata/exported_multi.sarif") - test, _ := ioutil.ReadFile("./testdata/sarif/test_multi.sarif") + exported, _ := os.ReadFile("./testdata/exported_multi.sarif") + test, _ := os.ReadFile("./testdata/sarif/test_multi.sarif") // trim carriage returns got := strings.TrimSuffix(string(exported), "\n") diff --git a/command/issues/list/utils.go b/command/issues/list/utils.go index cbdd86f3..1f6581b2 100644 --- a/command/issues/list/utils.go +++ b/command/issues/list/utils.go @@ -50,6 +50,19 @@ func filterIssuesByPath(path string, issuesData []issues.Issue) ([]issues.Issue, return getUniqueIssues(filteredIssues), nil } +// Filters issues based on the analyzer shortcode. +func filterIssuesBySeverity(severity string, issuesData []issues.Issue) ([]issues.Issue, error) { + var filteredIssues []issues.Issue + + for _, issue := range issuesData { + if issue.IssueSeverity == severity { + filteredIssues = append(filteredIssues, issue) + } + } + + return getUniqueIssues(filteredIssues), nil +} + // Filters issues based on the analyzer shortcode. func filterIssuesByAnalyzer(analyzer []string, issuesData []issues.Issue) ([]issues.Issue, error) { var filteredIssues []issues.Issue diff --git a/deepsource/issues/issues_list.go b/deepsource/issues/issues_list.go index 2f23b81b..b0e6b104 100644 --- a/deepsource/issues/issues_list.go +++ b/deepsource/issues/issues_list.go @@ -15,10 +15,13 @@ type AnalyzerMeta struct { } type Issue struct { - IssueText string `json:"issue_title"` // The describing heading of the issue - IssueCode string `json:"issue_code"` // DeepSource code for the issue reported - IssueCategory string `json:"issue_category"` // Category of the issue reported - IssueSeverity string `json:"issue_severity"` // Severity of the issue reported - Location Location `json:"location"` // The location data for the issue reported - Analyzer AnalyzerMeta // The Analyzer which raised the issue + IssueText string `json:"issue_title"` // The describing heading of the issue + IssueCode string `json:"issue_code"` // DeepSource code for the issue reported + IssueCategory string `json:"issue_category"` // Category of the issue reported + IssueSeverity string `json:"issue_severity"` // Severity of the issue reported + IsRecommended bool `json:"is_recommended"` + AutofixAvailable bool `json:"autofix_available"` + AutofixAiAvailable bool `json:"autofix_ai_available"` + Location Location `json:"location"` // The location data for the issue reported + Analyzer AnalyzerMeta // The Analyzer which raised the issue } diff --git a/deepsource/issues/queries/list_issues.go b/deepsource/issues/queries/list_issues.go index 61f69d4f..2e01404b 100644 --- a/deepsource/issues/queries/list_issues.go +++ b/deepsource/issues/queries/list_issues.go @@ -68,12 +68,14 @@ type IssuesListResponse struct { BeginLine int `json:"beginLine"` EndLine int `json:"endLine"` Issue struct { - Title string `json:"title"` - Shortcode string `json:"shortcode"` - Category string `json:"category"` - Severity string `json:"severity"` - IsRecommended bool `json:"isRecommended"` - Analyzer struct { + Title string `json:"title"` + Shortcode string `json:"shortcode"` + Category string `json:"category"` + Severity string `json:"severity"` + IsRecommended bool `json:"isRecommended"` + AutofixAvailable bool `json:"autofixAvailable"` + AutofixAiAvailable bool `json:"autofixAiAvailable"` + Analyzer struct { Name string `json:"name"` Shortcode string `json:"shortcode"` } `json:"analyzer"` @@ -114,10 +116,13 @@ func (i IssuesListRequest) Do(ctx context.Context, client IGQLClient) ([]issues. for _, occurenceEdge := range edge.Node.Occurrences.Edges { issueData := issues.Issue{ - IssueText: occurenceEdge.Node.Issue.Title, - IssueCode: occurenceEdge.Node.Issue.Shortcode, - IssueCategory: occurenceEdge.Node.Issue.Category, - IssueSeverity: occurenceEdge.Node.Issue.Severity, + IssueText: occurenceEdge.Node.Issue.Title, + IssueCode: occurenceEdge.Node.Issue.Shortcode, + IssueCategory: occurenceEdge.Node.Issue.Category, + IssueSeverity: occurenceEdge.Node.Issue.Severity, + IsRecommended: occurenceEdge.Node.Issue.IsRecommended, + AutofixAvailable: occurenceEdge.Node.Issue.AutofixAvailable, + AutofixAiAvailable: occurenceEdge.Node.Issue.AutofixAiAvailable, Location: issues.Location{ Path: occurenceEdge.Node.Path, Position: issues.Position{ diff --git a/deepsource/tests/get_analyzers_test.go b/deepsource/tests/get_analyzers_test.go index 8f8b876c..7c5c598d 100644 --- a/deepsource/tests/get_analyzers_test.go +++ b/deepsource/tests/get_analyzers_test.go @@ -2,9 +2,10 @@ package tests import ( "context" - "io/ioutil" + "io" "log" "net/http" + "os" "reflect" "testing" @@ -46,24 +47,24 @@ func TestAnalyzers(t *testing.T) { // a mock GraphQL handler for testing func mockAnalyzer(w http.ResponseWriter, r *http.Request) { - req, _ := ioutil.ReadAll(r.Body) + req, _ := io.ReadAll(r.Body) // Read test graphql request body artifact file - requestBodyData, err := ioutil.ReadFile("./testdata/analyzer/request_body.txt") + requestBodyData, err := os.ReadFile("./testdata/analyzer/request_body.txt") if err != nil { log.Println(err) return } // Read test graphql success response body artifact file - successResponseBodyData, err := ioutil.ReadFile("./testdata/analyzer/success_response_body.json") + successResponseBodyData, err := os.ReadFile("./testdata/analyzer/success_response_body.json") if err != nil { log.Println(err) return } // Read test graphql error response body artifact file - errorResponseBodyData, err := ioutil.ReadFile("./testdata/analyzer/error_response_body.json") + errorResponseBodyData, err := os.ReadFile("./testdata/analyzer/error_response_body.json") if err != nil { log.Println(err) return diff --git a/utils/array.go b/utils/array.go new file mode 100644 index 00000000..dfd1bd4a --- /dev/null +++ b/utils/array.go @@ -0,0 +1,11 @@ +package utils + +func Filter[T any](data []T, f func(T) bool) []T { + fltd := make([]T, 0, len(data)) + for _, e := range data { + if f(e) { + fltd = append(fltd, e) + } + } + return fltd +}