cloudflare/pint

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
v0.63.0

Branches

Tags

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

Clone

HTTPS

Download ZIP

internal/discovery/discovery.go

200lines · modecode

1package discovery
2
3import (
4 "encoding/json"
5 "errors"
6 "fmt"
7 "io"
8 "log/slog"
9 "time"
10
11 "golang.org/x/exp/slices"
12
13 "github.com/cloudflare/pint/internal/comments"
14 "github.com/cloudflare/pint/internal/parser"
15)
16
17const (
18 FileOwnerComment = "file/owner"
19 FileDisabledCheckComment = "file/disable"
20 FileSnoozeCheckComment = "file/snooze"
21 RuleOwnerComment = "rule/owner"
22)
23
24type FileIgnoreError struct {
25 Err error
26 Line int
27}
28
29func (fe FileIgnoreError) Error() string {
30 return fe.Err.Error()
31}
32
33type ChangeType uint8
34
35func (c ChangeType) String() string {
36 switch c {
37 case Unknown:
38 return "unknown"
39 case Noop:
40 return "noop"
41 case Added:
42 return "added"
43 case Modified:
44 return "modified"
45 case Removed:
46 return "removed"
47 case Moved:
48 return "moved"
49 case Excluded:
50 return "excluded"
51 default:
52 return "---"
53 }
54}
55
56func (c *ChangeType) MarshalJSON() ([]byte, error) {
57 return json.Marshal(c.String())
58}
59
60const (
61 Unknown ChangeType = iota
62 Noop
63 Added
64 Modified
65 Removed
66 Moved
67 Excluded
68)
69
70type Path struct {
71 Name string // file path, it can be symlink
72 SymlinkTarget string // symlink target, or the same as name if not a symlink
73}
74
75func (p Path) String() string {
76 if p.Name == p.SymlinkTarget {
77 return p.Name
78 }
79 return fmt.Sprintf("%s ~> %s", p.Name, p.SymlinkTarget)
80}
81
82type Entry struct {
83 PathError error
84 Path Path
85 Owner string
86 ModifiedLines []int
87 DisabledChecks []string
88 Rule parser.Rule
89 State ChangeType
90}
91
92func readRules(reportedPath, sourcePath string, r io.Reader, isStrict bool) (entries []Entry, err error) {
93 content, fileComments, err := parser.ReadContent(r)
94 if err != nil {
95 return nil, err
96 }
97
98 contentLines := parser.LineRange{
99 First: min(content.TotalLines, 1),
100 Last: content.TotalLines,
101 }
102
103 var fileOwner string
104 var disabledChecks []string
105 for _, comment := range fileComments {
106 // nolint:exhaustive
107 switch comment.Type {
108 case comments.FileOwnerType:
109 owner := comment.Value.(comments.Owner)
110 fileOwner = owner.Name
111 case comments.FileDisableType:
112 disable := comment.Value.(comments.Disable)
113 if !slices.Contains(disabledChecks, disable.Match) {
114 disabledChecks = append(disabledChecks, disable.Match)
115 }
116 case comments.FileSnoozeType:
117 snooze := comment.Value.(comments.Snooze)
118 if !snooze.Until.After(time.Now()) {
119 continue
120 }
121 if !slices.Contains(disabledChecks, snooze.Match) {
122 disabledChecks = append(disabledChecks, snooze.Match)
123 }
124 slog.Debug(
125 "Check snoozed by comment",
126 slog.String("check", snooze.Match),
127 slog.String("match", snooze.Match),
128 slog.Time("until", snooze.Until),
129 )
130 case comments.InvalidComment:
131 entries = append(entries, Entry{
132 Path: Path{
133 Name: sourcePath,
134 SymlinkTarget: reportedPath,
135 },
136 PathError: comment.Value.(comments.Invalid).Err,
137 Owner: fileOwner,
138 ModifiedLines: contentLines.Expand(),
139 })
140 }
141 }
142
143 if content.Ignored {
144 entries = append(entries, Entry{
145 Path: Path{
146 Name: sourcePath,
147 SymlinkTarget: reportedPath,
148 },
149 PathError: FileIgnoreError{
150 Line: content.IgnoreLine,
151 // nolint:revive
152 Err: errors.New("This file was excluded from pint checks."),
153 },
154 Owner: fileOwner,
155 ModifiedLines: contentLines.Expand(),
156 })
157 return entries, nil
158 }
159
160 p := parser.NewParser(isStrict)
161 rules, err := p.Parse(content.Body)
162 if err != nil {
163 slog.Warn(
164 "Failed to parse file content",
165 slog.Any("err", err),
166 slog.String("path", sourcePath),
167 slog.String("lines", contentLines.String()),
168 )
169 entries = append(entries, Entry{
170 Path: Path{
171 Name: sourcePath,
172 SymlinkTarget: reportedPath,
173 },
174 PathError: err,
175 Owner: fileOwner,
176 ModifiedLines: contentLines.Expand(),
177 })
178 return entries, nil
179 }
180
181 for _, rule := range rules {
182 ruleOwner := fileOwner
183 for _, owner := range comments.Only[comments.Owner](rule.Comments, comments.RuleOwnerType) {
184 ruleOwner = owner.Name
185 }
186 entries = append(entries, Entry{
187 Path: Path{
188 Name: sourcePath,
189 SymlinkTarget: reportedPath,
190 },
191 Rule: rule,
192 ModifiedLines: rule.Lines.Expand(),
193 Owner: ruleOwner,
194 DisabledChecks: disabledChecks,
195 })
196 }
197
198 slog.Debug("File parsed", slog.String("path", sourcePath), slog.Int("rules", len(entries)))
199 return entries, nil
200}
201