1
16
17
18
19 package warn
20
21 import (
22 "regexp"
23 "sort"
24 "strings"
25
26 "github.com/bazelbuild/buildtools/build"
27 "github.com/bazelbuild/buildtools/edit"
28 )
29
30
31
32
33
34 func packageOnTopWarning(f *build.File) []*LinterFinding {
35 if f.Type == build.TypeWorkspace || f.Type == build.TypeModule {
36
37 return nil
38 }
39
40
41 misplacedPackages := make(map[int]*build.CallExpr)
42 firstStmtIndex := -1
43 for i := 0; i < len(f.Stmt); i++ {
44 stmt := f.Stmt[i]
45
46
47
48
49
50 if _, ok := stmt.(*build.AssignExpr); ok {
51 break
52 }
53
54 _, isString := stmt.(*build.StringExpr)
55 _, isComment := stmt.(*build.CommentBlock)
56 _, isLoad := stmt.(*build.LoadStmt)
57 _, isLicenses := edit.ExprToRule(stmt, "licenses")
58 _, isPackageGroup := edit.ExprToRule(stmt, "package_group")
59 if isString || isComment || isLoad || isLicenses || isPackageGroup || stmt == nil {
60 continue
61 }
62 rule, ok := edit.ExprToRule(stmt, "package")
63 if !ok {
64 if firstStmtIndex == -1 {
65 firstStmtIndex = i
66 }
67 continue
68 }
69 if firstStmtIndex == -1 {
70 continue
71 }
72 misplacedPackages[i] = rule.Call
73 }
74 offset := len(misplacedPackages)
75 if offset == 0 {
76 return nil
77 }
78
79
80 if firstStmtIndex == -1 {
81 firstStmtIndex = 0
82 }
83 var replacements []LinterReplacement
84 for i := range f.Stmt {
85 if i < firstStmtIndex {
86
87 continue
88 } else if _, ok := misplacedPackages[i]; ok {
89
90 replacements = append(replacements, LinterReplacement{&f.Stmt[firstStmtIndex], f.Stmt[i]})
91 firstStmtIndex++
92 offset--
93 if offset == 0 {
94
95 break
96 }
97 } else {
98
99
100 replacements = append(replacements, LinterReplacement{&f.Stmt[i+offset], f.Stmt[i]})
101 }
102 }
103
104 var findings []*LinterFinding
105 for _, load := range misplacedPackages {
106 findings = append(findings, makeLinterFinding(load,
107 "Package declaration should be at the top of the file, after the load() statements, "+
108 "but before any call to a rule or a macro. "+
109 "package_group() and licenses() may be called before package().", replacements...))
110 }
111
112 return findings
113 }
114
115 func unsortedDictItemsWarning(f *build.File) []*LinterFinding {
116 var findings []*LinterFinding
117
118 compareItems := func(item1, item2 *build.KeyValueExpr) bool {
119 key1 := item1.Key.(*build.StringExpr).Value
120 key2 := item2.Key.(*build.StringExpr).Value
121
122 if strings.HasPrefix(key1, "_") {
123 return strings.HasPrefix(key2, "_") && key1 < key2
124 }
125 if strings.HasPrefix(key2, "_") {
126 return true
127 }
128
129
130 const conditionsDefault = "//conditions:default"
131 if key1 == conditionsDefault {
132 return false
133 } else if key2 == conditionsDefault {
134 return true
135 }
136
137 return key1 < key2
138 }
139
140 build.WalkPointers(f, func(expr *build.Expr, stack []build.Expr) {
141 dict, ok := (*expr).(*build.DictExpr)
142
143 mustSkipCheck := func(expr build.Expr) bool {
144 return edit.ContainsComments(expr, "@unsorted-dict-items")
145 }
146
147 if !ok || mustSkipCheck(dict) {
148 return
149 }
150
151
152 for i := len(stack) - 1; i >= 0; i-- {
153 if mustSkipCheck(stack[i]) {
154 return
155 }
156 }
157 var sortedItems []*build.KeyValueExpr
158 for _, item := range dict.List {
159
160 if _, ok = item.Key.(*build.StringExpr); !ok {
161 continue
162 }
163 sortedItems = append(sortedItems, item)
164 }
165
166
167 comp := func(i, j int) bool {
168 return compareItems(sortedItems[i], sortedItems[j])
169 }
170
171 var misplacedItems []*build.KeyValueExpr
172 for i := 1; i < len(sortedItems); i++ {
173 if comp(i, i-1) {
174 misplacedItems = append(misplacedItems, sortedItems[i])
175 }
176 }
177
178 if len(misplacedItems) == 0 {
179
180 return
181 }
182 newDict := *dict
183 newDict.List = append([]*build.KeyValueExpr{}, dict.List...)
184
185 sort.SliceStable(sortedItems, comp)
186 sortedItemIndex := 0
187 for originalItemIndex := 0; originalItemIndex < len(dict.List); originalItemIndex++ {
188 item := dict.List[originalItemIndex]
189 if _, ok := item.Key.(*build.StringExpr); !ok {
190 continue
191 }
192 newDict.List[originalItemIndex] = sortedItems[sortedItemIndex]
193 sortedItemIndex++
194 }
195
196 for _, item := range misplacedItems {
197 findings = append(findings, makeLinterFinding(item,
198 "Dictionary items are out of their lexicographical order.",
199 LinterReplacement{expr, &newDict}))
200 }
201 return
202 })
203 return findings
204 }
205
206
207
208 func skylarkToStarlark(s string) string {
209 switch {
210 case s == "SKYLARK":
211 return "STARLARK"
212 case strings.HasPrefix(s, "S"):
213 return "Starlark"
214 default:
215 return "starlark"
216 }
217 }
218
219
220
221
222
223
224
225 func replaceSkylark(s string) (newString string, changed bool) {
226 skylarkRegex := regexp.MustCompile("(?i)skylark")
227 newString = s
228 for _, r := range skylarkRegex.FindAllStringIndex(s, -1) {
229 if r[0] > 0 && s[r[0]-1] == '/' {
230 continue
231 }
232 if r[1] < len(s)-1 && s[r[1]+1] == '/' {
233 continue
234 }
235 newString = newString[:r[0]] + skylarkToStarlark(newString[r[0]:r[1]]) + newString[r[1]:]
236 }
237 return newString, newString != s
238 }
239
240 func skylarkCommentWarning(f *build.File) []*LinterFinding {
241 var findings []*LinterFinding
242 msg := `"Skylark" is an outdated name of the language, please use "starlark" instead.`
243
244
245 build.WalkPointers(f, func(expr *build.Expr, stack []build.Expr) {
246 comments := (*expr).Comment()
247 newComments := build.Comments{
248 Before: append([]build.Comment{}, comments.Before...),
249 Suffix: append([]build.Comment{}, comments.Suffix...),
250 After: append([]build.Comment{}, comments.After...),
251 }
252 isModified := false
253 var start, end build.Position
254
255 for _, block := range []*[]build.Comment{&newComments.Before, &newComments.Suffix, &newComments.After} {
256 for i, comment := range *block {
257
258 if strings.Contains(comment.Token, "disable=skylark-docstring") {
259 continue
260 }
261 newValue, changed := replaceSkylark(comment.Token)
262 (*block)[i] = build.Comment{
263 Start: comment.Start,
264 Token: newValue,
265 }
266 if changed {
267 isModified = true
268 start, end = comment.Span()
269 }
270 }
271 }
272 if !isModified {
273 return
274 }
275 newExpr := (*expr).Copy()
276 newExpr.Comment().Before = newComments.Before
277 newExpr.Comment().Suffix = newComments.Suffix
278 newExpr.Comment().After = newComments.After
279 finding := makeLinterFinding(*expr, msg, LinterReplacement{expr, newExpr})
280 finding.Start = start
281 finding.End = end
282 findings = append(findings, finding)
283 })
284
285 return findings
286 }
287
288 func checkSkylarkDocstring(stmts []build.Expr) *LinterFinding {
289 msg := `"Skylark" is an outdated name of the language, please use "starlark" instead.`
290
291 doc, ok := getDocstring(stmts)
292 if !ok {
293 return nil
294 }
295 docString := (*doc).(*build.StringExpr)
296 newValue, updated := replaceSkylark(docString.Value)
297 if !updated {
298 return nil
299 }
300 newDocString := *docString
301 newDocString.Value = newValue
302 return makeLinterFinding(docString, msg, LinterReplacement{doc, &newDocString})
303 }
304
305 func skylarkDocstringWarning(f *build.File) []*LinterFinding {
306 var findings []*LinterFinding
307
308
309 if finding := checkSkylarkDocstring(f.Stmt); finding != nil {
310 findings = append(findings, finding)
311 }
312
313
314 for _, stmt := range f.Stmt {
315 def, ok := stmt.(*build.DefStmt)
316 if !ok {
317 continue
318 }
319 if finding := checkSkylarkDocstring(def.Body); finding != nil {
320 findings = append(findings, finding)
321 }
322 }
323
324 return findings
325 }
326
View as plain text