cloudflare/pint

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
v0.65.2

Branches

Tags

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

Clone

HTTPS

Download ZIP

cmd/pint/lint.go

189lines · 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/discovery"
14 "github.com/cloudflare/pint/internal/git"
15 "github.com/cloudflare/pint/internal/reporter"
16
17 "github.com/urfave/cli/v2"
18)
19
20var requireOwnerFlag = "require-owner"
21
22var lintCmd = &cli.Command{
23 Name: "lint",
24 Usage: "Check specified files or directories (can be a glob).",
25 Action: actionLint,
26 Flags: []cli.Flag{
27 &cli.BoolFlag{
28 Name: requireOwnerFlag,
29 Aliases: []string{"r"},
30 Value: false,
31 Usage: "Require all rules to have an owner set via comment.",
32 },
33 &cli.StringFlag{
34 Name: minSeverityFlag,
35 Aliases: []string{"n"},
36 Value: "warning",
37 Usage: "Set minimum severity for reported problems.",
38 },
39 &cli.StringFlag{
40 Name: failOnFlag,
41 Aliases: []string{"w"},
42 Value: "bug",
43 Usage: "Exit with non-zero code if there are problems with given severity (or higher) detected.",
44 },
45 &cli.BoolFlag{
46 Name: teamCityFlag,
47 Aliases: []string{"t"},
48 Value: false,
49 Usage: "Report problems using TeamCity Service Messages.",
50 },
51 },
52}
53
54func actionLint(c *cli.Context) error {
55 meta, err := actionSetup(c)
56 if err != nil {
57 return err
58 }
59
60 paths := c.Args().Slice()
61 if len(paths) == 0 {
62 return errors.New("at least one file or directory required")
63 }
64
65 slog.Info("Finding all rules to check", slog.Any("paths", paths))
66 finder := discovery.NewGlobFinder(paths, git.NewPathFilter(
67 config.MustCompileRegexes(meta.cfg.Parser.Include...),
68 config.MustCompileRegexes(meta.cfg.Parser.Exclude...),
69 config.MustCompileRegexes(meta.cfg.Parser.Relaxed...),
70 ))
71 entries, err := finder.Find()
72 if err != nil {
73 return err
74 }
75
76 ctx := context.WithValue(context.Background(), config.CommandKey, config.LintCommand)
77
78 gen := config.NewPrometheusGenerator(meta.cfg, metricsRegistry)
79 defer gen.Stop()
80
81 if err = gen.GenerateStatic(); err != nil {
82 return err
83 }
84
85 summary, err := checkRules(ctx, meta.workers, meta.isOffline, gen, meta.cfg, entries)
86 if err != nil {
87 return err
88 }
89
90 if c.Bool(requireOwnerFlag) {
91 summary.Report(verifyOwners(entries, meta.cfg.Owners.CompileAllowed())...)
92 }
93
94 minSeverity, err := checks.ParseSeverity(c.String(minSeverityFlag))
95 if err != nil {
96 return fmt.Errorf("invalid --%s value: %w", minSeverityFlag, err)
97 }
98 failOn, err := checks.ParseSeverity(c.String(failOnFlag))
99 if err != nil {
100 return fmt.Errorf("invalid --%s value: %w", failOnFlag, err)
101 }
102
103 var r reporter.Reporter
104 if c.Bool(teamCityFlag) {
105 r = reporter.NewTeamCityReporter(os.Stderr)
106 } else {
107 r = reporter.NewConsoleReporter(os.Stderr, minSeverity)
108 }
109
110 err = r.Submit(summary)
111 if err != nil {
112 return err
113 }
114
115 bySeverity := summary.CountBySeverity()
116 var problems, hiddenProblems, failProblems int
117 for s, c := range bySeverity {
118 if s >= failOn {
119 failProblems++
120 }
121 if s < minSeverity {
122 hiddenProblems++
123 }
124 if s >= checks.Bug {
125 problems += c
126 }
127 }
128 if len(bySeverity) > 0 {
129 slog.Info("Problems found", logSeverityCounters(bySeverity)...)
130 }
131 if hiddenProblems > 0 {
132 slog.Info(fmt.Sprintf("%d problem(s) not visible because of --%s=%s flag", hiddenProblems, minSeverityFlag, c.String(minSeverityFlag)))
133 }
134
135 if failProblems > 0 {
136 return fmt.Errorf("found %d problem(s) with severity %s or higher", failProblems, failOn)
137 }
138 return nil
139}
140
141func verifyOwners(entries []discovery.Entry, allowedOwners []*regexp.Regexp) (reports []reporter.Report) {
142 for _, entry := range entries {
143 if entry.State == discovery.Removed {
144 continue
145 }
146 if entry.PathError != nil {
147 continue
148 }
149 if entry.Owner == "" {
150 reports = append(reports, reporter.Report{
151 Path: discovery.Path{
152 Name: entry.Path.Name,
153 SymlinkTarget: entry.Path.SymlinkTarget,
154 },
155 ModifiedLines: entry.ModifiedLines,
156 Rule: entry.Rule,
157 Problem: checks.Problem{
158 Lines: entry.Rule.Lines,
159 Reporter: discovery.RuleOwnerComment,
160 Text: 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.",
161 discovery.RuleOwnerComment, discovery.FileOwnerComment, discovery.RuleOwnerComment),
162 Severity: checks.Bug,
163 },
164 })
165 goto NEXT
166 }
167 for _, re := range allowedOwners {
168 if re.MatchString(entry.Owner) {
169 goto NEXT
170 }
171 }
172 reports = append(reports, reporter.Report{
173 Path: discovery.Path{
174 Name: entry.Path.Name,
175 SymlinkTarget: entry.Path.SymlinkTarget,
176 },
177 ModifiedLines: entry.ModifiedLines,
178 Rule: entry.Rule,
179 Problem: checks.Problem{
180 Lines: entry.Rule.Lines,
181 Reporter: discovery.RuleOwnerComment,
182 Text: 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),
183 Severity: checks.Bug,
184 },
185 })
186 NEXT:
187 }
188 return reports
189}
190