cloudflare/pint

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
v0.68.0

Branches

Tags

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

Clone

HTTPS

Download ZIP

internal/discovery/discovery.go

196lines · modecode

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