mirror of
https://github.com/Crocmagnon/fatcontext.git
synced 2024-11-25 01:18:09 +01:00
initial commit
This commit is contained in:
commit
c07c76b205
8 changed files with 201 additions and 0 deletions
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
.idea
|
10
cmd/foreshadow/main.go
Normal file
10
cmd/foreshadow/main.go
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/Crocmagnon/foreshadow/pkg/analyzer"
|
||||||
|
"golang.org/x/tools/go/analysis/singlechecker"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
singlechecker.Main(analyzer.Analyzer)
|
||||||
|
}
|
21
contrib/example.go
Normal file
21
contrib/example.go
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
package contrib
|
||||||
|
|
||||||
|
import "context"
|
||||||
|
|
||||||
|
func ok() {
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
for i := 0; i < 10; i++ {
|
||||||
|
ctx := context.WithValue(ctx, "key", i)
|
||||||
|
_ = ctx
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func notOk() {
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
for i := 0; i < 10; i++ {
|
||||||
|
ctx = context.WithValue(ctx, "key", i)
|
||||||
|
_ = ctx
|
||||||
|
}
|
||||||
|
}
|
7
go.mod
Normal file
7
go.mod
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
module github.com/Crocmagnon/foreshadow
|
||||||
|
|
||||||
|
go 1.22.1
|
||||||
|
|
||||||
|
require golang.org/x/tools v0.19.0
|
||||||
|
|
||||||
|
require golang.org/x/mod v0.16.0 // indirect
|
6
go.sum
Normal file
6
go.sum
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
golang.org/x/mod v0.16.0 h1:QX4fJ0Rr5cPQCF7O9lh9Se4pmwfwskqZfq5moyldzic=
|
||||||
|
golang.org/x/mod v0.16.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||||
|
golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ=
|
||||||
|
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||||
|
golang.org/x/tools v0.19.0 h1:tfGCXNR1OsFG+sVdLAitlpjAvD/I6dHDKnYrpEZUHkw=
|
||||||
|
golang.org/x/tools v0.19.0/go.mod h1:qoJWxmGSIBmAeriMx19ogtrEPrGtDbPK634QFIcLAhc=
|
105
pkg/analyzer/analyzer.go
Normal file
105
pkg/analyzer/analyzer.go
Normal file
|
@ -0,0 +1,105 @@
|
||||||
|
package analyzer
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"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",
|
||||||
|
Doc: "Enforces context shadowing inside loops.",
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
assignStmt.Tok = token.DEFINE
|
||||||
|
suggested := render(pass.Fset, assignStmt)
|
||||||
|
|
||||||
|
pass.Report(analysis.Diagnostic{
|
||||||
|
Pos: assignStmt.Pos(),
|
||||||
|
Message: "context not shadowed in loop",
|
||||||
|
SuggestedFixes: []analysis.SuggestedFix{
|
||||||
|
{
|
||||||
|
Message: fmt.Sprintf("replace `=` with `:=`"),
|
||||||
|
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()
|
||||||
|
}
|
19
pkg/analyzer/analyzer_test.go
Normal file
19
pkg/analyzer/analyzer_test.go
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
package analyzer_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/Crocmagnon/foreshadow/pkg/analyzer"
|
||||||
|
"golang.org/x/tools/go/analysis/analysistest"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestAll(t *testing.T) {
|
||||||
|
wd, err := os.Getwd()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to get wd: %s", err)
|
||||||
|
}
|
||||||
|
testdata := filepath.Join(filepath.Dir(filepath.Dir(wd)), "testdata")
|
||||||
|
|
||||||
|
analysistest.Run(t, testdata, analyzer.Analyzer, "./...")
|
||||||
|
}
|
32
testdata/src/example.go
vendored
Normal file
32
testdata/src/example.go
vendored
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
package src
|
||||||
|
|
||||||
|
import "context"
|
||||||
|
|
||||||
|
func example() {
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
for i := 0; i < 10; i++ {
|
||||||
|
ctx := context.WithValue(ctx, "key", i)
|
||||||
|
ctx = context.WithValue(ctx, "other", "val")
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < 10; i++ {
|
||||||
|
ctx = context.WithValue(ctx, "key", i) // want "context not shadowed in loop"
|
||||||
|
ctx = context.WithValue(ctx, "other", "val")
|
||||||
|
}
|
||||||
|
|
||||||
|
for item := range []string{"one", "two", "three"} {
|
||||||
|
ctx = wrapContext(ctx) // want "context not shadowed in loop"
|
||||||
|
ctx := context.WithValue(ctx, "key", item)
|
||||||
|
ctx = wrapContext(ctx)
|
||||||
|
}
|
||||||
|
|
||||||
|
for {
|
||||||
|
ctx = wrapContext(ctx) // want "context not shadowed in loop"
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func wrapContext(ctx context.Context) context.Context {
|
||||||
|
return context.WithoutCancel(ctx)
|
||||||
|
}
|
Loading…
Reference in a new issue