8000 staticcheck: preserve import aliases in fixes by dajiaohuang · Pull Request #1761 · dominikh/go-tools · GitHub
[go: up one dir, main page]

Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 15 additions & 9 deletions staticcheck/sa1004/sa1004.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"fmt"
"go/ast"
"go/constant"
"go/token"
"go/types"

"honnef.co/go/tools/analysis/code"
Expand Down Expand Up @@ -43,14 +44,10 @@ for some amount of nanoseconds.`,

var Analyzer = SCAnalyzer.Analyzer

var (
checkTimeSleepConstantPatternQ = pattern.MustParse(`(CallExpr (Symbol "time.Sleep") lit@(IntegerLiteral value))`)
checkTimeSleepConstantPatternRns = pattern.MustParse(`(BinaryExpr duration "*" (SelectorExpr (Ident "time") (Ident "Nanosecond")))`)
checkTimeSleepConstantPatternRs = pattern.MustParse(`(BinaryExpr duration "*" (SelectorExpr (Ident "time") (Ident "Second")))`)
)
var checkTimeSleepConstantPatternQ = pattern.MustParse(`(CallExpr (Symbol "time.Sleep") lit@(IntegerLiteral value))`)

func run(pass *analysis.Pass) (any, error) {
for _, m := range code.Matches(pass, checkTimeSleepConstantPatternQ) {
for callNode, m := range code.Matches(pass, checkTimeSleepConstantPatternQ) {
n, ok := constant.Int64Val(m.State["value"].(types.TypeAndValue).Value)
if !ok {
continue
Expand All @@ -62,11 +59,20 @@ func run(pass *analysis.Pass) (any, error) {
continue
}

lit := m.State["lit"].(ast.Node)
lit := m.State["lit"].(ast.Expr)
call := callNode.(*ast.CallExpr)
selector, ok := ast.Unparen(call.Fun).(*ast.SelectorExpr)
if !ok {
report.Report(pass, lit, fmt.Sprintf("sleeping for %d nanoseconds is probably a bug; be explicit if it isn't", n))
continue
}
pkg := selector.X
oneNS := &ast.BinaryExpr{X: lit, Op: token.MUL, Y: &ast.SelectorExpr{X: pkg, Sel: ast.NewIdent("Nanosecond")}}
oneS := &ast.BinaryExpr{X: lit, Op: token.MUL, Y: &ast.SelectorExpr{X: pkg, Sel: ast.NewIdent("Second")}}
report.Report(pass, lit,
fmt.Sprintf("sleeping for %d nanoseconds is probably a bug; be explicit if it isn't", n), report.Fixes(
edit.Fix("Explicitly use nanoseconds", edit.ReplaceWithPattern(pass.Fset, lit, checkTimeSleepConstantPatternRns, pattern.State{"duration": lit})),
edit.Fix("Use seconds", edit.ReplaceWithPattern(pass.Fset, lit, checkTimeSleepConstantPatternRs, pattern.State{"duration": lit}))))
edit.Fix("Explicitly use nanoseconds", edit.ReplaceWithNode(pass.Fset, lit, oneNS)),
edit.Fix("Use seconds", edit.ReplaceWithNode(pass.Fset, lit, oneS))))
}
return nil, nil
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package pkg

import timelib "time"

const c1 = 1
const c2 = 200

func fn() {
timelib.Sleep(1) //@ diag(`sleeping for 1`)
timelib.Sleep(42) //@ diag(`sleeping for 42`)
timelib.Sleep(201)
timelib.Sleep(c1)
timelib.Sleep(c2)
timelib.Sleep(2 * timelib.Nanosecond)
timelib.Sleep(timelib.Nanosecond)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
-- Explicitly use nanoseconds --
package pkg

import timelib "time"

const c1 = 1
const c2 = 200

func fn() {
timelib.Sleep(1 * timelib.Nanosecond) //@ diag(`sleeping for 1`)
timelib.Sleep(42 * timelib.Nanosecond) //@ diag(`sleeping for 42`)
timelib.Sleep(201)
timelib.Sleep(c1)
timelib.Sleep(c2)
timelib.Sleep(2 * timelib.Nanosecond)
timelib.Sleep(timelib.Nanosecond)
}

-- Use seconds --
package pkg

import timelib "time"

const c1 = 1
const c2 = 200

func fn() {
timelib.Sleep(1 * timelib.Second) //@ diag(`sleeping for 1`)
timelib.Sleep(42 * timelib.Second) //@ diag(`sleeping for 42`)
timelib.Sleep(201)
timelib.Sleep(c1)
timelib.Sleep(c2)
timelib.Sleep(2 * timelib.Nanosecond)
timelib.Sleep(timelib.Nanosecond)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package pkg

import . "time"

func fn() {
Sleep(1) //@ diag(`sleeping for 1`)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package pkg

import . "time"

func fn() {
Sleep(1) //@ diag(`sleeping for 1`)
}
38 changes: 34 additions & 4 deletions staticcheck/sa1008/sa1008.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package sa1008
import (
"fmt"
"go/ast"
"go/types"
"net/http"
"strconv"

Expand Down Expand Up @@ -92,11 +93,13 @@ func run(pass *analysis.Pass) (any, error) {
case *ast.BasicLit:
fix = edit.Fix("Canonicalize header key", edit.ReplaceWithString(op.Index, strconv.Quote(canonical)))
case *ast.Ident:
call := &ast.CallExpr{
Fun: edit.Selector("http", "CanonicalHeaderKey"),
Args: []ast.Expr{op.Index},
if name := importAlias(pass, op, "net/http"); name != "" {
call := &ast.CallExpr{
Fun: edit.Selector(name, "CanonicalHeaderKey"),
Args: []ast.Expr{op.Index},
}
fix = edit.Fix("Wrap in http.CanonicalHeaderKey", edit.ReplaceWithNode(pass.Fset, op.Index, call))
}
fix = edit.Fix("Wrap in http.CanonicalHeaderKey", edit.ReplaceWithNode(pass.Fset, op.Index, call))
}
msg := fmt.Sprintf("keys in http.Header are canonicalized, %q is not canonical; fix the constant or use http.CanonicalHeaderKey", s)
if fix.Message != "" {
Expand All @@ -109,3 +112,30 @@ func run(pass *analysis.Pass) (any, error) {
pass.ResultOf[inspect.Analyzer].(*inspector.Inspector).Nodes([]ast.Node{(*ast.AssignStmt)(nil), (*ast.IndexExpr)(nil)}, fn)
return nil, nil
}

func importAlias(pass *analysis.Pass, node ast.Node, importPath string) string {
for _, spec := range code.File(pass, node).Imports {
p, err := strconv.Unquote(spec.Path.Value)
if err != nil || p != importPath {
continue
}
var pkgName *types.PkgName
if spec.Name == nil {
pkgName, _ = pass.TypesInfo.Implicits[spec].(*types.PkgName)
} else {
pkgName, _ = pass.TypesInfo.Defs[spec.Name].(*types.PkgName)
}
if pkgName == nil || pkgName.Name() == "." || pkgName.Name() == "_" {
return ""
}
scope := pass.TypesInfo.Scopes[code.File(pass, node)].Innermost(node.Pos())
if scope != nil {
_, obj := scope.LookupParent(pkgName.Name(), node.Pos())
if obj == pkgName {
return pkgName.Name()
}
}
return ""
}
return ""
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package pkg

import httpalias "net/http"

func fn() {
const hdr = "foo"
var r httpalias.Request
h := httpalias.Header{}
var m map[string][]string
_ = h["foo"] //@ diag(`keys in http.Header are canonicalized`)
_ = h[hdr] //@ diag(`keys in http.Header are canonicalized`)
h["foo"] = nil
_ = r.Header["foo"] //@ diag(`keys in http.Header are canonicalized`)
r.Header["foo"] = nil
_ = m["foo"]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package pkg

import httpalias "net/http"

func fn() {
const hdr = "foo"
var r httpalias.Request
h := httpalias.Header{}
var m map[string][]string
_ = h["Foo"] //@ diag(`keys in http.Header are canonicalized`)
_ = h[httpalias.CanonicalHeaderKey(hdr)] //@ diag(`keys in http.Header are canonicalized`)
h["foo"] = nil
_ = r.Header["Foo"] //@ diag(`keys in http.Header are canonicalized`)
r.Header["foo"] = nil
_ = m["foo"]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package pkg

import htp "net/http"

func fn() {
h := htp.Header{}
htp := 1
_ = htp
const hdr = "foo"
_ = h[hdr] //@ diag(`keys in http.Header are canonicalized`)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package pkg

import htp "net/http"

func fn() {
h := htp.Header{}
htp := 1
_ = htp
const hdr = "foo"
_ = h[hdr] //@ diag(`keys in http.Header are canonicalized`)
}
45 changes: 37 additions & 8 deletions staticcheck/sa1012/sa1012.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package sa1012
import (
"go/ast"
"go/types"
"strconv"

"honnef.co/go/tools/analysis/code"
"honnef.co/go/tools/analysis/edit"
Expand Down Expand Up @@ -36,12 +37,6 @@ var Analyzer = SCAnalyzer.Analyzer
var checkNilContextQ = pattern.MustParse(`(CallExpr fun@(Symbol _) (Builtin "nil"):_)`)

func run(pass *analysis.Pass) (any, error) {
todo := &ast.CallExpr{
Fun: edit.Selector("context", "TODO"),
}
bg := &ast.CallExpr{
Fun: edit.Selector("context", "Background"),
}
for node, m := range code.Matches(pass, checkNilContextQ) {
call := node.(*ast.CallExpr)
fun, ok := m.State["fun"].(*types.Func)
Expand All @@ -59,10 +54,44 @@ func run(pass *analysis.Pass) (any, error) {
if !typeutil.IsTypeWithName(sig.Params().At(0).Type(), "context.Context") {
continue
}
report.Report(pass, call.Args[0],
"do not pass a nil Context, even if a function permits it; pass context.TODO if you are unsure about which Context to use", report.Fixes(
options := []report.Option(nil)
if contextAlias := importAlias(pass, call, "context"); contextAlias != "" {
todo := &ast.CallExpr{Fun: edit.Selector(contextAlias, "TODO")}
bg := &ast.CallExpr{Fun: edit.Selector(contextAlias, "Background")}
options = append(options, report.Fixes(
edit.Fix("Use context.TODO", edit.ReplaceWithNode(pass.Fset, call.Args[0], todo)),
edit.Fix("Use context.Background", edit.ReplaceWithNode(pass.Fset, call.Args[0], bg))))
}
report.Report(pass, call.Args[0],
"do not pass a nil Context, even if a function permits it; pass context.TODO if you are unsure about which Context to use", options...)
}
return nil, nil
}

func importAlias(pass *analysis.Pass, node ast.Node, path string) string {
file := code.File(pass, node)
for _, spec := range file.Imports {
importPath, err := strconv.Unquote(spec.Path.Value)
if err != nil || importPath != path {
continue
}
var pkgName *types.PkgName
if spec.Name == nil {
pkgName, _ = pass.TypesInfo.Implicits[spec].(*types.PkgName)
} else {
pkgName, _ = pass.TypesInfo.Defs[spec.Name].(*types.PkgName)
}
if pkgName == nil || pkgName.Name() == "." || pkgName.Name() == "_" {
return ""
}
scope := pass.TypesInfo.Scopes[file].Innermost(node.Pos())
if scope != nil {
_, obj := scope.LookupParent(pkgName.Name(), node.Pos())
if obj == pkgName {
return pkgName.Name()
}
}
return ""
}
return ""
}
10BC8
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package pkg

import ctx "context"

func fn(c ctx.Context) {}

func call() {
fn(nil) //@ diag(`do not pass a nil Context`)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
-- Use context.Background --
package pkg

import ctx "context"

func fn(c ctx.Context) {}

func call() {
fn(ctx.Background()) //@ diag(`do not pass a nil Context`)
}
-- Use context.TODO --
package pkg

import ctx "context"

func fn(c ctx.Context) {}

func call() {
fn(ctx.TODO()) //@ diag(`do not pass a nil Context`)
}
11 changes: 11 additions & 0 deletions staticcheck/sa1012/testdata/go1.0/CheckNilContextShadow/scope.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package pkg

import ctx "context"

func fn(c ctx.Context) {}

func call() {
ctx := 1
_ = ctx
fn(nil) //@ diag(`do not pass a nil Context`)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package pkg

import ctx "context"

func fn(c ctx.Context) {}

func call() {
ctx := 1
_ = ctx
fn(nil) //@ diag(`do not pass a nil Context`)
}
Loading
0