1// Warnings about deprecated Bazel-related operations
2
3package warn
4
5import (
6	"fmt"
7
8	"github.com/bazelbuild/buildtools/build"
9)
10
11func depsetUnionWarning(f *build.File) []*LinterFinding {
12	var findings []*LinterFinding
13	addWarning := func(expr build.Expr) {
14		findings = append(findings,
15			makeLinterFinding(expr, `Depsets should be joined using the "depset()" constructor.`))
16	}
17
18	types := detectTypes(f)
19	build.Walk(f, func(expr build.Expr, stack []build.Expr) {
20		switch expr := expr.(type) {
21		case *build.BinaryExpr:
22			// `depset1 + depset2` or `depset1 | depset2`
23			if types[expr.X] != Depset && types[expr.Y] != Depset {
24				return
25			}
26			switch expr.Op {
27			case "+", "|":
28				addWarning(expr)
29			}
30		case *build.AssignExpr:
31			// `depset1 += depset2` or `depset1 |= depset2`
32			if types[expr.LHS] != Depset && types[expr.RHS] != Depset {
33				return
34			}
35			switch expr.Op {
36			case "+=", "|=":
37				addWarning(expr)
38			}
39		case *build.CallExpr:
40			// `depset1.union(depset2)`
41			if len(expr.List) == 0 {
42				return
43			}
44			dot, ok := expr.X.(*build.DotExpr)
45			if !ok {
46				return
47			}
48			if dot.Name != "union" {
49				return
50			}
51			if types[dot.X] != Depset && types[expr.List[0]] != Depset {
52				return
53			}
54			addWarning(expr)
55		}
56	})
57	return findings
58}
59
60func depsetIterationWarning(f *build.File) []*LinterFinding {
61	var findings []*LinterFinding
62
63	addFinding := func(expr *build.Expr) {
64		_, end := (*expr).Span()
65		newNode := &build.CallExpr{
66			X: &build.DotExpr{
67				X:    *expr,
68				Name: "to_list",
69			},
70			End: build.End{Pos: end},
71		}
72		findings = append(findings,
73			makeLinterFinding(*expr, `Depset iteration is deprecated, use the "to_list()" method instead.`, LinterReplacement{expr, newNode}))
74	}
75
76	types := detectTypes(f)
77	build.WalkPointers(f, func(e *build.Expr, stack []build.Expr) {
78		switch expr := (*e).(type) {
79		case *build.ForStmt:
80			if types[expr.X] != Depset {
81				return
82			}
83			addFinding(&expr.X)
84		case *build.ForClause:
85			if types[expr.X] != Depset {
86				return
87			}
88			addFinding(&expr.X)
89		case *build.BinaryExpr:
90			if expr.Op != "in" && expr.Op != "not in" {
91				return
92			}
93			if types[expr.Y] != Depset {
94				return
95			}
96			addFinding(&expr.Y)
97		case *build.CallExpr:
98			ident, ok := expr.X.(*build.Ident)
99			if !ok {
100				return
101			}
102			switch ident.Name {
103			case "all", "any", "depset", "len", "sorted", "max", "min", "list", "tuple":
104				if len(expr.List) != 1 {
105					return
106				}
107				if types[expr.List[0]] != Depset {
108					return
109				}
110				addFinding(&expr.List[0])
111				if ident.Name == "list" {
112					// `list(d.to_list())` can be simplified to just `d.to_list()`
113					findings[len(findings)-1].Replacement[0].Old = e
114				}
115			case "zip":
116				for i, arg := range expr.List {
117					if types[arg] != Depset {
118						continue
119					}
120					addFinding(&expr.List[i])
121				}
122			}
123		}
124		return
125	})
126	return findings
127}
128
129func overlyNestedDepsetWarning(f *build.File) []*LinterFinding {
130	var findings []*LinterFinding
131	build.WalkStatements(f, func(expr build.Expr, stack []build.Expr) {
132		// Are we inside a for-loop?
133		isForLoop := false
134		for _, e := range stack {
135			if _, ok := e.(*build.ForStmt); ok {
136				isForLoop = true
137				break
138			}
139		}
140		if !isForLoop {
141			return
142		}
143
144		// Search for assignment statements
145		assign, ok := expr.(*build.AssignExpr)
146		if !ok {
147			return
148		}
149		// Is the LHS an ident?
150		lhs, ok := assign.LHS.(*build.Ident)
151		if !ok {
152			return
153		}
154		// Is the RHS a depset constructor?
155		call, ok := assign.RHS.(*build.CallExpr)
156		if !ok {
157			return
158		}
159		if ident, ok := call.X.(*build.Ident); !ok || ident.Name != "depset" {
160			return
161		}
162		_, _, param := getParam(call.List, "transitive")
163		if param == nil {
164			return
165		}
166		transitives, ok := param.RHS.(*build.ListExpr)
167		if !ok {
168			return
169		}
170		for _, transitive := range transitives.List {
171			if ident, ok := transitive.(*build.Ident); ok && ident.Name == lhs.Name {
172				findings = append(findings, makeLinterFinding(assign, fmt.Sprintf("Depset %q is potentially overly nested.", lhs.Name)))
173				return
174			}
175		}
176	})
177	return findings
178}
179