Skip to content

Commit bd842d2

Browse files
Use typesinfo to compare packages (#6)
This avoids a false positive in shadow.go, and enables detecting dot-imported. Fixes #5 Co-authored-by: Choko <[email protected]>
1 parent d6fa985 commit bd842d2

File tree

4 files changed

+64
-46
lines changed

4 files changed

+64
-46
lines changed

internal/analyzer/analyzer.go

Lines changed: 43 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -3,20 +3,22 @@ package analyzer
33
import (
44
"fmt"
55
"go/ast"
6+
"go/types"
67
"regexp"
7-
"strconv"
8-
"strings"
98

109
"golang.org/x/tools/go/analysis"
10+
"golang.org/x/tools/go/analysis/passes/inspect"
11+
"golang.org/x/tools/go/ast/inspector"
1112
)
1213

1314
const FlagPattern = "pattern"
1415

1516
func New() *analysis.Analyzer {
1617
a := &analysis.Analyzer{
17-
Name: "reassign",
18-
Doc: "Checks that package variables are not reassigned",
19-
Run: run,
18+
Name: "reassign",
19+
Doc: "Checks that package variables are not reassigned",
20+
Requires: []*analysis.Analyzer{inspect.Analyzer},
21+
Run: run,
2022
}
2123
a.Flags.String(FlagPattern, `^(Err.*|EOF)$`, "Pattern to match package variables against to prevent reassignment")
2224
return a
@@ -27,60 +29,56 @@ func run(pass *analysis.Pass) (interface{}, error) {
2729
if err != nil {
2830
return nil, fmt.Errorf("invalid pattern: %w", err)
2931
}
30-
for _, f := range pass.Files {
31-
state := &fileState{imports: make(map[string]struct{})}
32-
ast.Inspect(f, func(node ast.Node) bool {
33-
return inspect(pass, node, checkRE, state)
34-
})
35-
}
32+
33+
inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector)
34+
inspect.Preorder([]ast.Node{(*ast.AssignStmt)(nil), (*ast.UnaryExpr)(nil)}, func(node ast.Node) {
35+
switch node := node.(type) {
36+
case *ast.AssignStmt:
37+
for _, lhs := range node.Lhs {
38+
reportImported(pass, lhs, checkRE, "reassigning")
39+
}
40+
default:
41+
// TODO(chokoswitch): Consider handling operations other than assignment on globals, for example
42+
// taking their address.
43+
}
44+
})
3645
return nil, nil
3746
}
3847

39-
type fileState struct {
40-
imports map[string]struct{}
41-
}
48+
func reportImported(pass *analysis.Pass, expr ast.Expr, checkRE *regexp.Regexp, prefix string) {
49+
switch x := expr.(type) {
50+
case *ast.SelectorExpr:
51+
if !checkRE.MatchString(x.Sel.Name) {
52+
return
53+
}
4254

43-
func inspect(pass *analysis.Pass, node ast.Node, checkRE *regexp.Regexp, state *fileState) bool {
44-
if importSpec, ok := node.(*ast.ImportSpec); ok {
45-
if importSpec.Name != nil {
46-
state.imports[importSpec.Name.Name] = struct{}{}
47-
} else {
48-
n, err := strconv.Unquote(importSpec.Path.Value)
49-
if err != nil {
50-
return true
51-
}
52-
if idx := strings.LastIndexByte(n, '/'); idx != -1 {
53-
n = n[idx+1:]
55+
selectIdent, ok := x.X.(*ast.Ident)
56+
if !ok {
57+
return
58+
}
59+
60+
if selectObj, ok := pass.TypesInfo.Uses[selectIdent]; ok {
61+
if pkg, ok := selectObj.(*types.PkgName); !ok || pkg.Imported() == pass.Pkg {
62+
return
5463
}
55-
state.imports[n] = struct{}{}
5664
}
57-
return true
58-
}
5965

60-
assignStmt, ok := node.(*ast.AssignStmt)
61-
if !ok {
62-
return true
63-
}
66+
pass.Reportf(expr.Pos(), "%s variable %s in other package %s", prefix, x.Sel.Name, selectIdent.Name)
6467

65-
for _, lhs := range assignStmt.Lhs {
66-
selector, ok := lhs.(*ast.SelectorExpr)
68+
case *ast.Ident:
69+
use, ok := pass.TypesInfo.Uses[x].(*types.Var)
6770
if !ok {
68-
return true
71+
return
6972
}
7073

71-
if !checkRE.MatchString(selector.Sel.Name) {
72-
return true
74+
if use.Pkg() == pass.Pkg {
75+
return
7376
}
7477

75-
selectIdent, ok := selector.X.(*ast.Ident)
76-
if !ok {
77-
return true
78+
if !checkRE.MatchString(x.Name) {
79+
return
7880
}
7981

80-
if _, ok := state.imports[selectIdent.Name]; ok {
81-
pass.Reportf(node.Pos(), "reassigning variable %s in other package %s", selector.Sel.Name, selectIdent.Name)
82-
}
82+
pass.Reportf(expr.Pos(), "%s variable %s from other package %s", prefix, x.Name, use.Pkg().Path())
8383
}
84-
85-
return true
8684
}

internal/analyzer/analyzer_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ func TestAnalyzer(t *testing.T) {
4040
if tt.pattern != "" {
4141
_ = a.Flags.Set("pattern", tt.pattern)
4242
}
43-
analysistest.Run(t, td, a, "a")
43+
analysistest.Run(t, td, a, "./...")
4444
})
4545
}
4646
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package dotimport
2+
3+
import . "io"
4+
5+
func direct() {
6+
EOF = nil // want "reassigning variable"
7+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package shadow
2+
3+
import "fmt"
4+
5+
func use() {
6+
fmt.Println("hi")
7+
}
8+
9+
func shadow() any {
10+
fmt := struct{ EOF int }{}
11+
fmt.EOF = 5
12+
return fmt
13+
}

0 commit comments

Comments
 (0)