package usestrconv import ( "go/ast" astutils "git.frostfs.info/TrueCloudLab/linters/pkg/ast-utils" "github.com/mitchellh/mapstructure" "golang.org/x/tools/go/analysis" ) const LinterName = "useStrconv" var LogsAnalyzer = &analysis.Analyzer{ Name: LinterName, Doc: LinterName + " linter recommends the utilization of `strconv` over `fmt` when performing string conversions of primitive data types.", Run: run, } type Configuration struct { Enable bool `mapstructure:"enable"` } var Config = Configuration{ Enable: true, } var modyficators = []string{"%d", "%f", "%t", "%x"} func New(conf any) (*analysis.Analyzer, error) { configMap, ok := conf.(map[string]any) if !ok { Config.Enable = true return LogsAnalyzer, nil } var config Configuration config.Enable = true err := mapstructure.Decode(configMap, &config) if err != nil { return nil, err } Config.Enable = config.Enable return LogsAnalyzer, nil } func run(pass *analysis.Pass) (interface{}, error) { if !Config.Enable { return nil, nil } for _, file := range pass.Files { ast.Inspect(file, func(n ast.Node) bool { expr, ok := n.(*ast.CallExpr) if !ok { return true } expectedPackageAlias, err := astutils.GetAliasByPkgName(file, "fmt") if err != nil { return false } if expectedPackageAlias != astutils.GetPackageName(expr.Fun) { return true } isTarget, _ := astutils.IsTargetMethod(expr.Fun, []string{"Sprintf"}) if !isTarget || !astutils.IsStringValue(expr.Args[0]) { return true } stringLiteral, ok := expr.Args[0].(*ast.BasicLit) if !ok { return true } modValue := unquote(stringLiteral.Value) for _, modificator := range modyficators { if modValue == modificator { pass.Report(analysis.Diagnostic{ Pos: expr.Pos(), End: expr.End(), Category: LinterName, Message: `Usage of fmt.Sprintf(` + modificator + `) is not allowed`, SuggestedFixes: nil, }) return false } } return true }) } return nil, nil } func unquote(s string) string { if len(s) < 2 { return s } return s[1 : len(s)-1] }