cloudflare/pint

Public

mirrored from https://github.com/cloudflare/pintAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
v0.74.3

Branches

Tags

  • No tags available.
0Branches0Tags
Go to file
Add file
Code

Clone

HTTPS

Download ZIP

cmd/pint/lint.go

246lines · modecode

1package main
2
3import (
4 "context"
5 "errors"
6 "fmt"
7 "log/slog"
8 "os"
9 "regexp"
10
11 "github.com/cloudflare/pint/internal/checks"
12 "github.com/cloudflare/pint/internal/config"
13 "github.com/cloudflare/pint/internal/diags"
14 "github.com/cloudflare/pint/internal/discovery"
15 "github.com/cloudflare/pint/internal/git"
16 "github.com/cloudflare/pint/internal/reporter"
17
18 "github.com/urfave/cli/v3"
19)
20
21var requireOwnerFlag = "require-owner"
22
23var lintCmd = &cli.Command{
24 Name: "lint",
25 Usage: "Check specified files or directories (can be a glob).",
26 Action: actionLint,
27 Flags: []cli.Flag{
28 &cli.BoolFlag{
29 Name: requireOwnerFlag,
30 Aliases: []string{"r"},
31 Value: false,
32 Usage: "Require all rules to have an owner set via comment.",
33 },
34 &cli.StringFlag{
35 Name: minSeverityFlag,
36 Aliases: []string{"n"},
37 Value: "warning",
38 Usage: "Set minimum severity for reported problems.",
39 },
40 &cli.StringFlag{
41 Name: failOnFlag,
42 Aliases: []string{"w"},
43 Value: "bug",
44 Usage: "Exit with non-zero code if there are problems with given severity (or higher) detected.",
45 },
46 &cli.BoolFlag{
47 Name: teamCityFlag,
48 Aliases: []string{"t"},
49 Value: false,
50 Usage: "Report problems using TeamCity Service Messages.",
51 },
52 &cli.StringFlag{
53 Name: checkStyleFlag,
54 Aliases: []string{"c"},
55 Value: "",
56 Usage: "Write a checkstyle xml formatted report of all problems to this path.",
57 },
58 &cli.StringFlag{
59 Name: jsonFlag,
60 Aliases: []string{"j"},
61 Value: "",
62 Usage: "Write a JSON formatted report of all problems to this path.",
63 },
64 },
65}
66
67func actionLint(ctx context.Context, c *cli.Command) error {
68 meta, err := actionSetup(c)
69 if err != nil {
70 return err
71 }
72
73 paths := c.Args().Slice()
74 if len(paths) == 0 {
75 return errors.New("at least one file or directory required")
76 }
77
78 slog.Info("Finding all rules to check", slog.Any("paths", paths))
79 allowedOwners := meta.cfg.Owners.CompileAllowed()
80 finder := discovery.NewGlobFinder(
81 paths,
82 git.NewPathFilter(
83 config.MustCompileRegexes(meta.cfg.Parser.Include...),
84 config.MustCompileRegexes(meta.cfg.Parser.Exclude...),
85 config.MustCompileRegexes(meta.cfg.Parser.Relaxed...),
86 ),
87 parseSchema(meta.cfg.Parser.Schema),
88 parseNames(meta.cfg.Parser.Names),
89 allowedOwners,
90 )
91 entries, err := finder.Find()
92 if err != nil {
93 return err
94 }
95
96 ctx = context.WithValue(ctx, config.CommandKey, config.LintCommand)
97
98 gen := config.NewPrometheusGenerator(meta.cfg, metricsRegistry)
99 defer gen.Stop()
100
101 if err = gen.GenerateStatic(); err != nil {
102 return err
103 }
104
105 summary, err := checkRules(ctx, meta.workers, meta.isOffline, gen, meta.cfg, entries)
106 if err != nil {
107 return err
108 }
109
110 if c.Bool(requireOwnerFlag) {
111 summary.Report(verifyOwners(entries, allowedOwners)...)
112 }
113
114 minSeverity, err := checks.ParseSeverity(c.String(minSeverityFlag))
115 if err != nil {
116 return fmt.Errorf("invalid --%s value: %w", minSeverityFlag, err)
117 }
118 failOn, err := checks.ParseSeverity(c.String(failOnFlag))
119 if err != nil {
120 return fmt.Errorf("invalid --%s value: %w", failOnFlag, err)
121 }
122
123 reps := []reporter.Reporter{}
124 if c.Bool(teamCityFlag) {
125 reps = append(reps, reporter.NewTeamCityReporter(os.Stderr))
126 } else {
127 reps = append(
128 reps,
129 reporter.NewConsoleReporter(os.Stderr, minSeverity, c.Bool(noColorFlag), c.Bool(showDupsFlag)),
130 )
131 }
132
133 if c.String(checkStyleFlag) != "" {
134 var f *os.File
135 f, err = os.Create(c.String(checkStyleFlag))
136 if err != nil {
137 return err
138 }
139 defer f.Close()
140 reps = append(reps, reporter.NewCheckStyleReporter(f))
141 }
142
143 if c.String(jsonFlag) != "" {
144 var j *os.File
145 j, err = os.Create(c.String(jsonFlag))
146 if err != nil {
147 return err
148 }
149 defer j.Close()
150 reps = append(reps, reporter.NewJSONReporter(j))
151 }
152
153 summary.SortReports()
154 summary.Dedup()
155 for _, rep := range reps {
156 err = rep.Submit(summary)
157 if err != nil {
158 return fmt.Errorf("submitting reports: %w", err)
159 }
160 }
161
162 bySeverity := summary.CountBySeverity()
163 var problems, hiddenProblems, failProblems int
164 for s, c := range bySeverity {
165 if s >= failOn {
166 failProblems += c
167 }
168 if s < minSeverity {
169 hiddenProblems += c
170 }
171 if s >= checks.Bug {
172 problems += c
173 }
174 }
175 if len(bySeverity) > 0 {
176 slog.Info("Problems found", logSeverityCounters(bySeverity)...)
177 }
178 if hiddenProblems > 0 {
179 slog.Info(fmt.Sprintf("%d problem(s) not visible because of --%s=%s flag", hiddenProblems, minSeverityFlag, c.String(minSeverityFlag)))
180 }
181
182 if failProblems > 0 {
183 return fmt.Errorf("found %d problem(s) with severity %s or higher", failProblems, failOn)
184 }
185 return nil
186}
187
188func verifyOwners(entries []discovery.Entry, allowedOwners []*regexp.Regexp) (reports []reporter.Report) {
189 for _, entry := range entries {
190 if entry.State == discovery.Removed {
191 continue
192 }
193 if entry.PathError != nil {
194 continue
195 }
196 if entry.Owner == "" {
197 reports = append(reports, reporter.Report{
198 Path: entry.Path,
199 ModifiedLines: entry.ModifiedLines,
200 Rule: entry.Rule,
201 Owner: "",
202 Problem: checks.Problem{
203 Anchor: checks.AnchorAfter,
204 Lines: entry.Rule.Lines,
205 Reporter: discovery.RuleOwnerComment,
206 Summary: "missing owner",
207 Details: "",
208 Severity: checks.Bug,
209 Diagnostics: []diags.Diagnostic{
210 checks.WholeRuleDiag(entry.Rule, fmt.Sprintf("`%s` comments are required in all files, please add a `# pint %s $owner` somewhere in this file and/or `# pint %s $owner` on top of each rule.",
211 discovery.RuleOwnerComment, discovery.FileOwnerComment, discovery.RuleOwnerComment)),
212 },
213 },
214 IsDuplicate: false,
215 Duplicates: nil,
216 })
217 goto NEXT
218 }
219 for _, re := range allowedOwners {
220 if re.MatchString(entry.Owner) {
221 goto NEXT
222 }
223 }
224 reports = append(reports, reporter.Report{
225 Path: entry.Path,
226 ModifiedLines: entry.ModifiedLines,
227 Rule: entry.Rule,
228 Owner: "",
229 Problem: checks.Problem{
230 Anchor: checks.AnchorAfter,
231 Lines: entry.Rule.Lines,
232 Reporter: discovery.RuleOwnerComment,
233 Summary: "invalid owner",
234 Details: "",
235 Severity: checks.Bug,
236 Diagnostics: []diags.Diagnostic{
237 checks.WholeRuleDiag(entry.Rule, fmt.Sprintf("This rule is set as owned by `%s` but `%s` doesn't match any of the allowed owner values.", entry.Owner, entry.Owner)),
238 },
239 },
240 IsDuplicate: false,
241 Duplicates: nil,
242 })
243 NEXT:
244 }
245 return reports
246}
247