github.com/amarpal/go-tools@v0.0.0-20240422043104-40142f59f616/simple/s1039/s1039.go (about)

     1  package s1039
     2  
     3  import (
     4  	"fmt"
     5  	"go/ast"
     6  	"go/types"
     7  	"strings"
     8  
     9  	"github.com/amarpal/go-tools/analysis/code"
    10  	"github.com/amarpal/go-tools/analysis/edit"
    11  	"github.com/amarpal/go-tools/analysis/facts/generated"
    12  	"github.com/amarpal/go-tools/analysis/lint"
    13  	"github.com/amarpal/go-tools/analysis/report"
    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:     "S1039",
    23  		Run:      run,
    24  		Requires: []*analysis.Analyzer{inspect.Analyzer, generated.Analyzer},
    25  	},
    26  	Doc: &lint.Documentation{
    27  		Title: `Unnecessary use of \'fmt.Sprint\'`,
    28  		Text: `
    29  Calling \'fmt.Sprint\' with a single string argument is unnecessary
    30  and identical to using the string directly.`,
    31  		Since: "2020.1",
    32  		// MergeIfAll because s might not be a string under all build tags.
    33  		// you shouldn't write code like that…
    34  		MergeIf: lint.MergeIfAll,
    35  	},
    36  })
    37  
    38  var Analyzer = SCAnalyzer.Analyzer
    39  
    40  var checkSprintLiteralQ = pattern.MustParse(`
    41  	(CallExpr
    42  		fn@(Or
    43  			(Symbol "fmt.Sprint")
    44  			(Symbol "fmt.Sprintf"))
    45  		[lit@(BasicLit "STRING" _)])`)
    46  
    47  func run(pass *analysis.Pass) (interface{}, error) {
    48  	// We only flag calls with string literals, not expressions of
    49  	// type string, because some people use fmt.Sprint(s) as a pattern
    50  	// for copying strings, which may be useful when extracting a small
    51  	// substring from a large string.
    52  	fn := func(node ast.Node) {
    53  		m, ok := code.Match(pass, checkSprintLiteralQ, node)
    54  		if !ok {
    55  			return
    56  		}
    57  		callee := m.State["fn"].(*types.Func)
    58  		lit := m.State["lit"].(*ast.BasicLit)
    59  		if callee.Name() == "Sprintf" {
    60  			if strings.ContainsRune(lit.Value, '%') {
    61  				// This might be a format string
    62  				return
    63  			}
    64  		}
    65  		report.Report(pass, node, fmt.Sprintf("unnecessary use of fmt.%s", callee.Name()),
    66  			report.FilterGenerated(),
    67  			report.Fixes(edit.Fix("Replace with string literal", edit.ReplaceWithNode(pass.Fset, node, lit))))
    68  	}
    69  	code.Preorder(pass, fn, (*ast.CallExpr)(nil))
    70  	return nil, nil
    71  }