Skip to content

Commit 44be2f8

Browse files
Copilotpelikhan
andauthored
fix: resolve regexp package identity via type checker in regexpcompileinfunction linter
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
1 parent 979edab commit 44be2f8

3 files changed

Lines changed: 49 additions & 5 deletions

File tree

pkg/linters/regexpcompileinfunction/regexpcompileinfunction.go

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ func run(pass *analysis.Pass) (any, error) {
3434

3535
for cur := range insp.Root().Preorder((*ast.CallExpr)(nil)) {
3636
call, ok := cur.Node().(*ast.CallExpr)
37-
if !ok || !isRegexpCompileCall(call) {
37+
if !ok || !isRegexpCompileCall(pass, call) {
3838
continue
3939
}
4040
if !hasConstantStringPattern(pass, call) {
@@ -68,17 +68,27 @@ func run(pass *analysis.Pass) (any, error) {
6868
return nil, nil
6969
}
7070

71-
// isRegexpCompileCall checks if the call is to regexp.MustCompile or regexp.Compile.
72-
func isRegexpCompileCall(call *ast.CallExpr) bool {
71+
// isRegexpCompileCall checks if the call is to regexp.MustCompile or regexp.Compile,
72+
// resolving the package identity via the type checker to handle aliased imports
73+
// and avoid false positives from local identifiers named "regexp".
74+
func isRegexpCompileCall(pass *analysis.Pass, call *ast.CallExpr) bool {
7375
sel, ok := call.Fun.(*ast.SelectorExpr)
7476
if !ok {
7577
return false
7678
}
79+
if sel.Sel.Name != "MustCompile" && sel.Sel.Name != "Compile" {
80+
return false
81+
}
7782
ident, ok := sel.X.(*ast.Ident)
78-
if !ok {
83+
if !ok || pass.TypesInfo == nil {
84+
return false
85+
}
86+
obj := pass.TypesInfo.ObjectOf(ident)
87+
if obj == nil {
7988
return false
8089
}
81-
return ident.Name == "regexp" && (sel.Sel.Name == "MustCompile" || sel.Sel.Name == "Compile")
90+
pkgName, ok := obj.(*types.PkgName)
91+
return ok && pkgName.Imported().Path() == "regexp"
8292
}
8393

8494
// hasConstantStringPattern checks whether the regexp pattern is a compile-time constant string,
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package regexpcompileinfunction
2+
3+
import re "regexp"
4+
5+
// flagged: aliased import of regexp — alias must not cause a false negative.
6+
func ProcessWithAlias(s string) bool {
7+
r := re.MustCompile(`^[a-z]+$`) // want `regexp compilation inside function should be moved to package-level variable`
8+
return r.MatchString(s)
9+
}
10+
11+
func ValidateWithAlias(input string) (bool, error) {
12+
r, err := re.Compile(`\d+`) // want `regexp compilation inside function should be moved to package-level variable`
13+
if err != nil {
14+
return false, err
15+
}
16+
return r.MatchString(input), nil
17+
}
18+
19+
// not flagged: aliased import at package level is fine.
20+
var packageLevelAliasRegexp = re.MustCompile(`^[a-z]+$`)
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package regexpcompileinfunction
2+
3+
// customRegexp is an unrelated type that happens to have Compile/MustCompile methods.
4+
type customRegexp struct{}
5+
6+
func (customRegexp) Compile(_ string) (*customRegexp, error) { return &customRegexp{}, nil }
7+
func (customRegexp) MustCompile(_ string) *customRegexp { return &customRegexp{} }
8+
9+
// not flagged: local variable named "regexp" is not the stdlib regexp package.
10+
func GoodShadowedRegexpIdentifier(s string) bool {
11+
regexp := customRegexp{}
12+
_ = regexp.MustCompile(s)
13+
return true
14+
}

0 commit comments

Comments
 (0)