2024-03-27 19:24:38 +01:00
|
|
|
package analyzer
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
|
|
|
"errors"
|
|
|
|
"go/ast"
|
|
|
|
"go/printer"
|
|
|
|
"go/token"
|
|
|
|
"golang.org/x/tools/go/analysis"
|
|
|
|
"golang.org/x/tools/go/analysis/passes/inspect"
|
|
|
|
"golang.org/x/tools/go/ast/inspector"
|
|
|
|
)
|
|
|
|
|
|
|
|
var Analyzer = &analysis.Analyzer{
|
|
|
|
Name: "foreshadow",
|
2024-03-27 19:25:49 +01:00
|
|
|
Doc: "enforce context shadowing inside loops",
|
2024-03-27 19:24:38 +01:00
|
|
|
Run: run,
|
|
|
|
Requires: []*analysis.Analyzer{inspect.Analyzer},
|
|
|
|
}
|
|
|
|
|
|
|
|
var errUnknown = errors.New("unknown node type")
|
|
|
|
|
|
|
|
func run(pass *analysis.Pass) (interface{}, error) {
|
|
|
|
inspctr := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
|
|
|
|
|
|
|
|
nodeFilter := []ast.Node{
|
|
|
|
(*ast.ForStmt)(nil),
|
|
|
|
(*ast.RangeStmt)(nil),
|
|
|
|
}
|
|
|
|
|
|
|
|
inspctr.Preorder(nodeFilter, func(node ast.Node) {
|
|
|
|
body, err := getBody(node)
|
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, stmt := range body.List {
|
|
|
|
assignStmt, ok := stmt.(*ast.AssignStmt)
|
|
|
|
if !ok {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
t := pass.TypesInfo.TypeOf(assignStmt.Lhs[0])
|
|
|
|
if t == nil {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
if t.String() != "context.Context" {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
if assignStmt.Tok == token.DEFINE {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
|
2024-03-27 19:57:23 +01:00
|
|
|
suggestedStmt := ast.AssignStmt{
|
|
|
|
Lhs: assignStmt.Lhs,
|
|
|
|
TokPos: assignStmt.TokPos,
|
|
|
|
Tok: token.DEFINE,
|
|
|
|
Rhs: assignStmt.Rhs,
|
|
|
|
}
|
|
|
|
suggested := render(pass.Fset, &suggestedStmt)
|
2024-03-27 19:24:38 +01:00
|
|
|
|
|
|
|
pass.Report(analysis.Diagnostic{
|
|
|
|
Pos: assignStmt.Pos(),
|
|
|
|
Message: "context not shadowed in loop",
|
|
|
|
SuggestedFixes: []analysis.SuggestedFix{
|
|
|
|
{
|
2024-03-27 19:57:23 +01:00
|
|
|
Message: "replace `=` with `:=`",
|
2024-03-27 19:24:38 +01:00
|
|
|
TextEdits: []analysis.TextEdit{
|
|
|
|
{
|
|
|
|
Pos: assignStmt.Pos(),
|
|
|
|
End: assignStmt.End(),
|
|
|
|
NewText: []byte(suggested),
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
})
|
|
|
|
|
|
|
|
break
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func getBody(node ast.Node) (*ast.BlockStmt, error) {
|
|
|
|
forStmt, ok := node.(*ast.ForStmt)
|
|
|
|
if ok {
|
|
|
|
return forStmt.Body, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
rangeStmt, ok := node.(*ast.RangeStmt)
|
|
|
|
if ok {
|
|
|
|
return rangeStmt.Body, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil, errUnknown
|
|
|
|
}
|
|
|
|
|
|
|
|
// render returns the pretty-print of the given node
|
|
|
|
func render(fset *token.FileSet, x interface{}) string {
|
|
|
|
var buf bytes.Buffer
|
|
|
|
if err := printer.Fprint(&buf, fset, x); err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
return buf.String()
|
|
|
|
}
|