diff --git a/pkg/linters/regexpcompileinfunction/regexpcompileinfunction.go b/pkg/linters/regexpcompileinfunction/regexpcompileinfunction.go index 49ed2c5defb..01362d544dc 100644 --- a/pkg/linters/regexpcompileinfunction/regexpcompileinfunction.go +++ b/pkg/linters/regexpcompileinfunction/regexpcompileinfunction.go @@ -34,7 +34,7 @@ func run(pass *analysis.Pass) (any, error) { for cur := range insp.Root().Preorder((*ast.CallExpr)(nil)) { call, ok := cur.Node().(*ast.CallExpr) - if !ok || !isRegexpCompileCall(call) { + if !ok || !isRegexpCompileCall(pass, call) { continue } if !hasConstantStringPattern(pass, call) { @@ -68,17 +68,30 @@ func run(pass *analysis.Pass) (any, error) { return nil, nil } -// isRegexpCompileCall checks if the call is to regexp.MustCompile or regexp.Compile. -func isRegexpCompileCall(call *ast.CallExpr) bool { +// isRegexpCompileCall checks if the call is to regexp.MustCompile or regexp.Compile, +// resolving the package identity via the type checker to handle aliased imports +// and avoid false positives from local identifiers named "regexp". +func isRegexpCompileCall(pass *analysis.Pass, call *ast.CallExpr) bool { sel, ok := call.Fun.(*ast.SelectorExpr) if !ok { return false } + if sel.Sel.Name != "MustCompile" && sel.Sel.Name != "Compile" { + return false + } ident, ok := sel.X.(*ast.Ident) - if !ok { + if !ok || pass.TypesInfo == nil { + return false + } + obj := pass.TypesInfo.ObjectOf(ident) + if obj == nil { + return false + } + pkgName, ok := obj.(*types.PkgName) + if !ok || pkgName.Imported() == nil { return false } - return ident.Name == "regexp" && (sel.Sel.Name == "MustCompile" || sel.Sel.Name == "Compile") + return pkgName.Imported().Path() == "regexp" } // hasConstantStringPattern checks whether the regexp pattern is a compile-time constant string, diff --git a/pkg/linters/regexpcompileinfunction/testdata/src/regexpcompileinfunction/alias_import.go b/pkg/linters/regexpcompileinfunction/testdata/src/regexpcompileinfunction/alias_import.go new file mode 100644 index 00000000000..c78ccb8f650 --- /dev/null +++ b/pkg/linters/regexpcompileinfunction/testdata/src/regexpcompileinfunction/alias_import.go @@ -0,0 +1,20 @@ +package regexpcompileinfunction + +import re "regexp" + +// flagged: aliased import of regexp — alias must not cause a false negative. +func ProcessWithAlias(s string) bool { + r := re.MustCompile(`^[a-z]+$`) // want `regexp compilation inside function should be moved to package-level variable` + return r.MatchString(s) +} + +func ValidateWithAlias(input string) (bool, error) { + r, err := re.Compile(`\d+`) // want `regexp compilation inside function should be moved to package-level variable` + if err != nil { + return false, err + } + return r.MatchString(input), nil +} + +// not flagged: aliased import at package level is fine. +var packageLevelAliasRegexp = re.MustCompile(`^[a-z]+$`) diff --git a/pkg/linters/regexpcompileinfunction/testdata/src/regexpcompileinfunction/dot_import.go b/pkg/linters/regexpcompileinfunction/testdata/src/regexpcompileinfunction/dot_import.go new file mode 100644 index 00000000000..a39e6163267 --- /dev/null +++ b/pkg/linters/regexpcompileinfunction/testdata/src/regexpcompileinfunction/dot_import.go @@ -0,0 +1,11 @@ +package regexpcompileinfunction + +import . "regexp" + +// NOT flagged: dot imports produce bare identifier calls, not selector expressions, +// so isRegexpCompileCall exits early at the *ast.SelectorExpr guard. +// This is a known limitation of the linter. +func DotImportExample() bool { + r := MustCompile(`^[a-z]+$`) // not flagged: dot-import calls are not selector expressions + return r.MatchString("abc") +} diff --git a/pkg/linters/regexpcompileinfunction/testdata/src/regexpcompileinfunction/shadowed_identifier.go b/pkg/linters/regexpcompileinfunction/testdata/src/regexpcompileinfunction/shadowed_identifier.go new file mode 100644 index 00000000000..f051ddec67f --- /dev/null +++ b/pkg/linters/regexpcompileinfunction/testdata/src/regexpcompileinfunction/shadowed_identifier.go @@ -0,0 +1,14 @@ +package regexpcompileinfunction + +// customRegexp is an unrelated type that happens to have Compile/MustCompile methods. +type customRegexp struct{} + +func (customRegexp) Compile(_ string) (*customRegexp, error) { return &customRegexp{}, nil } +func (customRegexp) MustCompile(_ string) *customRegexp { return &customRegexp{} } + +// not flagged: local variable named "regexp" is not the stdlib regexp package. +func GoodShadowedRegexpIdentifier() bool { + regexp := customRegexp{} + _ = regexp.MustCompile(`^[a-z]+$`) + return true +}