diff --git a/internal/analyzers/noliteral/linter.go b/internal/analyzers/noliteral/linter.go index 1e88b89..aa453b8 100644 --- a/internal/analyzers/noliteral/linter.go +++ b/internal/analyzers/noliteral/linter.go @@ -2,10 +2,9 @@ package noliteral import ( "go/ast" - "go/token" - "strings" - "sync" + astutils "git.frostfs.info/TrueCloudLab/linters/pkg/ast-utils" + "github.com/mitchellh/mapstructure" "golang.org/x/tools/go/analysis" ) @@ -17,21 +16,39 @@ var LogsAnalyzer = &analysis.Analyzer{ Run: run, } -var ( - aliasCache = sync.Map{} -) - type Configuration struct { TargetMethods []string `mapstructure:"target-methods"` DisablePackages []string `mapstructure:"disable-packages"` ConstantsPackage string `mapstructure:"constants-package"` + Enable bool `mapstructure:"enable"` } var Config = Configuration{ TargetMethods: []string{"Debug", "Info", "Warn", "Error"}, } +func New(conf any) (*analysis.Analyzer, error) { + var config Configuration + config.Enable = true + + err := mapstructure.Decode(conf, &config) + if err != nil { + Config.Enable = true + return LogsAnalyzer, nil + } + + Config.TargetMethods = append(Config.TargetMethods, config.TargetMethods...) + Config.ConstantsPackage = config.ConstantsPackage + Config.DisablePackages = config.DisablePackages + 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) @@ -39,19 +56,19 @@ func run(pass *analysis.Pass) (interface{}, error) { return true } - isLog, _ := isLogDot(expr.Fun) + isLog, _ := astutils.IsTargetMethod(expr.Fun, Config.TargetMethods) if !isLog || len(expr.Args) == 0 { return true } - if !isStringValue(expr.Args[0]) { - alias, _ := getAliasByPkgName(file, Config.ConstantsPackage) - if Config.ConstantsPackage == "" || getPackageName(expr.Args[0]) == alias || getPackageName(expr.Args[0]) == "" { + if !astutils.IsStringValue(expr.Args[0]) { + alias, _ := astutils.GetAliasByPkgName(file, Config.ConstantsPackage) + if Config.ConstantsPackage == "" || astutils.GetPackageName(expr.Args[0]) == alias || astutils.GetPackageName(expr.Args[0]) == "" { return true } for _, pkgName := range Config.DisablePackages { - if pkgName == getPackageName(expr.Args[0]) { + if pkgName == astutils.GetPackageName(expr.Args[0]) { return true } } @@ -80,81 +97,3 @@ func run(pass *analysis.Pass) (interface{}, error) { return nil, nil } - -func isLogDot(expr ast.Expr) (bool, string) { - sel, ok := expr.(*ast.SelectorExpr) - if !ok { - return false, "" - } - - for _, method := range Config.TargetMethods { - if isIdent(sel.Sel, method) { - return true, method - } - } - return false, "" -} - -func isIdent(expr ast.Expr, ident string) bool { - id, ok := expr.(*ast.Ident) - if ok && id.Name == ident { - return true - } - - return false -} - -func isStringValue(expr ast.Expr) bool { - basicLit, ok := expr.(*ast.BasicLit) - return ok && basicLit.Kind == token.STRING -} - -func getAliasByPkgName(file *ast.File, pkgName string) (string, error) { - if alias, ok := aliasCache.Load(file); ok { - return alias.(string), nil - } - - var alias string - specs := file.Imports - - for _, spec := range specs { - alias = getAliasFromImportSpec(spec, pkgName) - if alias != "" { - break - } - } - - aliasCache.Store(file, alias) - return alias, nil -} - -func getAliasFromImportSpec(spec *ast.ImportSpec, pkgName string) string { - if spec == nil { - return "" - } - importName := strings.Replace(spec.Path.Value, "\"", "", -1) - if importName != pkgName { - return "" - } - - split := strings.Split(importName, "/") - - if len(split) == 0 { - return "" - } - alias := split[len(split)-1] - if spec.Name != nil { - alias = spec.Name.Name - } - - return alias -} - -func getPackageName(expr ast.Expr) string { - if selectorExpr, ok := expr.(*ast.SelectorExpr); ok { - if ident, ok := selectorExpr.X.(*ast.Ident); ok { - return ident.Name - } - } - return "" -} diff --git a/internal/analyzers/noliteral/linter_test.go b/internal/analyzers/noliteral/linter_test.go index c582b59..b968853 100644 --- a/internal/analyzers/noliteral/linter_test.go +++ b/internal/analyzers/noliteral/linter_test.go @@ -13,6 +13,7 @@ import ( func init() { Config.ConstantsPackage = "git.frostfs.info/TrueCloudLab/frostfs-node/internal/logs" + Config.Enable = true } func TestAnalyzer_negative(t *testing.T) { diff --git a/main.go b/main.go index 633c6c3..e5136d2 100644 --- a/main.go +++ b/main.go @@ -1,11 +1,7 @@ package main import ( - "os" - "strings" - - noliteral "git.frostfs.info/TrueCloudLab/linters/internal/analyzers/noliteral" - "github.com/mitchellh/mapstructure" + "git.frostfs.info/TrueCloudLab/linters/internal/analyzers/noliteral" "golang.org/x/tools/go/analysis" ) @@ -21,31 +17,16 @@ func (*analyzerPlugin) GetAnalyzers() []*analysis.Analyzer { // for version ci-lint >= '1.5.4'. func New(conf any) ([]*analysis.Analyzer, error) { - targetMethods := strings.Split(os.Getenv("NOLITERAL_TARGET_METHODS"), ",") - disablePackages := strings.Split(os.Getenv("NOLITERAL_DISABLE_PACKAGES"), ",") - constantsPackage := os.Getenv("NOLITERAL_CONSTANTS_PACKAGE") + confMap, ok := conf.(map[string]any) - configMap := map[string]any{ - "target-methods": targetMethods, - "constants-package": constantsPackage, - "disable-packages": disablePackages, + var noliteralConfig any + if ok { + noliteralConfig = confMap["noliteral"] } - - if confMap, ok := conf.(map[string]any); ok { - for k, v := range confMap { - configMap[k] = v - } - } - - var config noliteral.Configuration - err := mapstructure.Decode(configMap, &config) + noliteral, err := noliteral.New(noliteralConfig) if err != nil { return nil, err } - noliteral.Config.TargetMethods = append(noliteral.Config.TargetMethods, config.TargetMethods...) - noliteral.Config.ConstantsPackage = config.ConstantsPackage - noliteral.Config.DisablePackages = config.DisablePackages - - return []*analysis.Analyzer{noliteral.LogsAnalyzer}, nil + return []*analysis.Analyzer{noliteral}, nil } diff --git a/pkg/ast-utils/ast.go b/pkg/ast-utils/ast.go new file mode 100644 index 0000000..fcb379e --- /dev/null +++ b/pkg/ast-utils/ast.go @@ -0,0 +1,96 @@ +package astutils + +import ( + "go/ast" + "go/token" + "strings" + "sync" +) + +type aliasCacheKey struct { + File *ast.File + PkgName string +} + +var ( + aliasCache = sync.Map{} +) + +func IsTargetMethod(expr ast.Expr, targetMethods []string) (bool, string) { + sel, ok := expr.(*ast.SelectorExpr) + if !ok { + return false, "" + } + + for _, method := range targetMethods { + if IsIdent(sel.Sel, method) { + return true, method + } + } + return false, "" +} + +func IsIdent(expr ast.Expr, ident string) bool { + id, ok := expr.(*ast.Ident) + if ok && id.Name == ident { + return true + } + + return false +} + +func IsStringValue(expr ast.Expr) bool { + basicLit, ok := expr.(*ast.BasicLit) + return ok && basicLit.Kind == token.STRING +} + +func GetAliasByPkgName(file *ast.File, pkgName string) (string, error) { + key := aliasCacheKey{File: file, PkgName: pkgName} + if alias, ok := aliasCache.Load(key); ok { + return alias.(string), nil + } + + var alias string + specs := file.Imports + + for _, spec := range specs { + alias = GetAliasFromImportSpec(spec, pkgName) + if alias != "" { + break + } + } + + aliasCache.Store(key, alias) + return alias, nil +} + +func GetAliasFromImportSpec(spec *ast.ImportSpec, pkgName string) string { + if spec == nil { + return "" + } + importName := strings.Replace(spec.Path.Value, "\"", "", -1) + if importName != pkgName { + return "" + } + + split := strings.Split(importName, "/") + + if len(split) == 0 { + return "" + } + alias := split[len(split)-1] + if spec.Name != nil { + alias = spec.Name.Name + } + + return alias +} + +func GetPackageName(expr ast.Expr) string { + if selectorExpr, ok := expr.(*ast.SelectorExpr); ok { + if ident, ok := selectorExpr.X.(*ast.Ident); ok { + return ident.Name + } + } + return "" +}