From 184546c5aa421d192599be24e1d4d346c62720fe Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 19 Jun 2026 05:43:45 +0000 Subject: [PATCH 1/3] Initial plan From 7b7160f0fcdcab21e51d8e712e1b4ddb9bdd667e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 19 Jun 2026 05:54:49 +0000 Subject: [PATCH 2/3] Migrate osexitinlibrary, fprintlnsprintf, errstringmatch to IsPkgSelector Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- pkg/linters/errstringmatch/errstringmatch.go | 10 +++------- .../testdata/src/errstringmatch/alias_import.go | 8 ++++++++ .../testdata/src/errstringmatch/errstringmatch.go | 10 ++++++++++ pkg/linters/fprintlnsprintf/fprintlnsprintf.go | 12 ++++-------- .../testdata/src/fprintlnsprintf/alias_import.go | 11 +++++++++++ .../src/fprintlnsprintf/alias_import.go.golden | 11 +++++++++++ .../testdata/src/fprintlnsprintf/fprintlnsprintf.go | 11 +++++++++++ .../src/fprintlnsprintf/fprintlnsprintf.go.golden | 11 +++++++++++ pkg/linters/osexitinlibrary/osexitinlibrary.go | 6 +----- .../testdata/src/osexitinlibrary/alias_import.go | 8 ++++++++ .../testdata/src/osexitinlibrary/osexitinlibrary.go | 10 ++++++++++ 11 files changed, 88 insertions(+), 20 deletions(-) create mode 100644 pkg/linters/errstringmatch/testdata/src/errstringmatch/alias_import.go create mode 100644 pkg/linters/fprintlnsprintf/testdata/src/fprintlnsprintf/alias_import.go create mode 100644 pkg/linters/fprintlnsprintf/testdata/src/fprintlnsprintf/alias_import.go.golden create mode 100644 pkg/linters/osexitinlibrary/testdata/src/osexitinlibrary/alias_import.go diff --git a/pkg/linters/errstringmatch/errstringmatch.go b/pkg/linters/errstringmatch/errstringmatch.go index db0175faacc..87a0afbe169 100644 --- a/pkg/linters/errstringmatch/errstringmatch.go +++ b/pkg/linters/errstringmatch/errstringmatch.go @@ -46,7 +46,7 @@ func run(pass *analysis.Pass) (any, error) { } // Match strings.Contains(X, Y) - if !isStringsContains(outer) { + if !isStringsContains(pass, outer) { return } if len(outer.Args) != 2 { @@ -73,16 +73,12 @@ func run(pass *analysis.Pass) (any, error) { } // isStringsContains returns true for strings.Contains(...) call expressions. -func isStringsContains(call *ast.CallExpr) bool { +func isStringsContains(pass *analysis.Pass, call *ast.CallExpr) bool { sel, ok := call.Fun.(*ast.SelectorExpr) if !ok { return false } - ident, ok := sel.X.(*ast.Ident) - if !ok { - return false - } - return ident.Name == "strings" && sel.Sel.Name == "Contains" + return astutil.IsPkgSelector(pass, sel, "strings") && sel.Sel.Name == "Contains" } // isErrDotError returns true when expr is a method call of the form .Error() diff --git a/pkg/linters/errstringmatch/testdata/src/errstringmatch/alias_import.go b/pkg/linters/errstringmatch/testdata/src/errstringmatch/alias_import.go new file mode 100644 index 00000000000..01351eaae8d --- /dev/null +++ b/pkg/linters/errstringmatch/testdata/src/errstringmatch/alias_import.go @@ -0,0 +1,8 @@ +package errstringmatch + +import str "strings" + +// aliased import: str.Contains(err.Error(), ...) should still be flagged. +func checkErrorAlias(err error) bool { + return str.Contains(err.Error(), "not found") // want `avoid strings\.Contains\(err\.Error\(\)` +} diff --git a/pkg/linters/errstringmatch/testdata/src/errstringmatch/errstringmatch.go b/pkg/linters/errstringmatch/testdata/src/errstringmatch/errstringmatch.go index aa2db862eb0..83f4fdbdd3e 100644 --- a/pkg/linters/errstringmatch/testdata/src/errstringmatch/errstringmatch.go +++ b/pkg/linters/errstringmatch/testdata/src/errstringmatch/errstringmatch.go @@ -35,3 +35,13 @@ func checkIgnoredPreviousLine(err error) bool { func checkIgnoredSameLine(err error) bool { return strings.Contains(err.Error(), "already merged") //nolint:errstringmatch // gh CLI merge status is only available as text. } + +// shadowed: a local variable named "strings" with a Contains method should NOT be flagged. +type stringsLike struct{} + +func (stringsLike) Contains(s, sub string) bool { return false } + +func shadowedStrings(err error) bool { + strings := stringsLike{} + return strings.Contains(err.Error(), "not found") // should NOT be flagged +} diff --git a/pkg/linters/fprintlnsprintf/fprintlnsprintf.go b/pkg/linters/fprintlnsprintf/fprintlnsprintf.go index d649078f596..fece88d32da 100644 --- a/pkg/linters/fprintlnsprintf/fprintlnsprintf.go +++ b/pkg/linters/fprintlnsprintf/fprintlnsprintf.go @@ -43,7 +43,7 @@ func run(pass *analysis.Pass) (any, error) { } // Check if this is exactly fmt.Fprintln(w, fmt.Sprintf(...)). - if !isFmtFunc(call, "Fprintln") { + if !isFmtFunc(pass, call, "Fprintln") { return } if len(call.Args) != 2 { @@ -61,7 +61,7 @@ func run(pass *analysis.Pass) (any, error) { if !ok { return } - if !isFmtFunc(printedArg, "Sprintf") { + if !isFmtFunc(pass, printedArg, "Sprintf") { return } if nolint.HasDirective(pos, noLintLinesByFile) { @@ -132,14 +132,10 @@ func buildFprintfFix(call *ast.CallExpr, sprintfCall *ast.CallExpr) []analysis.S } // isFmtFunc returns true if call is a call to fmt.. -func isFmtFunc(call *ast.CallExpr, name string) bool { +func isFmtFunc(pass *analysis.Pass, call *ast.CallExpr, name string) bool { sel, ok := call.Fun.(*ast.SelectorExpr) if !ok { return false } - ident, ok := sel.X.(*ast.Ident) - if !ok { - return false - } - return ident.Name == "fmt" && sel.Sel.Name == name + return astutil.IsPkgSelector(pass, sel, "fmt") && sel.Sel.Name == name } diff --git a/pkg/linters/fprintlnsprintf/testdata/src/fprintlnsprintf/alias_import.go b/pkg/linters/fprintlnsprintf/testdata/src/fprintlnsprintf/alias_import.go new file mode 100644 index 00000000000..e09255d6eb5 --- /dev/null +++ b/pkg/linters/fprintlnsprintf/testdata/src/fprintlnsprintf/alias_import.go @@ -0,0 +1,11 @@ +package fprintlnsprintf + +import ( + f "fmt" + "os" +) + +// aliased import: f.Fprintln(w, f.Sprintf(...)) should still be flagged. +func flaggedAlias(name string) { + f.Fprintln(os.Stderr, f.Sprintf("hello %s", name)) // want "use fmt.Fprintf" +} diff --git a/pkg/linters/fprintlnsprintf/testdata/src/fprintlnsprintf/alias_import.go.golden b/pkg/linters/fprintlnsprintf/testdata/src/fprintlnsprintf/alias_import.go.golden new file mode 100644 index 00000000000..4bf32c9486e --- /dev/null +++ b/pkg/linters/fprintlnsprintf/testdata/src/fprintlnsprintf/alias_import.go.golden @@ -0,0 +1,11 @@ +package fprintlnsprintf + +import ( + f "fmt" + "os" +) + +// aliased import: f.Fprintln(w, f.Sprintf(...)) should still be flagged. +func flaggedAlias(name string) { + f.Fprintf(os.Stderr, "hello %s\n", name) // want "use fmt.Fprintf" +} diff --git a/pkg/linters/fprintlnsprintf/testdata/src/fprintlnsprintf/fprintlnsprintf.go b/pkg/linters/fprintlnsprintf/testdata/src/fprintlnsprintf/fprintlnsprintf.go index 0e523366563..586b68964d5 100644 --- a/pkg/linters/fprintlnsprintf/testdata/src/fprintlnsprintf/fprintlnsprintf.go +++ b/pkg/linters/fprintlnsprintf/testdata/src/fprintlnsprintf/fprintlnsprintf.go @@ -26,3 +26,14 @@ func suppressed(name string) { fmt.Fprintln(os.Stderr, fmt.Sprintf("hello %s", name)) fmt.Fprintln(os.Stderr, fmt.Sprintf("hello %s", name)) //nolint:fprintlnsprintf } + +// shadowed: a local variable named "fmt" with Fprintln/Sprintf methods should NOT be flagged. +type fmtWriter struct{} + +func (fmtWriter) Fprintln(_ interface{}, _ ...interface{}) {} +func (fmtWriter) Sprintf(_ string, _ ...interface{}) string { return "" } + +func shadowedFmt(name string) { + fmt := fmtWriter{} + fmt.Fprintln(os.Stderr, fmt.Sprintf("hello %s", name)) // should NOT be flagged +} diff --git a/pkg/linters/fprintlnsprintf/testdata/src/fprintlnsprintf/fprintlnsprintf.go.golden b/pkg/linters/fprintlnsprintf/testdata/src/fprintlnsprintf/fprintlnsprintf.go.golden index 736668e41aa..33f7b461320 100644 --- a/pkg/linters/fprintlnsprintf/testdata/src/fprintlnsprintf/fprintlnsprintf.go.golden +++ b/pkg/linters/fprintlnsprintf/testdata/src/fprintlnsprintf/fprintlnsprintf.go.golden @@ -26,3 +26,14 @@ func suppressed(name string) { fmt.Fprintln(os.Stderr, fmt.Sprintf("hello %s", name)) fmt.Fprintln(os.Stderr, fmt.Sprintf("hello %s", name)) //nolint:fprintlnsprintf } + +// shadowed: a local variable named "fmt" with Fprintln/Sprintf methods should NOT be flagged. +type fmtWriter struct{} + +func (fmtWriter) Fprintln(_ interface{}, _ ...interface{}) {} +func (fmtWriter) Sprintf(_ string, _ ...interface{}) string { return "" } + +func shadowedFmt(name string) { + fmt := fmtWriter{} + fmt.Fprintln(os.Stderr, fmt.Sprintf("hello %s", name)) // should NOT be flagged +} diff --git a/pkg/linters/osexitinlibrary/osexitinlibrary.go b/pkg/linters/osexitinlibrary/osexitinlibrary.go index cc99e8ac73e..b705386f03e 100644 --- a/pkg/linters/osexitinlibrary/osexitinlibrary.go +++ b/pkg/linters/osexitinlibrary/osexitinlibrary.go @@ -52,11 +52,7 @@ func run(pass *analysis.Pass) (any, error) { if !ok { return } - ident, ok := sel.X.(*ast.Ident) - if !ok { - return - } - if ident.Name == "os" && sel.Sel.Name == "Exit" { + if astutil.IsPkgSelector(pass, sel, "os") && sel.Sel.Name == "Exit" { position := pass.Fset.PositionFor(call.Pos(), false) if nolint.HasDirective(position, noLintLinesByFile) { return diff --git a/pkg/linters/osexitinlibrary/testdata/src/osexitinlibrary/alias_import.go b/pkg/linters/osexitinlibrary/testdata/src/osexitinlibrary/alias_import.go new file mode 100644 index 00000000000..2c6de21997b --- /dev/null +++ b/pkg/linters/osexitinlibrary/testdata/src/osexitinlibrary/alias_import.go @@ -0,0 +1,8 @@ +package osexitinlibrary + +import xos "os" + +// aliased import: xos.Exit should still be flagged. +func stopProcessAlias() { + xos.Exit(1) // want `os.Exit called in library package` +} diff --git a/pkg/linters/osexitinlibrary/testdata/src/osexitinlibrary/osexitinlibrary.go b/pkg/linters/osexitinlibrary/testdata/src/osexitinlibrary/osexitinlibrary.go index 36fe0f9e495..e0c6cbe9cf0 100644 --- a/pkg/linters/osexitinlibrary/testdata/src/osexitinlibrary/osexitinlibrary.go +++ b/pkg/linters/osexitinlibrary/testdata/src/osexitinlibrary/osexitinlibrary.go @@ -20,3 +20,13 @@ func suppressedPreviousLine() { func suppressedSameLine() { os.Exit(1) //nolint:osexitinlibrary } + +// shadowed: a local variable named "os" with an Exit method should NOT be flagged. +type osLike struct{} + +func (osLike) Exit(int) {} + +func shadowedOs() { + os := osLike{} + os.Exit(1) // should NOT be flagged +} From 6b9e3ea501ea8dd2f12c33b8e081f456aa9eef37 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 19 Jun 2026 06:36:12 +0000 Subject: [PATCH 3/3] fprintlnsprintf: use io.Writer and any in shadow stub per review suggestion Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- .../testdata/src/fprintlnsprintf/fprintlnsprintf.go | 5 +++-- .../testdata/src/fprintlnsprintf/fprintlnsprintf.go.golden | 5 +++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/pkg/linters/fprintlnsprintf/testdata/src/fprintlnsprintf/fprintlnsprintf.go b/pkg/linters/fprintlnsprintf/testdata/src/fprintlnsprintf/fprintlnsprintf.go index 586b68964d5..2c6f0bf6c0b 100644 --- a/pkg/linters/fprintlnsprintf/testdata/src/fprintlnsprintf/fprintlnsprintf.go +++ b/pkg/linters/fprintlnsprintf/testdata/src/fprintlnsprintf/fprintlnsprintf.go @@ -2,6 +2,7 @@ package fprintlnsprintf import ( "fmt" + "io" "os" ) @@ -30,8 +31,8 @@ func suppressed(name string) { // shadowed: a local variable named "fmt" with Fprintln/Sprintf methods should NOT be flagged. type fmtWriter struct{} -func (fmtWriter) Fprintln(_ interface{}, _ ...interface{}) {} -func (fmtWriter) Sprintf(_ string, _ ...interface{}) string { return "" } +func (fmtWriter) Fprintln(_ io.Writer, _ ...any) {} +func (fmtWriter) Sprintf(_ string, _ ...any) string { return "" } func shadowedFmt(name string) { fmt := fmtWriter{} diff --git a/pkg/linters/fprintlnsprintf/testdata/src/fprintlnsprintf/fprintlnsprintf.go.golden b/pkg/linters/fprintlnsprintf/testdata/src/fprintlnsprintf/fprintlnsprintf.go.golden index 33f7b461320..b8b347e2832 100644 --- a/pkg/linters/fprintlnsprintf/testdata/src/fprintlnsprintf/fprintlnsprintf.go.golden +++ b/pkg/linters/fprintlnsprintf/testdata/src/fprintlnsprintf/fprintlnsprintf.go.golden @@ -2,6 +2,7 @@ package fprintlnsprintf import ( "fmt" + "io" "os" ) @@ -30,8 +31,8 @@ func suppressed(name string) { // shadowed: a local variable named "fmt" with Fprintln/Sprintf methods should NOT be flagged. type fmtWriter struct{} -func (fmtWriter) Fprintln(_ interface{}, _ ...interface{}) {} -func (fmtWriter) Sprintf(_ string, _ ...interface{}) string { return "" } +func (fmtWriter) Fprintln(_ io.Writer, _ ...any) {} +func (fmtWriter) Sprintf(_ string, _ ...any) string { return "" } func shadowedFmt(name string) { fmt := fmtWriter{}