From 818daea6c152b7d7171991e98d9b47496c42f37a Mon Sep 17 00:00:00 2001 From: Philippe Antoine Date: Mon, 13 Nov 2023 16:01:39 +0100 Subject: [PATCH] fix: fix imports Fixes #14 --- analyzer/analyzer.go | 146 +++++++++++++++++++++++ analyzer/testdata/src/noconv/p.go | 2 +- analyzer/testdata/src/noconv/p.go.golden | 1 + analyzer/testdata/src/p/p.go | 2 +- analyzer/testdata/src/p/p.go.golden | 2 + 5 files changed, 151 insertions(+), 2 deletions(-) diff --git a/analyzer/analyzer.go b/analyzer/analyzer.go index 0c6a76d..d117e82 100644 --- a/analyzer/analyzer.go +++ b/analyzer/analyzer.go @@ -6,6 +6,7 @@ import ( "go/format" "go/token" "go/types" + "sort" "strconv" "strings" @@ -58,6 +59,8 @@ func (n *perfSprint) run(pass *analysis.Pass) (interface{}, error) { if fmtSprintfObj == nil && fmtSprintObj == nil && fmtErrorfObj == nil { return nil, nil } + removedFmtUsages := make(map[string]int) + neededPackages := make(map[string]map[string]bool) insp := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector) nodeFilter := []ast.Node{ @@ -129,7 +132,14 @@ func (n *perfSprint) run(pass *analysis.Pass) (interface{}, error) { var d *analysis.Diagnostic switch { case isBasicType(valueType, types.String) && oneOf(verb, "%v", "%s"): + fname := pass.Fset.File(call.Pos()).Name() + removedFmtUsages[fname] = removedFmtUsages[fname] + 1 + _, ok := neededPackages[fname] + if !ok { + neededPackages[fname] = make(map[string]bool) + } if fn == "fmt.Errorf" { + neededPackages[fname]["errors"] = true d = &analysis.Diagnostic{ Pos: call.Pos(), End: call.End(), @@ -166,6 +176,8 @@ func (n *perfSprint) run(pass *analysis.Pass) (interface{}, error) { // known false positive if this error is nil // fmt.Sprint(nil) does not panic like nil.Error() does errMethodCall := formatNode(pass.Fset, value) + ".Error()" + fname := pass.Fset.File(call.Pos()).Name() + removedFmtUsages[fname] = removedFmtUsages[fname] + 1 d = &analysis.Diagnostic{ Pos: call.Pos(), End: call.End(), @@ -183,6 +195,13 @@ func (n *perfSprint) run(pass *analysis.Pass) (interface{}, error) { } case isBasicType(valueType, types.Bool) && oneOf(verb, "%v", "%t"): + fname := pass.Fset.File(call.Pos()).Name() + removedFmtUsages[fname] = removedFmtUsages[fname] + 1 + _, ok := neededPackages[fname] + if !ok { + neededPackages[fname] = make(map[string]bool) + } + neededPackages[fname]["strconv"] = true d = &analysis.Diagnostic{ Pos: call.Pos(), End: call.End(), @@ -205,6 +224,13 @@ func (n *perfSprint) run(pass *analysis.Pass) (interface{}, error) { return } + fname := pass.Fset.File(call.Pos()).Name() + removedFmtUsages[fname] = removedFmtUsages[fname] + 1 + _, ok := neededPackages[fname] + if !ok { + neededPackages[fname] = make(map[string]bool) + } + neededPackages[fname]["encoding/hex"] = true d = &analysis.Diagnostic{ Pos: call.Pos(), End: call.End(), @@ -228,6 +254,13 @@ func (n *perfSprint) run(pass *analysis.Pass) (interface{}, error) { }, } case isSlice && isBasicType(s.Elem(), types.Uint8) && oneOf(verb, "%x"): + fname := pass.Fset.File(call.Pos()).Name() + removedFmtUsages[fname] = removedFmtUsages[fname] + 1 + _, ok := neededPackages[fname] + if !ok { + neededPackages[fname] = make(map[string]bool) + } + neededPackages[fname]["encoding/hex"] = true d = &analysis.Diagnostic{ Pos: call.Pos(), End: call.End(), @@ -245,6 +278,13 @@ func (n *perfSprint) run(pass *analysis.Pass) (interface{}, error) { } case isBasicType(valueType, types.Int8, types.Int16, types.Int32) && oneOf(verb, "%v", "%d") && n.intConv: + fname := pass.Fset.File(call.Pos()).Name() + removedFmtUsages[fname] = removedFmtUsages[fname] + 1 + _, ok := neededPackages[fname] + if !ok { + neededPackages[fname] = make(map[string]bool) + } + neededPackages[fname]["strconv"] = true d = &analysis.Diagnostic{ Pos: call.Pos(), End: call.End(), @@ -268,6 +308,13 @@ func (n *perfSprint) run(pass *analysis.Pass) (interface{}, error) { }, } case isBasicType(valueType, types.Int) && oneOf(verb, "%v", "%d"): + fname := pass.Fset.File(call.Pos()).Name() + removedFmtUsages[fname] = removedFmtUsages[fname] + 1 + _, ok := neededPackages[fname] + if !ok { + neededPackages[fname] = make(map[string]bool) + } + neededPackages[fname]["strconv"] = true d = &analysis.Diagnostic{ Pos: call.Pos(), End: call.End(), @@ -284,6 +331,13 @@ func (n *perfSprint) run(pass *analysis.Pass) (interface{}, error) { }, } case isBasicType(valueType, types.Int64) && oneOf(verb, "%v", "%d"): + fname := pass.Fset.File(call.Pos()).Name() + removedFmtUsages[fname] = removedFmtUsages[fname] + 1 + _, ok := neededPackages[fname] + if !ok { + neededPackages[fname] = make(map[string]bool) + } + neededPackages[fname]["strconv"] = true d = &analysis.Diagnostic{ Pos: call.Pos(), End: call.End(), @@ -312,6 +366,13 @@ func (n *perfSprint) run(pass *analysis.Pass) (interface{}, error) { if verb == "%x" { base = []byte("), 16") } + fname := pass.Fset.File(call.Pos()).Name() + removedFmtUsages[fname] = removedFmtUsages[fname] + 1 + _, ok := neededPackages[fname] + if !ok { + neededPackages[fname] = make(map[string]bool) + } + neededPackages[fname]["strconv"] = true d = &analysis.Diagnostic{ Pos: call.Pos(), End: call.End(), @@ -339,6 +400,13 @@ func (n *perfSprint) run(pass *analysis.Pass) (interface{}, error) { if verb == "%x" { base = []byte(", 16") } + fname := pass.Fset.File(call.Pos()).Name() + removedFmtUsages[fname] = removedFmtUsages[fname] + 1 + _, ok := neededPackages[fname] + if !ok { + neededPackages[fname] = make(map[string]bool) + } + neededPackages[fname]["strconv"] = true d = &analysis.Diagnostic{ Pos: call.Pos(), End: call.End(), @@ -368,6 +436,8 @@ func (n *perfSprint) run(pass *analysis.Pass) (interface{}, error) { } else { fix = formatNode(pass.Fset, value) + "+" + strconv.Quote(verb[2:]) } + fname := pass.Fset.File(call.Pos()).Name() + removedFmtUsages[fname] = removedFmtUsages[fname] + 1 d = &analysis.Diagnostic{ Pos: call.Pos(), End: call.End(), @@ -391,6 +461,82 @@ func (n *perfSprint) run(pass *analysis.Pass) (interface{}, error) { } }) + if len(removedFmtUsages) > 0 { + for _, pkg := range pass.Pkg.Imports() { + if pkg.Path() == "fmt" { + insp = pass.ResultOf[inspect.Analyzer].(*inspector.Inspector) + nodeFilter = []ast.Node{ + (*ast.SelectorExpr)(nil), + } + insp.Preorder(nodeFilter, func(node ast.Node) { + selec := node.(*ast.SelectorExpr) + selecok, ok := selec.X.(*ast.Ident) + if ok { + pkgname, ok := pass.TypesInfo.ObjectOf(selecok).(*types.PkgName) + if ok && pkgname.Name() == pkg.Name() { + fname := pass.Fset.File(pkgname.Pos()).Name() + removedFmtUsages[fname] = removedFmtUsages[fname] - 1 + } + } + }) + } else if pkg.Path() == "errors" || pkg.Path() == "strconv" || pkg.Path() == "encoding/hex" { + insp = pass.ResultOf[inspect.Analyzer].(*inspector.Inspector) + nodeFilter = []ast.Node{ + (*ast.ImportSpec)(nil), + } + insp.Preorder(nodeFilter, func(node ast.Node) { + gd := node.(*ast.ImportSpec) + if gd.Path.Value == strconv.Quote(pkg.Path()) { + fname := pass.Fset.File(gd.Pos()).Name() + _, ok := neededPackages[fname] + if ok { + delete(neededPackages[fname], pkg.Path()) + } + } + }) + } + } + insp = pass.ResultOf[inspect.Analyzer].(*inspector.Inspector) + nodeFilter = []ast.Node{ + (*ast.ImportSpec)(nil), + } + insp.Preorder(nodeFilter, func(node ast.Node) { + gd := node.(*ast.ImportSpec) + if gd.Path.Value == `"fmt"` { + fix := "" + fname := pass.Fset.File(gd.Pos()).Name() + if removedFmtUsages[fname] < 0 { + fix = fix + `"fmt"` + if len(neededPackages[fname]) == 0 { + return + } + } + keys := make([]string, 0, len(neededPackages[fname])) + for k := range neededPackages[fname] { + keys = append(keys, k) + } + sort.Strings(keys) + for _, k := range keys { + fix = fix + "\n\t\"" + k + `"` + } + pass.Report(analysis.Diagnostic{ + Pos: gd.Pos(), + End: gd.End(), + Message: "Fix imports", + SuggestedFixes: []analysis.SuggestedFix{ + { + Message: "Fix imports", + TextEdits: []analysis.TextEdit{{ + Pos: gd.Pos(), + End: gd.End(), + NewText: []byte(fix), + }}, + }, + }}) + } + }) + } + return nil, nil } diff --git a/analyzer/testdata/src/noconv/p.go b/analyzer/testdata/src/noconv/p.go index 29a8a0a..b21faa5 100644 --- a/analyzer/testdata/src/noconv/p.go +++ b/analyzer/testdata/src/noconv/p.go @@ -2,7 +2,7 @@ package noconv import ( "errors" - "fmt" + "fmt" // want "Fix imports" "os" ) diff --git a/analyzer/testdata/src/noconv/p.go.golden b/analyzer/testdata/src/noconv/p.go.golden index 46740fe..4d8330b 100644 --- a/analyzer/testdata/src/noconv/p.go.golden +++ b/analyzer/testdata/src/noconv/p.go.golden @@ -4,6 +4,7 @@ import ( "errors" "fmt" "os" + "strconv" // want "Fix imports" ) var errSentinel = errors.New("connection refused") diff --git a/analyzer/testdata/src/p/p.go b/analyzer/testdata/src/p/p.go index c5628a9..367877c 100644 --- a/analyzer/testdata/src/p/p.go +++ b/analyzer/testdata/src/p/p.go @@ -2,7 +2,7 @@ package p import ( "errors" - "fmt" + "fmt" // want "Fix imports" "io" "log" "net/url" diff --git a/analyzer/testdata/src/p/p.go.golden b/analyzer/testdata/src/p/p.go.golden index ce735d2..6ca0b3b 100644 --- a/analyzer/testdata/src/p/p.go.golden +++ b/analyzer/testdata/src/p/p.go.golden @@ -1,12 +1,14 @@ package p import ( + "encoding/hex" "errors" "fmt" "io" "log" "net/url" "os" + "strconv" // want "Fix imports" "time" )