github.com/amarpal/go-tools@v0.0.0-20240422043104-40142f59f616/quickfix/qf1005/qf1005.go (about) 1 package qf1005 2 3 import ( 4 "go/ast" 5 "go/constant" 6 "go/token" 7 "go/types" 8 9 "github.com/amarpal/go-tools/analysis/code" 10 "github.com/amarpal/go-tools/analysis/edit" 11 "github.com/amarpal/go-tools/analysis/lint" 12 "github.com/amarpal/go-tools/analysis/report" 13 "github.com/amarpal/go-tools/go/ast/astutil" 14 "github.com/amarpal/go-tools/pattern" 15 16 "golang.org/x/tools/go/analysis" 17 "golang.org/x/tools/go/analysis/passes/inspect" 18 ) 19 20 var SCAnalyzer = lint.InitializeAnalyzer(&lint.Analyzer{ 21 Analyzer: &analysis.Analyzer{ 22 Name: "QF1005", 23 Run: run, 24 Requires: []*analysis.Analyzer{inspect.Analyzer}, 25 }, 26 Doc: &lint.Documentation{ 27 Title: `Expand call to \'math.Pow\'`, 28 Text: `Some uses of \'math.Pow\' can be simplified to basic multiplication.`, 29 Before: `math.Pow(x, 2)`, 30 After: `x * x`, 31 Since: "2021.1", 32 Severity: lint.SeverityHint, 33 }, 34 }) 35 36 var Analyzer = SCAnalyzer.Analyzer 37 38 var mathPowQ = pattern.MustParse(`(CallExpr (Symbol "math.Pow") [x (IntegerLiteral n)])`) 39 40 func run(pass *analysis.Pass) (interface{}, error) { 41 fn := func(node ast.Node) { 42 matcher, ok := code.Match(pass, mathPowQ, node) 43 if !ok { 44 return 45 } 46 47 x := matcher.State["x"].(ast.Expr) 48 if code.MayHaveSideEffects(pass, x, nil) { 49 return 50 } 51 n, ok := constant.Int64Val(constant.ToInt(matcher.State["n"].(types.TypeAndValue).Value)) 52 if !ok { 53 return 54 } 55 56 needConversion := false 57 if T, ok := pass.TypesInfo.Types[x]; ok && T.Value != nil { 58 info := types.Info{ 59 Types: map[ast.Expr]types.TypeAndValue{}, 60 } 61 62 // determine if the constant expression would have type float64 if used on its own 63 if err := types.CheckExpr(pass.Fset, pass.Pkg, x.Pos(), x, &info); err != nil { 64 // This should not happen 65 return 66 } 67 if T, ok := info.Types[x].Type.(*types.Basic); ok { 68 if T.Kind() != types.UntypedFloat && T.Kind() != types.Float64 { 69 needConversion = true 70 } 71 } else { 72 needConversion = true 73 } 74 } 75 76 var replacement ast.Expr 77 switch n { 78 case 0: 79 replacement = &ast.BasicLit{ 80 Kind: token.FLOAT, 81 Value: "1.0", 82 } 83 case 1: 84 replacement = x 85 case 2, 3: 86 r := &ast.BinaryExpr{ 87 X: x, 88 Op: token.MUL, 89 Y: x, 90 } 91 for i := 3; i <= int(n); i++ { 92 r = &ast.BinaryExpr{ 93 X: r, 94 Op: token.MUL, 95 Y: x, 96 } 97 } 98 99 rc, ok := astutil.CopyExpr(r) 100 if !ok { 101 return 102 } 103 replacement = astutil.SimplifyParentheses(rc) 104 default: 105 return 106 } 107 if needConversion && n != 0 { 108 replacement = &ast.CallExpr{ 109 Fun: &ast.Ident{Name: "float64"}, 110 Args: []ast.Expr{replacement}, 111 } 112 } 113 report.Report(pass, node, "could expand call to math.Pow", 114 report.Fixes(edit.Fix("Expand call to math.Pow", edit.ReplaceWithNode(pass.Fset, node, replacement)))) 115 } 116 code.Preorder(pass, fn, (*ast.CallExpr)(nil)) 117 return nil, nil 118 }