mirror of
https://github.com/Crocmagnon/fatcontext.git
synced 2024-11-21 23:48:10 +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