aboutsummaryrefslogtreecommitdiffstats
path: root/vendor/github.com/Crocmagnon
diff options
context:
space:
mode:
authorTaras Madan <tarasmadan@google.com>2024-09-10 12:16:33 +0200
committerTaras Madan <tarasmadan@google.com>2024-09-10 14:05:26 +0000
commitc97c816133b42257d0bcf1ee4bd178bb2a7a2b9e (patch)
tree0bcbc2e540bbf8f62f6c17887cdd53b8c2cee637 /vendor/github.com/Crocmagnon
parent54e657429ab892ad06c90cd7c1a4eb33ba93a3dc (diff)
vendor: update
Diffstat (limited to 'vendor/github.com/Crocmagnon')
-rw-r--r--vendor/github.com/Crocmagnon/fatcontext/LICENSE21
-rw-r--r--vendor/github.com/Crocmagnon/fatcontext/pkg/analyzer/analyzer.go224
2 files changed, 245 insertions, 0 deletions
diff --git a/vendor/github.com/Crocmagnon/fatcontext/LICENSE b/vendor/github.com/Crocmagnon/fatcontext/LICENSE
new file mode 100644
index 000000000..96f153ca4
--- /dev/null
+++ b/vendor/github.com/Crocmagnon/fatcontext/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2024 Gabriel Augendre
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/vendor/github.com/Crocmagnon/fatcontext/pkg/analyzer/analyzer.go b/vendor/github.com/Crocmagnon/fatcontext/pkg/analyzer/analyzer.go
new file mode 100644
index 000000000..a65efbba8
--- /dev/null
+++ b/vendor/github.com/Crocmagnon/fatcontext/pkg/analyzer/analyzer.go
@@ -0,0 +1,224 @@
+package analyzer
+
+import (
+ "bytes"
+ "errors"
+ "fmt"
+ "go/ast"
+ "go/printer"
+ "go/token"
+ "go/types"
+
+ "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: "fatcontext",
+ Doc: "detects nested contexts in loops and function literals",
+ 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),
+ (*ast.FuncLit)(nil),
+ }
+
+ inspctr.Preorder(nodeFilter, func(node ast.Node) {
+ body, err := getBody(node)
+ if err != nil {
+ return
+ }
+
+ assignStmt := findNestedContext(pass, node, body.List)
+ if assignStmt == nil {
+ return
+ }
+
+ suggestedStmt := ast.AssignStmt{
+ Lhs: assignStmt.Lhs,
+ TokPos: assignStmt.TokPos,
+ Tok: token.DEFINE,
+ Rhs: assignStmt.Rhs,
+ }
+ suggested, err := render(pass.Fset, &suggestedStmt)
+
+ var fixes []analysis.SuggestedFix
+ if err == nil {
+ fixes = append(fixes, analysis.SuggestedFix{
+ Message: "replace `=` with `:=`",
+ TextEdits: []analysis.TextEdit{
+ {
+ Pos: assignStmt.Pos(),
+ End: assignStmt.End(),
+ NewText: []byte(suggested),
+ },
+ },
+ })
+ }
+
+ pass.Report(analysis.Diagnostic{
+ Pos: assignStmt.Pos(),
+ Message: getReportMessage(node),
+ SuggestedFixes: fixes,
+ })
+ })
+
+ return nil, nil
+}
+
+func getReportMessage(node ast.Node) string {
+ switch node.(type) {
+ case *ast.ForStmt, *ast.RangeStmt:
+ return "nested context in loop"
+ case *ast.FuncLit:
+ return "nested context in function literal"
+ default:
+ return "unsupported nested context type"
+ }
+}
+
+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
+ }
+
+ funcLit, ok := node.(*ast.FuncLit)
+ if ok {
+ return funcLit.Body, nil
+ }
+
+ return nil, errUnknown
+}
+
+func findNestedContext(pass *analysis.Pass, node ast.Node, stmts []ast.Stmt) *ast.AssignStmt {
+ for _, stmt := range stmts {
+ // Recurse if necessary
+ if inner, ok := stmt.(*ast.BlockStmt); ok {
+ found := findNestedContext(pass, node, inner.List)
+ if found != nil {
+ return found
+ }
+ }
+
+ if inner, ok := stmt.(*ast.IfStmt); ok {
+ found := findNestedContext(pass, node, inner.Body.List)
+ if found != nil {
+ return found
+ }
+ }
+
+ if inner, ok := stmt.(*ast.SwitchStmt); ok {
+ found := findNestedContext(pass, node, inner.Body.List)
+ if found != nil {
+ return found
+ }
+ }
+
+ if inner, ok := stmt.(*ast.CaseClause); ok {
+ found := findNestedContext(pass, node, inner.Body)
+ if found != nil {
+ return found
+ }
+ }
+
+ if inner, ok := stmt.(*ast.SelectStmt); ok {
+ found := findNestedContext(pass, node, inner.Body.List)
+ if found != nil {
+ return found
+ }
+ }
+
+ if inner, ok := stmt.(*ast.CommClause); ok {
+ found := findNestedContext(pass, node, inner.Body)
+ if found != nil {
+ return found
+ }
+ }
+
+ // Actually check for nested context
+ 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 {
+ continue
+ }
+
+ // allow assignment to non-pointer children of values defined within the loop
+ if lhs := getRootIdent(pass, assignStmt.Lhs[0]); lhs != nil {
+ if obj := pass.TypesInfo.ObjectOf(lhs); obj != nil {
+ if checkObjectScopeWithinNode(obj.Parent(), node) {
+ continue // definition is within the loop
+ }
+ }
+ }
+
+ return assignStmt
+ }
+
+ return nil
+}
+
+func checkObjectScopeWithinNode(scope *types.Scope, node ast.Node) bool {
+ if scope == nil {
+ return false
+ }
+
+ if scope.Pos() >= node.Pos() && scope.End() <= node.End() {
+ return true
+ }
+
+ return false
+}
+
+func getRootIdent(pass *analysis.Pass, node ast.Node) *ast.Ident {
+ for {
+ switch n := node.(type) {
+ case *ast.Ident:
+ return n
+ case *ast.IndexExpr:
+ node = n.X
+ case *ast.SelectorExpr:
+ if sel, ok := pass.TypesInfo.Selections[n]; ok && sel.Indirect() {
+ return nil // indirected (pointer) roots don't imply a (safe) copy
+ }
+ node = n.X
+ default:
+ return nil
+ }
+ }
+}
+
+// render returns the pretty-print of the given node
+func render(fset *token.FileSet, x interface{}) (string, error) {
+ var buf bytes.Buffer
+ if err := printer.Fprint(&buf, fset, x); err != nil {
+ return "", fmt.Errorf("printing node: %w", err)
+ }
+ return buf.String(), nil
+}