diff --git a/cmd/pint/ci.go b/cmd/pint/ci.go index 469e42e7..c783c95b 100644 --- a/cmd/pint/ci.go +++ b/cmd/pint/ci.go @@ -145,6 +145,7 @@ func actionCI(c *cli.Context) error { token, meta.cfg.Repository.BitBucket.Project, meta.cfg.Repository.BitBucket.Repository, + meta.cfg.Repository.BitBucket.MaxComments, git.RunGit, ) reps = append(reps, br) @@ -178,6 +179,7 @@ func actionCI(c *cli.Context) error { meta.cfg.Repository.GitHub.Owner, meta.cfg.Repository.GitHub.Repo, prNum, + meta.cfg.Repository.GitHub.MaxComments, git.RunGit, ); err != nil { return err diff --git a/docs/changelog.md b/docs/changelog.md index 5c022c69..3776934a 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -1,5 +1,13 @@ # Changelog +## v0.57.0 + +### Added + +- GitHub and BitBucker reporters now supports `maxComments` option to limit the number + of comments pint can create on a single pull request. + Default value of `maxComments` is `50`. + ## v0.56.2 ### Fixed diff --git a/docs/configuration.md b/docs/configuration.md index 034b50dc..8ba879db 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -158,10 +158,11 @@ Syntax: ```js repository { bitbucket { - uri = "https://..." - timeout = "1m" - project = "..." - repository = "..." + uri = "https://..." + timeout = "1m" + project = "..." + repository = "..." + maxComments = 50 } } ``` @@ -171,15 +172,18 @@ repository { - `bitbucket:timeout` - timeout to be used for API requests, defaults to 1 minute. - `bitbucket:project` - name of the BitBucket project for this repository. - `bitbucket:repository` - name of the BitBucket repository. +- `bitbucket:maxComments` - the maximum number of comments pint can create on a single + pull request. Default is 50. ```js repository { github { - baseuri = "https://..." - uploaduri = "https://..." - timeout = "1m" - owner = "..." - repo = "..." + baseuri = "https://..." + uploaduri = "https://..." + timeout = "1m" + owner = "..." + repo = "..." + maxComments = 50 } } ``` @@ -196,6 +200,7 @@ If `github:baseuri` _or_ `github:uploaduri` are not specified, then [GitHub](htt If not set, `pint` will try to use the `GITHUB_REPOSITORY` environment variable instead (if set). - `github:repo` - name of the GitHub repository (e.g. `monitoring`). If not set, `pint` will try to use the `GITHUB_REPOSITORY` environment variable instead (if set). +- `github:maxComments` - the maximum number of comments pint can create on a single pull request. Default is 50. Most GitHub settings can be detected from environment variables that are set inside GitHub Actions environment. The only exception is `GITHUB_AUTH_TOKEN` environment variable that must be set diff --git a/internal/config/config.go b/internal/config/config.go index db75270e..792dd317 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -262,6 +262,9 @@ func Load(path string, failOnMissing bool) (cfg Config, err error) { if cfg.Repository.BitBucket.Timeout == "" { cfg.Repository.BitBucket.Timeout = time.Minute.String() } + if cfg.Repository.BitBucket.MaxComments == 0 { + cfg.Repository.BitBucket.MaxComments = 50 + } if err = cfg.Repository.BitBucket.validate(); err != nil { return cfg, err } @@ -271,6 +274,9 @@ func Load(path string, failOnMissing bool) (cfg Config, err error) { if cfg.Repository.GitHub.Timeout == "" { cfg.Repository.GitHub.Timeout = time.Minute.String() } + if cfg.Repository.GitHub.MaxComments == 0 { + cfg.Repository.GitHub.MaxComments = 50 + } if err = cfg.Repository.GitHub.validate(); err != nil { return cfg, err } diff --git a/internal/config/repository.go b/internal/config/repository.go index feeb4f52..e2f06a3a 100644 --- a/internal/config/repository.go +++ b/internal/config/repository.go @@ -8,10 +8,11 @@ import ( ) type BitBucket struct { - URI string `hcl:"uri"` - Timeout string `hcl:"timeout,optional"` - Project string `hcl:"project"` - Repository string `hcl:"repository"` + URI string `hcl:"uri"` + Timeout string `hcl:"timeout,optional"` + Project string `hcl:"project"` + Repository string `hcl:"repository"` + MaxComments int `hcl:"maxComments,optional"` } func (bb BitBucket) validate() error { @@ -27,15 +28,19 @@ func (bb BitBucket) validate() error { if bb.URI == "" { return fmt.Errorf("uri cannot be empty") } + if bb.MaxComments < 0 { + return fmt.Errorf("maxComments cannot be negative") + } return nil } type GitHub struct { - BaseURI string `hcl:"baseuri,optional"` - UploadURI string `hcl:"uploaduri,optional"` - Timeout string `hcl:"timeout,optional"` - Owner string `hcl:"owner,optional"` - Repo string `hcl:"repo,optional"` + BaseURI string `hcl:"baseuri,optional"` + UploadURI string `hcl:"uploaduri,optional"` + Timeout string `hcl:"timeout,optional"` + Owner string `hcl:"owner,optional"` + Repo string `hcl:"repo,optional"` + MaxComments int `hcl:"maxComments,optional"` } func (gh GitHub) validate() error { @@ -73,7 +78,9 @@ func (gh GitHub) validate() error { return fmt.Errorf("invalid uploaduri: %w", err) } } - + if gh.MaxComments < 0 { + return fmt.Errorf("maxComments cannot be negative") + } return nil } diff --git a/internal/config/repository_test.go b/internal/config/repository_test.go index cb7d967b..00489938 100644 --- a/internal/config/repository_test.go +++ b/internal/config/repository_test.go @@ -67,6 +67,16 @@ func TestBitBucketSettings(t *testing.T) { }, err: errors.New(`not a valid duration string: "abc"`), }, + { + conf: BitBucket{ + URI: "http://localhost", + Timeout: "5m", + MaxComments: -1, + Project: "foo", + Repository: "bar", + }, + err: errors.New("maxComments cannot be negative"), + }, } for _, tc := range testCases { @@ -165,6 +175,15 @@ func TestGitHubSettings(t *testing.T) { env: map[string]string{"GITHUB_REPOSITORY": "foo/"}, err: errors.New("repo cannot be empty"), }, + { + conf: GitHub{ + Owner: "bob", + Repo: "foo", + Timeout: "5m", + MaxComments: -1, + }, + err: errors.New("maxComments cannot be negative"), + }, } for _, tc := range testCases { diff --git a/internal/reporter/bitbucket.go b/internal/reporter/bitbucket.go index a2820e18..1715de2e 100644 --- a/internal/reporter/bitbucket.go +++ b/internal/reporter/bitbucket.go @@ -14,9 +14,9 @@ const ( "Checks can be either offline (static checks using only rule definition) or online (validate rule against live Prometheus server)." ) -func NewBitBucketReporter(version, uri string, timeout time.Duration, token, project, repo string, gitCmd git.CommandRunner) BitBucketReporter { +func NewBitBucketReporter(version, uri string, timeout time.Duration, token, project, repo string, maxComments int, gitCmd git.CommandRunner) BitBucketReporter { return BitBucketReporter{ - api: newBitBucketAPI(version, uri, timeout, token, project, repo), + api: newBitBucketAPI(version, uri, timeout, token, project, repo, maxComments), gitCmd: gitCmd, } } @@ -79,6 +79,12 @@ func (r BitBucketReporter) Submit(summary Summary) (err error) { pendingComments := r.api.makeComments(summary, changes) slog.Info("Generated comments to add to BitBucket", slog.Int("count", len(pendingComments))) + pendingComments = r.api.limitComments(pendingComments) + slog.Info("Will add comments to BitBucket", + slog.Int("count", len(pendingComments)), + slog.Int("limit", r.api.maxComments), + ) + slog.Info("Deleting stale comments from BitBucket") r.api.pruneComments(pr, existingComments, pendingComments) diff --git a/internal/reporter/bitbucket_api.go b/internal/reporter/bitbucket_api.go index 9ac20fdc..39fe21ab 100644 --- a/internal/reporter/bitbucket_api.go +++ b/internal/reporter/bitbucket_api.go @@ -226,11 +226,11 @@ func (pc pendingComment) toBitBucketComment(changes *bitBucketPRChanges) BitBuck } type BitBucketPendingCommentAnchor struct { - Path string `json:"path"` - LineType string `json:"lineType"` - FileType string `json:"fileType"` + Path string `json:"path,omitempty"` + LineType string `json:"lineType,omitempty"` + FileType string `json:"fileType,omitempty"` DiffType string `json:"diffType"` - Line int `json:"line"` + Line int `json:"line,omitempty"` } type BitBucketPendingComment struct { @@ -249,7 +249,7 @@ type BitBucketCommentSeverityUpdate struct { Version int `json:"version"` } -func newBitBucketAPI(pintVersion, uri string, timeout time.Duration, token, project, repo string) *bitBucketAPI { +func newBitBucketAPI(pintVersion, uri string, timeout time.Duration, token, project, repo string, maxComments int) *bitBucketAPI { return &bitBucketAPI{ pintVersion: pintVersion, uri: uri, @@ -257,6 +257,7 @@ func newBitBucketAPI(pintVersion, uri string, timeout time.Duration, token, proj authToken: token, project: project, repo: repo, + maxComments: maxComments, } } @@ -650,6 +651,22 @@ func (bb bitBucketAPI) makeComments(summary Summary, changes *bitBucketPRChanges return comments } +func (bb bitBucketAPI) limitComments(src []BitBucketPendingComment) []BitBucketPendingComment { + if len(src) <= bb.maxComments { + return src + } + comments := src[:bb.maxComments] + comments = append(comments, BitBucketPendingComment{ + Text: fmt.Sprintf(`This pint run would create %d comment(s), which is more than %d limit configured for pint. +%d comments were skipped and won't be visibile on this PR.`, len(src), bb.maxComments, len(src)-bb.maxComments), + Severity: "NORMAL", + Anchor: BitBucketPendingCommentAnchor{ + DiffType: "EFFECTIVE", + }, + }) + return comments +} + func (bb bitBucketAPI) pruneComments(pr *bitBucketPR, currentComments []bitBucketComment, pendingComments []BitBucketPendingComment) { for _, cur := range currentComments { slog.Debug( diff --git a/internal/reporter/bitbucket_comments_test.go b/internal/reporter/bitbucket_comments_test.go index e7f6e9c4..ed0c3870 100644 --- a/internal/reporter/bitbucket_comments_test.go +++ b/internal/reporter/bitbucket_comments_test.go @@ -19,6 +19,7 @@ func TestBitBucketMakeComments(t *testing.T) { summary Summary changes *bitBucketPRChanges comments []BitBucketPendingComment + maxComments int } commentBody := func(icon, severity, reporter, text string) string { @@ -31,10 +32,12 @@ func TestBitBucketMakeComments(t *testing.T) { testCases := []testCaseT{ { description: "empty summary", + maxComments: 50, comments: []BitBucketPendingComment{}, }, { description: "report not included in changes", + maxComments: 50, summary: Summary{reports: []Report{ { ReportedPath: "rule.yaml", @@ -50,6 +53,7 @@ func TestBitBucketMakeComments(t *testing.T) { }, { description: "reports included in changes", + maxComments: 50, summary: Summary{reports: []Report{ { ReportedPath: "rule.yaml", @@ -213,6 +217,7 @@ func TestBitBucketMakeComments(t *testing.T) { }, { description: "dedup reporter", + maxComments: 50, summary: Summary{reports: []Report{ { ReportedPath: "rule.yaml", @@ -269,6 +274,7 @@ func TestBitBucketMakeComments(t *testing.T) { }, { description: "dedup identical reports", + maxComments: 50, summary: Summary{reports: []Report{ { ReportedPath: "rule.yaml", @@ -325,6 +331,7 @@ func TestBitBucketMakeComments(t *testing.T) { }, { description: "dedup details", + maxComments: 50, summary: Summary{reports: []Report{ { ReportedPath: "rule.yaml", @@ -379,6 +386,144 @@ func TestBitBucketMakeComments(t *testing.T) { }, }, }, + { + description: "maxComments 2", + maxComments: 2, + summary: Summary{reports: []Report{ + { + ReportedPath: "rule.yaml", + SourcePath: "rule.yaml", + ModifiedLines: []int{2, 3}, + Problem: checks.Problem{ + Severity: checks.Bug, + Lines: parser.LineRange{ + First: 2, + Last: 2, + }, + Text: "first error", + Details: "first details", + Reporter: "r1", + }, + }, + { + ReportedPath: "rule.yaml", + SourcePath: "rule.yaml", + ModifiedLines: []int{2, 3}, + Problem: checks.Problem{ + Severity: checks.Warning, + Lines: parser.LineRange{ + First: 3, + Last: 3, + }, + Text: "second error", + Details: "second details", + Reporter: "r1", + }, + }, + { + ReportedPath: "rule.yaml", + SourcePath: "rule.yaml", + ModifiedLines: []int{2, 3}, + Problem: checks.Problem{ + Severity: checks.Bug, + Lines: parser.LineRange{ + First: 2, + Last: 2, + }, + Text: "third error", + Details: "third details", + Reporter: "r2", + }, + }, + { + ReportedPath: "rule.yaml", + SourcePath: "symlink.yaml", + ModifiedLines: []int{2, 3}, + Problem: checks.Problem{ + Severity: checks.Bug, + Lines: parser.LineRange{ + First: 2, + Last: 2, + }, + Text: "fourth error", + Details: "fourth details", + Reporter: "r2", + }, + }, + { + ReportedPath: "second.yaml", + SourcePath: "second.yaml", + ModifiedLines: []int{1, 2, 3}, + Problem: checks.Problem{ + Anchor: checks.AnchorBefore, + Severity: checks.Bug, + Lines: parser.LineRange{ + First: 2, + Last: 2, + }, + Text: "fifth error", + Details: "fifth details", + Reporter: "r2", + }, + }, + { + ReportedPath: "second.yaml", + SourcePath: "second.yaml", + ModifiedLines: []int{1, 2, 3}, + Problem: checks.Problem{ + Severity: checks.Bug, + Lines: parser.LineRange{ + First: 2, + Last: 2, + }, + Text: "sixth error", + Details: "sixth details", + Reporter: "r2", + }, + }, + }}, + changes: &bitBucketPRChanges{ + pathModifiedLines: map[string][]int{ + "rule.yaml": {2, 3}, + "second.yaml": {1, 2, 3}, + }, + pathLineMapping: map[string]map[int]int{ + "rule.yaml": {2: 2, 3: 3}, + "second.yaml": {1: 5, 2: 6, 3: 7}, + }, + }, + comments: []BitBucketPendingComment{ + { + Text: commentBody("stop_sign", "Bug", "r1", "first error\n\nfirst details"), + Severity: "BLOCKER", + Anchor: BitBucketPendingCommentAnchor{ + Path: "rule.yaml", + Line: 2, + LineType: "ADDED", + FileType: "TO", + DiffType: "EFFECTIVE", + }, + }, + { + Text: commentBody("warning", "Warning", "r1", "second error\n\nsecond details"), + Severity: "NORMAL", + Anchor: BitBucketPendingCommentAnchor{ + Path: "rule.yaml", + Line: 3, + LineType: "ADDED", + FileType: "TO", + DiffType: "EFFECTIVE", + }, + }, + { + Text: "This pint run would create 5 comment(s), which is more than 2 limit configured for pint.\n3 comments were skipped and won't be visibile on this PR.", + Severity: "NORMAL", + Anchor: BitBucketPendingCommentAnchor{ + DiffType: "EFFECTIVE", + }, + }, + }, + }, } for _, tc := range testCases { @@ -391,8 +536,10 @@ func TestBitBucketMakeComments(t *testing.T) { "token", "proj", "repo", - nil) - comments := r.api.makeComments(tc.summary, tc.changes) + tc.maxComments, + nil, + ) + comments := r.api.limitComments(r.api.makeComments(tc.summary, tc.changes)) if diff := cmp.Diff(tc.comments, comments); diff != "" { t.Errorf("api.makeComments() returned wrong output (-want +got):\n%s", diff) return diff --git a/internal/reporter/bitbucket_test.go b/internal/reporter/bitbucket_test.go index e1e7eeb3..21b46522 100644 --- a/internal/reporter/bitbucket_test.go +++ b/internal/reporter/bitbucket_test.go @@ -1969,6 +1969,7 @@ func TestBitBucketReporter(t *testing.T) { "token", "proj", "repo", + 50, tc.gitCmd) summary := reporter.NewSummary(tc.reports) err := r.Submit(summary) diff --git a/internal/reporter/github.go b/internal/reporter/github.go index 7b785179..87b03bc3 100644 --- a/internal/reporter/github.go +++ b/internal/reporter/github.go @@ -22,30 +22,32 @@ var reviewBody = "### This pull request was validated by [pint](https://github.c type GithubReporter struct { gitCmd git.CommandRunner - client *github.Client - version string - baseURL string - uploadURL string - authToken string - owner string - repo string - timeout time.Duration - prNum int + client *github.Client + version string + baseURL string + uploadURL string + authToken string + owner string + repo string + timeout time.Duration + prNum int + maxComments int } // NewGithubReporter creates a new GitHub reporter that reports // problems via comments on a given pull request number (integer). -func NewGithubReporter(version, baseURL, uploadURL string, timeout time.Duration, token, owner, repo string, prNum int, gitCmd git.CommandRunner) (_ GithubReporter, err error) { +func NewGithubReporter(version, baseURL, uploadURL string, timeout time.Duration, token, owner, repo string, prNum, maxComments int, gitCmd git.CommandRunner) (_ GithubReporter, err error) { gr := GithubReporter{ - version: version, - baseURL: baseURL, - uploadURL: uploadURL, - timeout: timeout, - authToken: token, - owner: owner, - repo: repo, - prNum: prNum, - gitCmd: gitCmd, + version: version, + baseURL: baseURL, + uploadURL: uploadURL, + timeout: timeout, + authToken: token, + owner: owner, + repo: repo, + prNum: prNum, + maxComments: maxComments, + gitCmd: gitCmd, } ts := oauth2.StaticTokenSource( @@ -132,6 +134,7 @@ func (gr GithubReporter) addReviewComments(headCommit string, summary Summary) e return err } + var added int for _, rep := range summary.Reports() { comment := reportToGitHubComment(headCommit, rep) @@ -157,6 +160,11 @@ func (gr GithubReporter) addReviewComments(headCommit string, summary Summary) e if err := gr.createComment(comment); err != nil { return err } + added++ + + if added >= gr.maxComments { + return gr.tooManyComments(headCommit, len(summary.Reports())) + } } return nil @@ -317,3 +325,12 @@ func reportToGitHubComment(headCommit string, rep Report) *github.PullRequestCom return &c } + +func (gr GithubReporter) tooManyComments(headCommit string, nrComments int) error { + comment := github.PullRequestComment{ + CommitID: github.String(headCommit), + Body: github.String(fmt.Sprintf(`This pint run would create %d comment(s), which is more than %d limit configured for pint. +%d comments were skipped and won't be visibile on this PR.`, nrComments, gr.maxComments, nrComments-gr.maxComments)), + } + return gr.createComment(&comment) +} diff --git a/internal/reporter/github_test.go b/internal/reporter/github_test.go index 9ca650d1..b5718b90 100644 --- a/internal/reporter/github_test.go +++ b/internal/reporter/github_test.go @@ -2,9 +2,11 @@ package reporter_test import ( "fmt" + "io" "log/slog" "net/http" "net/http/httptest" + "strings" "testing" "time" @@ -25,11 +27,12 @@ func TestGithubReporter(t *testing.T) { error func(uri string) string gitCmd git.CommandRunner - owner string - repo string - token string - prNum int - timeout time.Duration + owner string + repo string + token string + prNum int + maxComments int + timeout time.Duration } p := parser.NewParser() @@ -54,6 +57,7 @@ filename %s repo: "bar", token: "something", prNum: 123, + maxComments: 50, httpHandler: http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { time.Sleep(1 * time.Second) _, _ = w.Write([]byte("OK")) @@ -95,6 +99,7 @@ filename %s repo: "bar", token: "something", prNum: 123, + maxComments: 50, timeout: time.Second, gitCmd: func(args ...string) ([]byte, error) { if args[0] == "rev-parse" { @@ -141,6 +146,7 @@ filename %s repo: "bar", token: "something", prNum: 123, + maxComments: 50, timeout: time.Second, gitCmd: func(args ...string) ([]byte, error) { if args[0] == "rev-parse" { @@ -176,6 +182,7 @@ filename %s repo: "bar", token: "something", prNum: 123, + maxComments: 50, timeout: time.Second, gitCmd: func(args ...string) ([]byte, error) { if args[0] == "rev-parse" { @@ -222,6 +229,7 @@ filename %s repo: "bar", token: "something", prNum: 123, + maxComments: 50, timeout: time.Second, gitCmd: func(args ...string) ([]byte, error) { if args[0] == "rev-parse" { @@ -272,6 +280,7 @@ filename %s repo: "bar", token: "something", prNum: 123, + maxComments: 50, timeout: time.Second, gitCmd: func(args ...string) ([]byte, error) { if args[0] == "rev-parse" { @@ -312,6 +321,109 @@ filename %s }, }, }, + { + description: "maxComments 2", + owner: "foo", + repo: "bar", + token: "something", + prNum: 123, + maxComments: 2, + timeout: time.Second, + gitCmd: func(args ...string) ([]byte, error) { + if args[0] == "rev-parse" { + return []byte("fake-commit-id"), nil + } + if args[0] == "blame" { + content := blameLine("fake-commit-id", 2, "foo.txt", "up == 0") + return []byte(content), nil + } + return nil, nil + }, + httpHandler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.Method == http.MethodGet && r.URL.Path == "/api/v3/repos/foo/bar/pulls/123/reviews" { + _, _ = w.Write([]byte(`[{"id":1,"body":"### This pull request was validated by [pint](https://github.com/cloudflare/pint).\nxxxx"}]`)) + return + } + if r.Method == http.MethodGet && r.URL.Path == "/api/v3/repos/foo/bar/pulls/123/comments" { + _, _ = w.Write([]byte(`[{"id":1,"commit_id":"fake-commit-id","path":"foo.txt","line":2,"body":":stop_sign: [mock](https://cloudflare.github.io/pint/checks/mock.html): syntax error\n\nsyntax details"}]`)) + return + } + if r.Method == http.MethodPost && r.URL.Path == "/api/v3/repos/foo/bar/pulls/123/comments" { + body, _ := io.ReadAll(r.Body) + b := strings.TrimSpace(strings.TrimRight(string(body), "\n\t\r")) + switch b { + case `{"body":":stop_sign: [mock1](https://cloudflare.github.io/pint/checks/mock1.html): syntax error1\n\nsyntax details1","path":"","line":2,"side":"RIGHT","commit_id":"fake-commit-id"}`: + case `{"body":":stop_sign: [mock2](https://cloudflare.github.io/pint/checks/mock2.html): syntax error2\n\nsyntax details2","path":"","line":2,"side":"RIGHT","commit_id":"fake-commit-id"}`: + case `{"body":"This pint run would create 4 comment(s), which is more than 2 limit configured for pint.\n2 comments were skipped and won't be visibile on this PR.","commit_id":"fake-commit-id"}`: + default: + t.Errorf("Unexpected comment: %s", b) + } + } + _, _ = w.Write([]byte("")) + }), + reports: []reporter.Report{ + { + SourcePath: "foo.txt", + ModifiedLines: []int{2}, + Rule: mockRules[1], + Problem: checks.Problem{ + Lines: parser.LineRange{ + First: 2, + Last: 2, + }, + Reporter: "mock1", + Text: "syntax error1", + Details: "syntax details1", + Severity: checks.Bug, + }, + }, + { + SourcePath: "foo.txt", + ModifiedLines: []int{2}, + Rule: mockRules[1], + Problem: checks.Problem{ + Lines: parser.LineRange{ + First: 2, + Last: 2, + }, + Reporter: "mock2", + Text: "syntax error2", + Details: "syntax details2", + Severity: checks.Bug, + }, + }, + { + SourcePath: "foo.txt", + ModifiedLines: []int{2}, + Rule: mockRules[1], + Problem: checks.Problem{ + Lines: parser.LineRange{ + First: 2, + Last: 2, + }, + Reporter: "mock3", + Text: "syntax error3", + Details: "syntax details3", + Severity: checks.Fatal, + }, + }, + { + SourcePath: "foo.txt", + ModifiedLines: []int{2}, + Rule: mockRules[1], + Problem: checks.Problem{ + Lines: parser.LineRange{ + First: 2, + Last: 2, + }, + Reporter: "mock4", + Text: "syntax error4", + Details: "syntax details4", + Severity: checks.Fatal, + }, + }, + }, + }, } { t.Run(tc.description, func(t *testing.T) { slog.SetDefault(slogt.New(t)) @@ -348,6 +460,7 @@ filename %s tc.owner, tc.repo, tc.prNum, + tc.maxComments, tc.gitCmd, ) require.NoError(t, err)