1
16
17
18
19 package warn
20
21 import (
22 "fmt"
23 "sort"
24
25 "github.com/bazelbuild/buildtools/build"
26 "github.com/bazelbuild/buildtools/bzlenv"
27 "github.com/bazelbuild/buildtools/edit"
28 "github.com/bazelbuild/buildtools/tables"
29 )
30
31
32
33
34
35
36
37
38 func negateExpression(expr build.Expr) build.Expr {
39 paren, ok := expr.(*build.ParenExpr)
40 if ok {
41 newParen := *paren
42 newParen.X = negateExpression(paren.X)
43 return &newParen
44 }
45
46 unary, ok := expr.(*build.UnaryExpr)
47 if ok && unary.Op == "not" {
48 return unary.X
49 }
50
51 boolean, ok := expr.(*build.Ident)
52 if ok {
53 newBoolean := *boolean
54 if boolean.Name == "True" {
55 newBoolean.Name = "False"
56 } else {
57 newBoolean.Name = "True"
58 }
59 return &newBoolean
60 }
61
62 return &build.UnaryExpr{
63 Op: "not",
64 X: expr,
65 }
66 }
67
68
69
70 func getParam(attrs []build.Expr, paramName string) (int, *build.Ident, *build.AssignExpr) {
71 for i, attr := range attrs {
72 as, ok := attr.(*build.AssignExpr)
73 if !ok {
74 continue
75 }
76 name, ok := (as.LHS).(*build.Ident)
77 if !ok || name.Name != paramName {
78 continue
79 }
80 return i, name, as
81 }
82 return -1, nil, nil
83 }
84
85
86 func isFunctionCall(expr build.Expr, name string) (*build.CallExpr, bool) {
87 call, ok := expr.(*build.CallExpr)
88 if !ok {
89 return nil, false
90 }
91 if ident, ok := call.X.(*build.Ident); ok && ident.Name == name {
92 return call, true
93 }
94 return nil, false
95 }
96
97
98
99 func globalVariableUsageCheck(f *build.File, global, alternative string) []*LinterFinding {
100 var findings []*LinterFinding
101
102 if f.Type != build.TypeBzl {
103 return findings
104 }
105
106 var walk func(e *build.Expr, env *bzlenv.Environment)
107 walk = func(e *build.Expr, env *bzlenv.Environment) {
108 defer bzlenv.WalkOnceWithEnvironment(*e, env, walk)
109
110 ident, ok := (*e).(*build.Ident)
111 if !ok {
112 return
113 }
114 if ident.Name != global {
115 return
116 }
117 if binding := env.Get(ident.Name); binding != nil {
118 return
119 }
120
121
122 newIdent := *ident
123 newIdent.Name = alternative
124
125 findings = append(findings, makeLinterFinding(ident,
126 fmt.Sprintf(`Global variable %q is deprecated in favor of %q. Please rename it.`, global, alternative),
127 LinterReplacement{e, &newIdent}))
128 }
129 var expr build.Expr = f
130 walk(&expr, bzlenv.NewEnvironment())
131
132 return findings
133 }
134
135
136
137 func insertLoad(f *build.File, module string, symbols []string) *LinterReplacement {
138
139 for i, stmt := range f.Stmt {
140 load, ok := stmt.(*build.LoadStmt)
141 if !ok || load.Module.Value != module {
142 continue
143 }
144
145
146 newLoad := *load
147 if !edit.AppendToLoad(&newLoad, symbols, symbols) {
148 return nil
149 }
150 return &LinterReplacement{&(f.Stmt[i]), &newLoad}
151 }
152
153
154
155 i := 0
156 for i = range f.Stmt {
157 stmt := f.Stmt[i]
158 _, isComment := stmt.(*build.CommentBlock)
159 _, isString := stmt.(*build.StringExpr)
160 isDocString := isString && i == 0
161 if !isComment && !isDocString {
162
163 break
164 }
165 }
166 stmts := append([]build.Expr{}, f.Stmt[:i]...)
167 stmts = append(stmts, nil)
168 stmts = append(stmts, f.Stmt[i:]...)
169 f.Stmt = stmts
170
171 return &LinterReplacement{&(f.Stmt[i]), edit.NewLoad(module, symbols, symbols)}
172 }
173
174 func notLoadedFunctionUsageCheckInternal(expr *build.Expr, env *bzlenv.Environment, globals []string, loadFrom string) ([]string, []*LinterFinding) {
175 var loads []string
176 var findings []*LinterFinding
177
178 call, ok := (*expr).(*build.CallExpr)
179 if !ok {
180 return loads, findings
181 }
182
183 var name string
184 var replacements []LinterReplacement
185 switch node := call.X.(type) {
186 case *build.DotExpr:
187
188 ident, ok := node.X.(*build.Ident)
189 if !ok || ident.Name != "native" {
190 return loads, findings
191 }
192
193 name = node.Name
194
195 newCall := *call
196 newCall.X = &build.Ident{Name: node.Name}
197 replacements = append(replacements, LinterReplacement{expr, &newCall})
198 case *build.Ident:
199
200 if binding := env.Get(node.Name); binding != nil {
201 return loads, findings
202 }
203 name = node.Name
204 default:
205 return loads, findings
206 }
207
208 for _, global := range globals {
209 if name == global {
210 loads = append(loads, name)
211 findings = append(findings,
212 makeLinterFinding(call.X, fmt.Sprintf(`Function %q is not global anymore and needs to be loaded from %q.`, global, loadFrom), replacements...))
213 break
214 }
215 }
216
217 return loads, findings
218 }
219
220 func notLoadedSymbolUsageCheckInternal(expr *build.Expr, env *bzlenv.Environment, globals []string, loadFrom string) ([]string, []*LinterFinding) {
221 var loads []string
222 var findings []*LinterFinding
223
224 ident, ok := (*expr).(*build.Ident)
225 if !ok {
226 return loads, findings
227 }
228 if binding := env.Get(ident.Name); binding != nil {
229 return loads, findings
230 }
231
232 for _, global := range globals {
233 if ident.Name == global {
234 loads = append(loads, ident.Name)
235 findings = append(findings,
236 makeLinterFinding(ident, fmt.Sprintf(`Symbol %q is not global anymore and needs to be loaded from %q.`, global, loadFrom)))
237 break
238 }
239 }
240
241 return loads, findings
242 }
243
244
245
246 func notLoadedUsageCheck(f *build.File, functions, symbols []string, loadFrom string) []*LinterFinding {
247 toLoad := make(map[string]bool)
248 var findings []*LinterFinding
249
250 var walk func(expr *build.Expr, env *bzlenv.Environment)
251 walk = func(expr *build.Expr, env *bzlenv.Environment) {
252 defer bzlenv.WalkOnceWithEnvironment(*expr, env, walk)
253
254 functionLoads, functionFindings := notLoadedFunctionUsageCheckInternal(expr, env, functions, loadFrom)
255 findings = append(findings, functionFindings...)
256 for _, load := range functionLoads {
257 toLoad[load] = true
258 }
259
260 symbolLoads, symbolFindings := notLoadedSymbolUsageCheckInternal(expr, env, symbols, loadFrom)
261 findings = append(findings, symbolFindings...)
262 for _, load := range symbolLoads {
263 toLoad[load] = true
264 }
265 }
266 var expr build.Expr = f
267 walk(&expr, bzlenv.NewEnvironment())
268
269 if len(toLoad) == 0 {
270 return nil
271 }
272
273 loads := []string{}
274 for l := range toLoad {
275 loads = append(loads, l)
276 }
277
278 sort.Strings(loads)
279 replacement := insertLoad(f, loadFrom, loads)
280 if replacement != nil {
281
282 for _, f := range findings {
283 f.Replacement = append(f.Replacement, *replacement)
284 }
285 }
286
287 return findings
288 }
289
290
291
292 func NotLoadedFunctionUsageCheck(f *build.File, globals []string, loadFrom string) []*LinterFinding {
293 return notLoadedUsageCheck(f, globals, []string{}, loadFrom)
294 }
295
296
297 func makePositional(argument build.Expr) build.Expr {
298 if binary, ok := argument.(*build.AssignExpr); ok {
299 return binary.RHS
300 }
301 return argument
302 }
303
304
305 func makeKeyword(argument build.Expr, name string) build.Expr {
306 assign, ok := argument.(*build.AssignExpr)
307 if !ok {
308 return &build.AssignExpr{
309 LHS: &build.Ident{Name: name},
310 Op: "=",
311 RHS: argument,
312 }
313 }
314 ident, ok := assign.LHS.(*build.Ident)
315 if ok && ident.Name == name {
316
317 return argument
318 }
319
320
321 newAssign := *assign
322 newAssign.LHS = &build.Ident{Name: name}
323 return &newAssign
324 }
325
326 func attrConfigurationWarning(f *build.File) []*LinterFinding {
327 if f.Type != build.TypeBzl {
328 return nil
329 }
330
331 var findings []*LinterFinding
332 build.WalkPointers(f, func(expr *build.Expr, stack []build.Expr) {
333
334 call, ok := (*expr).(*build.CallExpr)
335 if !ok {
336 return
337 }
338 dot, ok := (call.X).(*build.DotExpr)
339 if !ok {
340 return
341 }
342 base, ok := dot.X.(*build.Ident)
343 if !ok || base.Name != "attr" {
344 return
345 }
346 i, _, param := getParam(call.List, "cfg")
347 if param == nil {
348 return
349 }
350 value, ok := (param.RHS).(*build.StringExpr)
351 if !ok {
352 return
353 }
354
355 newCall := *call
356 switch value.Value {
357 case "data":
358 newCall.List = append(newCall.List[:i], newCall.List[i+1:]...)
359 findings = append(findings,
360 makeLinterFinding(param, `cfg = "data" for attr definitions has no effect and should be removed.`,
361 LinterReplacement{expr, &newCall}))
362
363 case "host":
364 {
365 newCall.List = append([]build.Expr{}, newCall.List...)
366 newParam := newCall.List[i].Copy().(*build.AssignExpr)
367 newRHS := newParam.RHS.Copy().(*build.StringExpr)
368 newRHS.Value = "exec"
369 newParam.RHS = newRHS
370 newCall.List[i] = newParam
371 findings = append(findings,
372 makeLinterFinding(param, `cfg = "host" for attr definitions should be replaced by cfg = "exec".`,
373 LinterReplacement{expr, &newCall}))
374 }
375
376 default:
377
378 return
379 }
380 })
381 return findings
382 }
383
384 func depsetItemsWarning(f *build.File) []*LinterFinding {
385 var findings []*LinterFinding
386
387 types := DetectTypes(f)
388 build.WalkPointers(f, func(expr *build.Expr, stack []build.Expr) {
389 call, ok := (*expr).(*build.CallExpr)
390 if !ok {
391 return
392 }
393 base, ok := call.X.(*build.Ident)
394 if !ok || base.Name != "depset" {
395 return
396 }
397 if len(call.List) == 0 {
398 return
399 }
400 _, _, param := getParam(call.List, "items")
401 if param != nil {
402 findings = append(findings,
403 makeLinterFinding(param, `Parameter "items" is deprecated, use "direct" and/or "transitive" instead.`))
404 return
405 }
406 if _, ok := call.List[0].(*build.AssignExpr); ok {
407 return
408 }
409
410 if types[call.List[0]] == Depset {
411 findings = append(findings,
412 makeLinterFinding(call.List[0], `Giving a depset as first unnamed parameter to depset() is deprecated, use the "transitive" parameter instead.`))
413 }
414 })
415 return findings
416 }
417
418 func attrNonEmptyWarning(f *build.File) []*LinterFinding {
419 if f.Type != build.TypeBzl {
420 return nil
421 }
422
423 var findings []*LinterFinding
424 build.WalkPointers(f, func(expr *build.Expr, stack []build.Expr) {
425
426 call, ok := (*expr).(*build.CallExpr)
427 if !ok {
428 return
429 }
430 dot, ok := (call.X).(*build.DotExpr)
431 if !ok {
432 return
433 }
434 base, ok := dot.X.(*build.Ident)
435 if !ok || base.Name != "attr" {
436 return
437 }
438 _, name, param := getParam(call.List, "non_empty")
439 if param == nil {
440 return
441 }
442
443
444 newName := *name
445 newName.Name = "allow_empty"
446 negatedRHS := negateExpression(param.RHS)
447
448 findings = append(findings,
449 makeLinterFinding(param, "non_empty attributes for attr definitions are deprecated in favor of allow_empty.",
450 LinterReplacement{¶m.LHS, &newName},
451 LinterReplacement{¶m.RHS, negatedRHS},
452 ))
453 })
454 return findings
455 }
456
457 func attrSingleFileWarning(f *build.File) []*LinterFinding {
458 if f.Type != build.TypeBzl {
459 return nil
460 }
461
462 var findings []*LinterFinding
463 build.WalkPointers(f, func(expr *build.Expr, stack []build.Expr) {
464
465 call, ok := (*expr).(*build.CallExpr)
466 if !ok {
467 return
468 }
469 dot, ok := (call.X).(*build.DotExpr)
470 if !ok {
471 return
472 }
473 base, ok := dot.X.(*build.Ident)
474 if !ok || base.Name != "attr" {
475 return
476 }
477 singleFileIndex, singleFileKw, singleFileParam := getParam(call.List, "single_file")
478 if singleFileParam == nil {
479 return
480 }
481
482
483 newCall := *call
484 newCall.List = append([]build.Expr{}, call.List...)
485
486 newSingleFileKw := *singleFileKw
487 newSingleFileKw.Name = "allow_single_file"
488 singleFileValue := singleFileParam.RHS
489
490 if boolean, ok := singleFileValue.(*build.Ident); ok && boolean.Name == "False" {
491
492 newCall.List = append(newCall.List[:singleFileIndex], newCall.List[singleFileIndex+1:]...)
493 } else {
494
495 allowFileIndex, _, allowFilesParam := getParam(call.List, "allow_files")
496 if allowFilesParam != nil {
497 singleFileValue = allowFilesParam.RHS
498 newCall.List = append(newCall.List[:allowFileIndex], newCall.List[allowFileIndex+1:]...)
499 if singleFileIndex > allowFileIndex {
500 singleFileIndex--
501 }
502 }
503 }
504 findings = append(findings,
505 makeLinterFinding(singleFileParam, "single_file is deprecated in favor of allow_single_file.",
506 LinterReplacement{expr, &newCall},
507 LinterReplacement{&singleFileParam.LHS, &newSingleFileKw},
508 LinterReplacement{&singleFileParam.RHS, singleFileValue},
509 ))
510 })
511 return findings
512 }
513
514 func ctxActionsWarning(f *build.File) []*LinterFinding {
515 if f.Type != build.TypeBzl {
516 return nil
517 }
518
519 var findings []*LinterFinding
520 build.WalkPointers(f, func(expr *build.Expr, stack []build.Expr) {
521
522 call, ok := (*expr).(*build.CallExpr)
523 if !ok {
524 return
525 }
526 dot, ok := (call.X).(*build.DotExpr)
527 if !ok {
528 return
529 }
530 base, ok := dot.X.(*build.Ident)
531 if !ok || base.Name != "ctx" {
532 return
533 }
534
535 switch dot.Name {
536 case "new_file", "experimental_new_directory", "file_action", "action", "empty_action", "template_action":
537
538 default:
539 return
540 }
541
542
543 newCall := *call
544 newCall.List = append([]build.Expr{}, call.List...)
545 newDot := *dot
546 newCall.X = &newDot
547
548 switch dot.Name {
549 case "new_file":
550 if len(call.List) > 2 {
551
552 findings = append(findings,
553 makeLinterFinding(dot, fmt.Sprintf(`"ctx.new_file" is deprecated in favor of "ctx.actions.declare_file".`)))
554 return
555 }
556 newDot.Name = "actions.declare_file"
557 if len(call.List) == 2 {
558
559
560 newCall.List[0], newCall.List[1] = makePositional(call.List[1]), makeKeyword(call.List[0], "sibling")
561 }
562 case "experimental_new_directory":
563 newDot.Name = "actions.declare_directory"
564 case "file_action":
565 newDot.Name = "actions.write"
566 i, ident, param := getParam(newCall.List, "executable")
567 if ident != nil {
568 newIdent := *ident
569 newIdent.Name = "is_executable"
570 newParam := *param
571 newParam.LHS = &newIdent
572 newCall.List[i] = &newParam
573 }
574 case "action":
575 newDot.Name = "actions.run"
576 if _, _, command := getParam(call.List, "command"); command != nil {
577 newDot.Name = "actions.run_shell"
578 }
579 case "empty_action":
580 newDot.Name = "actions.do_nothing"
581 case "template_action":
582 newDot.Name = "actions.expand_template"
583 if i, ident, param := getParam(call.List, "executable"); ident != nil {
584 newIdent := *ident
585 newIdent.Name = "is_executable"
586 newParam := *param
587 newParam.LHS = &newIdent
588 newCall.List[i] = &newParam
589 }
590 }
591
592 findings = append(findings, makeLinterFinding(dot,
593 fmt.Sprintf(`"ctx.%s" is deprecated in favor of "ctx.%s".`, dot.Name, newDot.Name),
594 LinterReplacement{expr, &newCall}))
595 })
596 return findings
597 }
598
599 func fileTypeWarning(f *build.File) []*LinterFinding {
600 if f.Type != build.TypeBzl {
601 return nil
602 }
603
604 var findings []*LinterFinding
605 var walk func(e *build.Expr, env *bzlenv.Environment)
606 walk = func(e *build.Expr, env *bzlenv.Environment) {
607 defer bzlenv.WalkOnceWithEnvironment(*e, env, walk)
608
609 call, ok := isFunctionCall(*e, "FileType")
610 if !ok {
611 return
612 }
613 if binding := env.Get("FileType"); binding == nil {
614 findings = append(findings,
615 makeLinterFinding(call, "The FileType function is deprecated."))
616 }
617 }
618 var expr build.Expr = f
619 walk(&expr, bzlenv.NewEnvironment())
620
621 return findings
622 }
623
624 func packageNameWarning(f *build.File) []*LinterFinding {
625 return globalVariableUsageCheck(f, "PACKAGE_NAME", "native.package_name()")
626 }
627
628 func repositoryNameWarning(f *build.File) []*LinterFinding {
629 return globalVariableUsageCheck(f, "REPOSITORY_NAME", "native.repository_name()")
630 }
631
632 func outputGroupWarning(f *build.File) []*LinterFinding {
633 if f.Type != build.TypeBzl {
634 return nil
635 }
636
637 var findings []*LinterFinding
638 build.WalkPointers(f, func(expr *build.Expr, stack []build.Expr) {
639
640 outputGroup, ok := (*expr).(*build.DotExpr)
641 if !ok || outputGroup.Name != "output_group" {
642 return
643 }
644 dep, ok := (outputGroup.X).(*build.DotExpr)
645 if !ok {
646 return
647 }
648 attr, ok := (dep.X).(*build.DotExpr)
649 if !ok || attr.Name != "attr" {
650 return
651 }
652 ctx, ok := (attr.X).(*build.Ident)
653 if !ok || ctx.Name != "ctx" {
654 return
655 }
656
657
658 findings = append(findings,
659 makeLinterFinding(outputGroup,
660 `"ctx.attr.dep.output_group" is deprecated in favor of "ctx.attr.dep[OutputGroupInfo]".`,
661 LinterReplacement{expr, &build.IndexExpr{
662 X: dep,
663 Y: &build.Ident{Name: "OutputGroupInfo"},
664 },
665 }))
666 })
667 return findings
668 }
669
670 func nativeGitRepositoryWarning(f *build.File) []*LinterFinding {
671 if f.Type != build.TypeBzl {
672 return nil
673 }
674 return NotLoadedFunctionUsageCheck(f, []string{"git_repository", "new_git_repository"}, "@bazel_tools//tools/build_defs/repo:git.bzl")
675 }
676
677 func nativeHTTPArchiveWarning(f *build.File) []*LinterFinding {
678 if f.Type != build.TypeBzl {
679 return nil
680 }
681 return NotLoadedFunctionUsageCheck(f, []string{"http_archive"}, "@bazel_tools//tools/build_defs/repo:http.bzl")
682 }
683
684 func nativeAndroidRulesWarning(f *build.File) []*LinterFinding {
685 if f.Type != build.TypeBzl && f.Type != build.TypeBuild {
686 return nil
687 }
688 return NotLoadedFunctionUsageCheck(f, tables.AndroidNativeRules, tables.AndroidLoadPath)
689 }
690
691 func nativeCcRulesWarning(f *build.File) []*LinterFinding {
692 if f.Type != build.TypeBzl && f.Type != build.TypeBuild {
693 return nil
694 }
695 return NotLoadedFunctionUsageCheck(f, tables.CcNativeRules, tables.CcLoadPath)
696 }
697
698 func nativeJavaRulesWarning(f *build.File) []*LinterFinding {
699 if f.Type != build.TypeBzl && f.Type != build.TypeBuild {
700 return nil
701 }
702 return NotLoadedFunctionUsageCheck(f, tables.JavaNativeRules, tables.JavaLoadPath)
703 }
704
705 func nativePyRulesWarning(f *build.File) []*LinterFinding {
706 if f.Type != build.TypeBzl && f.Type != build.TypeBuild {
707 return nil
708 }
709 return NotLoadedFunctionUsageCheck(f, tables.PyNativeRules, tables.PyLoadPath)
710 }
711
712 func nativeProtoRulesWarning(f *build.File) []*LinterFinding {
713 if f.Type != build.TypeBzl && f.Type != build.TypeBuild {
714 return nil
715 }
716 return notLoadedUsageCheck(f, tables.ProtoNativeRules, tables.ProtoNativeSymbols, tables.ProtoLoadPath)
717 }
718
719 func contextArgsAPIWarning(f *build.File) []*LinterFinding {
720 if f.Type != build.TypeBzl {
721 return nil
722 }
723
724 var findings []*LinterFinding
725 types := DetectTypes(f)
726
727 build.WalkPointers(f, func(expr *build.Expr, stack []build.Expr) {
728
729 call, ok := (*expr).(*build.CallExpr)
730 if !ok {
731 return
732 }
733 dot, ok := call.X.(*build.DotExpr)
734 if !ok || dot.Name != "add" || types[dot.X] != CtxActionsArgs {
735 return
736 }
737
738
739
740
741
742 _, beforeEachKw, beforeEach := getParam(call.List, "before_each")
743 _, _, joinWith := getParam(call.List, "join_with")
744 _, mapFnKw, mapFn := getParam(call.List, "map_fn")
745 if beforeEach == nil && joinWith == nil && mapFn == nil {
746
747 return
748 }
749
750
751 var replacements []LinterReplacement
752
753 newDot := *dot
754 newDot.Name = "add_all"
755 replacements = append(replacements, LinterReplacement{&call.X, &newDot})
756
757 if joinWith != nil {
758 newDot.Name = "add_joined"
759 if beforeEach != nil {
760
761
762 newBeforeEachKw := *beforeEachKw
763 newBeforeEachKw.Name = "format_each"
764
765 replacements = append(replacements, LinterReplacement{&beforeEach.LHS, &newBeforeEachKw})
766 replacements = append(replacements, LinterReplacement{&beforeEach.RHS, &build.BinaryExpr{
767 X: beforeEach.RHS,
768 Op: "+",
769 Y: &build.StringExpr{Value: "%s"},
770 }})
771 }
772 }
773 if mapFnKw != nil {
774
775 newMapFnKw := *mapFnKw
776 newMapFnKw.Name = "map_each"
777 replacements = append(replacements, LinterReplacement{&mapFn.LHS, &newMapFnKw})
778 }
779
780 findings = append(findings,
781 makeLinterFinding(call,
782 `"ctx.actions.args().add()" for multiple arguments is deprecated in favor of "add_all()" or "add_joined()".`,
783 replacements...))
784
785 })
786 return findings
787 }
788
789 func attrOutputDefaultWarning(f *build.File) []*LinterFinding {
790 if f.Type != build.TypeBzl {
791 return nil
792 }
793
794 var findings []*LinterFinding
795 build.Walk(f, func(expr build.Expr, stack []build.Expr) {
796
797 call, ok := expr.(*build.CallExpr)
798 if !ok {
799 return
800 }
801 dot, ok := (call.X).(*build.DotExpr)
802 if !ok || dot.Name != "output" {
803 return
804 }
805 base, ok := dot.X.(*build.Ident)
806 if !ok || base.Name != "attr" {
807 return
808 }
809 _, _, param := getParam(call.List, "default")
810 if param == nil {
811 return
812 }
813 findings = append(findings,
814 makeLinterFinding(param, `The "default" parameter for attr.output() is deprecated.`))
815 })
816 return findings
817 }
818
819 func attrLicenseWarning(f *build.File) []*LinterFinding {
820 if f.Type != build.TypeBzl {
821 return nil
822 }
823
824 var findings []*LinterFinding
825 build.Walk(f, func(expr build.Expr, stack []build.Expr) {
826
827 call, ok := expr.(*build.CallExpr)
828 if !ok {
829 return
830 }
831 dot, ok := (call.X).(*build.DotExpr)
832 if !ok || dot.Name != "license" {
833 return
834 }
835 base, ok := dot.X.(*build.Ident)
836 if !ok || base.Name != "attr" {
837 return
838 }
839 findings = append(findings,
840 makeLinterFinding(expr, `"attr.license()" is deprecated and shouldn't be used.`))
841 })
842 return findings
843 }
844
845
846 func ruleImplReturnWarning(f *build.File) []*LinterFinding {
847 if f.Type != build.TypeBzl {
848 return nil
849 }
850
851 var findings []*LinterFinding
852
853
854 implNames := make(map[string]bool)
855 build.Walk(f, func(expr build.Expr, stack []build.Expr) {
856 call, ok := isFunctionCall(expr, "rule")
857 if !ok {
858 return
859 }
860
861
862 var impl build.Expr
863 _, _, param := getParam(call.List, "implementation")
864 if param != nil {
865 impl = param.RHS
866 } else if len(call.List) > 0 {
867 impl = call.List[0]
868 }
869 if name, ok := impl.(*build.Ident); ok {
870 implNames[name.Name] = true
871 }
872 })
873
874
875 for _, stmt := range f.Stmt {
876 def, ok := stmt.(*build.DefStmt)
877 if !ok || !implNames[def.Name] {
878
879 continue
880 }
881
882 build.Walk(def, func(expr build.Expr, stack []build.Expr) {
883 ret, ok := expr.(*build.ReturnStmt)
884 if !ok {
885 return
886 }
887
888 if _, ok := isFunctionCall(ret.Result, "struct"); ok {
889 findings = append(findings, makeLinterFinding(ret, `Avoid using the legacy provider syntax.`))
890 }
891 })
892 }
893
894 return findings
895 }
896
897 type signature struct {
898 Positional []string
899 Keyword []string
900 }
901
902 var signatures = map[string]signature{
903 "all": {[]string{"elements"}, []string{}},
904 "any": {[]string{"elements"}, []string{}},
905 "tuple": {[]string{"x"}, []string{}},
906 "list": {[]string{"x"}, []string{}},
907 "len": {[]string{"x"}, []string{}},
908 "str": {[]string{"x"}, []string{}},
909 "repr": {[]string{"x"}, []string{}},
910 "bool": {[]string{"x"}, []string{}},
911 "int": {[]string{"x"}, []string{}},
912 "dir": {[]string{"x"}, []string{}},
913 "type": {[]string{"x"}, []string{}},
914 "hasattr": {[]string{"x", "name"}, []string{}},
915 "getattr": {[]string{"x", "name", "default"}, []string{}},
916 "select": {[]string{"x"}, []string{}},
917 }
918
919
920
921 func functionName(call *build.CallExpr) (string, bool) {
922 if ident, ok := call.X.(*build.Ident); ok {
923 return ident.Name, true
924 }
925
926 dot, ok := call.X.(*build.DotExpr)
927 if !ok {
928 return "", false
929 }
930 if ident, ok := dot.X.(*build.Ident); !ok || ident.Name != "native" {
931 return "", false
932 }
933 return dot.Name, true
934 }
935
936 const (
937 typePositional int = iota
938 typeKeyword
939 typeArgs
940 typeKwargs
941 )
942
943
944 func paramType(param build.Expr) (int, string) {
945 switch param := param.(type) {
946 case *build.AssignExpr:
947 if param.Op == "=" {
948 ident, ok := param.LHS.(*build.Ident)
949 if ok {
950 return typeKeyword, ident.Name
951 }
952 return typeKeyword, ""
953 }
954 case *build.UnaryExpr:
955 switch param.Op {
956 case "*":
957 return typeArgs, ""
958 case "**":
959 return typeKwargs, ""
960 }
961 }
962 return typePositional, ""
963 }
964
965
966 func keywordPositionalParametersWarning(f *build.File) []*LinterFinding {
967 var findings []*LinterFinding
968
969
970 build.WalkPointers(f, func(expr *build.Expr, stack []build.Expr) {
971 call, ok := (*expr).(*build.CallExpr)
972 if !ok || len(call.List) == 0 {
973 return
974 }
975 function, ok := functionName(call)
976 if !ok {
977 return
978 }
979
980
981 var callFindings []*LinterFinding
982 var callReplacements []LinterReplacement
983
984 signature, ok := signatures[function]
985 if !ok {
986 return
987 }
988
989 var paramTypes []int
990 for i, parameter := range call.List {
991 pType, name := paramType(parameter)
992 paramTypes = append(paramTypes, pType)
993
994 if pType == typeKeyword && i < len(signature.Positional) && signature.Positional[i] == name {
995
996 callFindings = append(callFindings, makeLinterFinding(
997 parameter,
998 fmt.Sprintf(`Keyword parameter %q for %q should be positional.`, signature.Positional[i], function),
999 ))
1000 callReplacements = append(callReplacements, LinterReplacement{&call.List[i], makePositional(parameter)})
1001 paramTypes[i] = typePositional
1002 }
1003
1004 if pType == typePositional && i >= len(signature.Positional) && i < len(signature.Positional)+len(signature.Keyword) {
1005
1006 keyword := signature.Keyword[i-len(signature.Positional)]
1007 callFindings = append(callFindings, makeLinterFinding(
1008 parameter,
1009 fmt.Sprintf(`Parameter at the position %d for %q should be keyword (%s = ...).`, i+1, function, keyword),
1010 ))
1011 callReplacements = append(callReplacements, LinterReplacement{&call.List[i], makeKeyword(parameter, keyword)})
1012 paramTypes[i] = typeKeyword
1013 }
1014 }
1015
1016 if len(callFindings) == 0 {
1017 return
1018 }
1019
1020
1021
1022
1023
1024
1025 if sort.IntsAreSorted(paramTypes) {
1026
1027
1028
1029 for _, t := range paramTypes {
1030 if t == typeKeyword {
1031
1032 newCall := *call
1033 newCall.ForceCompact = false
1034 callFindings[0].Replacement = append(callFindings[0].Replacement, LinterReplacement{expr, &newCall})
1035 break
1036 }
1037 }
1038
1039 callFindings[0].Replacement = append(callFindings[0].Replacement, callReplacements...)
1040 }
1041
1042 findings = append(findings, callFindings...)
1043 })
1044
1045 return findings
1046 }
1047
1048 func providerParamsWarning(f *build.File) []*LinterFinding {
1049 if f.Type != build.TypeBzl {
1050 return nil
1051 }
1052
1053 var findings []*LinterFinding
1054 build.Walk(f, func(expr build.Expr, stack []build.Expr) {
1055 call, ok := isFunctionCall(expr, "provider")
1056 if !ok {
1057 return
1058 }
1059
1060 _, _, fields := getParam(call.List, "fields")
1061 _, _, doc := getParam(call.List, "doc")
1062
1063 hasPositional := false
1064 if len(call.List) > 0 {
1065 if _, ok := call.List[0].(*build.AssignExpr); !ok {
1066 hasPositional = true
1067 }
1068 }
1069 msg := ""
1070 if fields == nil {
1071 msg = "a list of fields"
1072 }
1073 if doc == nil && !hasPositional {
1074 if msg != "" {
1075 msg += " and "
1076 }
1077 msg += "a documentation"
1078 }
1079 if msg != "" {
1080 findings = append(findings, makeLinterFinding(call,
1081 `Calls to 'provider' should provide `+msg+`:\n`+
1082 ` provider("description", fields = [...])`))
1083 }
1084 })
1085 return findings
1086 }
1087
1088
1089 func attrNameWarning(f *build.File, names []string) []*LinterFinding {
1090 if f.Type != build.TypeBzl {
1091 return nil
1092 }
1093
1094 var findings []*LinterFinding
1095 build.WalkPointers(f, func(expr *build.Expr, stack []build.Expr) {
1096
1097 dict, ok := (*expr).(*build.DictExpr)
1098 if !ok {
1099 return
1100 }
1101 for _, item := range dict.List {
1102
1103 value, ok := item.Key.(*build.StringExpr)
1104 if !ok {
1105 continue
1106 }
1107 for _, name := range names {
1108 if value.Value == name {
1109 findings = append(findings, makeLinterFinding(dict,
1110 fmt.Sprintf(`Do not use '%s' as an attribute name.`+
1111 ` It may cause unexpected behavior.`, value.Value)))
1112
1113 }
1114 }
1115 }
1116 })
1117 return findings
1118 }
1119
1120 func attrLicensesWarning(f *build.File) []*LinterFinding {
1121 return attrNameWarning(f, []string{"licenses"})
1122 }
1123
1124 func attrApplicableLicensesWarning(f *build.File) []*LinterFinding {
1125 return attrNameWarning(f, []string{"applicable_licenses", "package_metadata"})
1126 }
1127
View as plain text