| /* |
| Copyright 2020 Google LLC |
| |
| Licensed under the Apache License, Version 2.0 (the "License"); |
| you may not use this file except in compliance with the License. |
| You may obtain a copy of the License at |
| |
| https://www.apache.org/licenses/LICENSE-2.0 |
| |
| Unless required by applicable law or agreed to in writing, software |
| distributed under the License is distributed on an "AS IS" BASIS, |
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| See the License for the specific language governing permissions and |
| limitations under the License. |
| */ |
| |
| // Warnings about deprecated operations in Starlark |
| |
| package warn |
| |
| import ( |
| "github.com/bazelbuild/buildtools/build" |
| ) |
| |
| func dictionaryConcatenationWarning(f *build.File) []*LinterFinding { |
| var findings []*LinterFinding |
| |
| var addWarning = func(expr build.Expr) { |
| findings = append(findings, |
| makeLinterFinding(expr, "Dictionary concatenation is deprecated.")) |
| } |
| |
| types := DetectTypes(f) |
| build.Walk(f, func(expr build.Expr, stack []build.Expr) { |
| switch expr := expr.(type) { |
| case *build.BinaryExpr: |
| if expr.Op != "+" { |
| return |
| } |
| if types[expr.X] == Dict || types[expr.Y] == Dict { |
| addWarning(expr) |
| } |
| case *build.AssignExpr: |
| if expr.Op != "+=" { |
| return |
| } |
| if types[expr.LHS] == Dict || types[expr.RHS] == Dict { |
| addWarning(expr) |
| } |
| } |
| }) |
| return findings |
| } |
| |
| // Detect the patterns: |
| // |
| // dict.pop(..., default = ...) |
| // dict.pop(..., default = ...) |
| // dict.setdefault(..., default = ...) |
| // |
| // And recommend removing the `default =` part. These functions take positional argument instead. |
| func dictMethodNamedArgWarning(f *build.File) []*LinterFinding { |
| var findings []*LinterFinding |
| |
| types := DetectTypes(f) |
| build.Walk(f, func(expr build.Expr, stack []build.Expr) { |
| |
| var callexpr *build.CallExpr |
| var ok bool |
| if callexpr, ok = expr.(*build.CallExpr); !ok { |
| return |
| } |
| var dotexpr *build.DotExpr |
| if dotexpr, ok = callexpr.X.(*build.DotExpr); !ok || types[dotexpr.X] != Dict { |
| return |
| } |
| |
| if dotexpr.Name != "pop" && dotexpr.Name != "get" && dotexpr.Name != "setdefault" { |
| return |
| } |
| |
| for _, expr := range callexpr.List { |
| assignExpr, ok := expr.(*build.AssignExpr) |
| if !ok { |
| continue |
| } |
| if left, ok := assignExpr.LHS.(*build.Ident); !ok || left.Name != "default" { |
| continue |
| } |
| findings = append(findings, |
| makeLinterFinding(expr, "Named argument \"default\" not allowed, use a positional (unnamed) argument")) |
| } |
| }) |
| return findings |
| } |
| |
| func stringIterationWarning(f *build.File) []*LinterFinding { |
| var findings []*LinterFinding |
| |
| addWarning := func(expr build.Expr) { |
| findings = append(findings, |
| makeLinterFinding(expr, "String iteration is deprecated.")) |
| } |
| |
| types := DetectTypes(f) |
| build.Walk(f, func(expr build.Expr, stack []build.Expr) { |
| switch expr := expr.(type) { |
| case *build.ForStmt: |
| if types[expr.X] == String { |
| addWarning(expr.X) |
| } |
| case *build.ForClause: |
| if types[expr.X] == String { |
| addWarning(expr.X) |
| } |
| case *build.CallExpr: |
| ident, ok := expr.X.(*build.Ident) |
| if !ok { |
| return |
| } |
| switch ident.Name { |
| case "all", "any", "reversed", "max", "min": |
| if len(expr.List) != 1 { |
| return |
| } |
| if types[expr.List[0]] == String { |
| addWarning(expr.List[0]) |
| } |
| case "zip": |
| for _, arg := range expr.List { |
| if types[arg] == String { |
| addWarning(arg) |
| } |
| } |
| } |
| } |
| }) |
| return findings |
| } |
| |
| func integerDivisionWarning(f *build.File) []*LinterFinding { |
| var findings []*LinterFinding |
| |
| types := DetectTypes(f) |
| build.WalkPointers(f, func(e *build.Expr, stack []build.Expr) { |
| switch expr := (*e).(type) { |
| case *build.BinaryExpr: |
| if expr.Op != "/" { |
| return |
| } |
| if types[expr.X] != Int || types[expr.Y] != Int { |
| return |
| } |
| newBinary := *expr |
| newBinary.Op = "//" |
| findings = append(findings, |
| makeLinterFinding(expr, `The "/" operator for integer division is deprecated in favor of "//".`, |
| LinterReplacement{e, &newBinary})) |
| |
| case *build.AssignExpr: |
| if expr.Op != "/=" { |
| return |
| } |
| if types[expr.LHS] != Int || types[expr.RHS] != Int { |
| return |
| } |
| newAssign := *expr |
| newAssign.Op = "//=" |
| findings = append(findings, |
| makeLinterFinding(expr, `The "/=" operator for integer division is deprecated in favor of "//=".`, |
| LinterReplacement{e, &newAssign})) |
| } |
| }) |
| return findings |
| } |
| |
| func listAppendWarning(f *build.File) []*LinterFinding { |
| var findings []*LinterFinding |
| |
| build.WalkPointers(f, func(expr *build.Expr, stack []build.Expr) { |
| as, ok := (*expr).(*build.AssignExpr) |
| if !ok || as.Op != "+=" { |
| return |
| } |
| |
| list, ok := as.RHS.(*build.ListExpr) |
| if !ok || len(list.List) != 1 { |
| return |
| } |
| |
| _, end := as.Span() |
| findings = append(findings, makeLinterFinding(as, `Prefer using ".append()" to adding a single element list. NOTE: This does not work if the target is a select, hence this warning can safely be ignored or suppressed.`, |
| LinterReplacement{expr, &build.CallExpr{ |
| Comments: as.Comments, |
| X: &build.DotExpr{ |
| X: as.LHS, |
| Name: "append", |
| }, |
| List: list.List, |
| End: build.End{Pos: end}, |
| }})) |
| |
| }) |
| return findings |
| } |