diff --git a/internal/ginkgohandler/dothandler.go b/internal/ginkgohandler/dothandler.go new file mode 100644 index 0000000..9c54b43 --- /dev/null +++ b/internal/ginkgohandler/dothandler.go @@ -0,0 +1,36 @@ +package ginkgohandler + +import ( + "go/ast" + + "golang.org/x/tools/go/analysis" + + "github.com/nunnatsa/ginkgolinter/types" +) + +// dotHandler is used when importing ginkgo with dot; i.e. +// import . "github.com/onsi/ginkgo" +type dotHandler struct{} + +func (h dotHandler) HandleGinkgoSpecs(expr ast.Expr, config types.Config, pass *analysis.Pass) bool { + return handleGinkgoSpecs(expr, config, pass, h) +} + +func (h dotHandler) getFocusContainerName(exp *ast.CallExpr) (bool, *ast.Ident) { + if fun, ok := exp.Fun.(*ast.Ident); ok { + return isFocusContainer(fun.Name), fun + } + return false, nil +} + +func (h dotHandler) isWrapContainer(exp *ast.CallExpr) bool { + if fun, ok := exp.Fun.(*ast.Ident); ok { + return isWrapContainer(fun.Name) + } + return false +} + +func (h dotHandler) isFocusSpec(exp ast.Expr) bool { + id, ok := exp.(*ast.Ident) + return ok && id.Name == focusSpec +} diff --git a/internal/ginkgohandler/ginkgoinfo.go b/internal/ginkgohandler/ginkgoinfo.go new file mode 100644 index 0000000..d8bb753 --- /dev/null +++ b/internal/ginkgohandler/ginkgoinfo.go @@ -0,0 +1,63 @@ +package ginkgohandler + +const ( // container names + describe = "Describe" + pdescribe = "PDescribe" + xdescribe = "XDescribe" + fdescribe = "FDescribe" + + when = "When" + pwhen = "PWhen" + xwhen = "XWhen" + fwhen = "FWhen" + + contextContainer = "Context" + pcontext = "PContext" + xcontext = "XContext" + fcontext = "FContext" + + it = "It" + pit = "PIt" + xit = "XIt" + fit = "FIt" + + describeTable = "DescribeTable" + pdescribeTable = "PDescribeTable" + xdescribeTable = "XDescribeTable" + fdescribeTable = "FDescribeTable" + + entry = "Entry" + pentry = "PEntry" + xentry = "XEntry" + fentry = "FEntry" +) + +func isFocusContainer(name string) bool { + switch name { + case fdescribe, fcontext, fwhen, fit, fdescribeTable, fentry: + return true + } + return false +} + +func isContainer(name string) bool { + switch name { + case it, when, contextContainer, describe, describeTable, entry, + pit, pwhen, pcontext, pdescribe, pdescribeTable, pentry, + xit, xwhen, xcontext, xdescribe, xdescribeTable, xentry: + return true + } + return isFocusContainer(name) +} + +func isWrapContainer(name string) bool { + switch name { + case when, contextContainer, describe, + fwhen, fcontext, fdescribe, + pwhen, pcontext, pdescribe, + xwhen, xcontext, xdescribe: + return true + } + + return false +} diff --git a/internal/ginkgohandler/handler.go b/internal/ginkgohandler/handler.go index f10d831..c44e3e8 100644 --- a/internal/ginkgohandler/handler.go +++ b/internal/ginkgohandler/handler.go @@ -2,6 +2,10 @@ package ginkgohandler import ( "go/ast" + + "golang.org/x/tools/go/analysis" + + "github.com/nunnatsa/ginkgolinter/types" ) const ( @@ -14,116 +18,31 @@ const ( // Handler provide different handling, depend on the way ginkgo was imported, whether // in imported with "." name, custom name or without any name. type Handler interface { - GetFocusContainerName(*ast.CallExpr) (bool, *ast.Ident) - IsWrapContainer(*ast.CallExpr) bool - IsFocusSpec(ident ast.Expr) bool + HandleGinkgoSpecs(ast.Expr, types.Config, *analysis.Pass) bool + getFocusContainerName(*ast.CallExpr) (bool, *ast.Ident) + isWrapContainer(*ast.CallExpr) bool + isFocusSpec(ident ast.Expr) bool } // GetGinkgoHandler returns a ginkgor handler according to the way ginkgo was imported in the specific file func GetGinkgoHandler(file *ast.File) Handler { for _, imp := range file.Imports { - if imp.Path.Value != importPath && imp.Path.Value != importPathV2 { - continue - } + switch imp.Path.Value { + + case importPath, importPathV2: + switch name := imp.Name.String(); { + case name == ".": + return dotHandler{} + case name == "": // import with no local name + return nameHandler("ginkgo") + default: + return nameHandler(name) + } - switch name := imp.Name.String(); { - case name == ".": - return dotHandler{} - case name == "": // import with no local name - return nameHandler("ginkgo") default: - return nameHandler(name) - } - } - - return nil // no ginkgo import; this file does not use ginkgo -} - -// dotHandler is used when importing ginkgo with dot; i.e. -// import . "github.com/onsi/ginkgo" -type dotHandler struct{} - -func (h dotHandler) GetFocusContainerName(exp *ast.CallExpr) (bool, *ast.Ident) { - if fun, ok := exp.Fun.(*ast.Ident); ok { - return isFocusContainer(fun.Name), fun - } - return false, nil -} - -func (h dotHandler) IsWrapContainer(exp *ast.CallExpr) bool { - if fun, ok := exp.Fun.(*ast.Ident); ok { - return IsWrapContainer(fun.Name) - } - return false -} - -func (h dotHandler) IsFocusSpec(exp ast.Expr) bool { - id, ok := exp.(*ast.Ident) - return ok && id.Name == focusSpec -} - -// nameHandler is used when importing ginkgo without name; i.e. -// import "github.com/onsi/ginkgo" -// -// or with a custom name; e.g. -// import customname "github.com/onsi/ginkgo" -type nameHandler string - -func (h nameHandler) GetFocusContainerName(exp *ast.CallExpr) (bool, *ast.Ident) { - if sel, ok := exp.Fun.(*ast.SelectorExpr); ok { - if id, ok := sel.X.(*ast.Ident); ok && id.Name == string(h) { - return isFocusContainer(sel.Sel.Name), sel.Sel - } - } - return false, nil -} - -func (h nameHandler) IsWrapContainer(exp *ast.CallExpr) bool { - if sel, ok := exp.Fun.(*ast.SelectorExpr); ok { - if id, ok := sel.X.(*ast.Ident); ok && id.Name == string(h) { - return IsWrapContainer(sel.Sel.Name) - } - } - return false - -} - -func (h nameHandler) IsFocusSpec(exp ast.Expr) bool { - if selExp, ok := exp.(*ast.SelectorExpr); ok { - if x, ok := selExp.X.(*ast.Ident); ok && x.Name == string(h) { - return selExp.Sel.Name == focusSpec + continue } } - return false -} - -func isFocusContainer(name string) bool { - switch name { - case "FDescribe", "FContext", "FWhen", "FIt", "FDescribeTable", "FEntry": - return true - } - return false -} - -func IsContainer(name string) bool { - switch name { - case "It", "When", "Context", "Describe", "DescribeTable", "Entry", - "PIt", "PWhen", "PContext", "PDescribe", "PDescribeTable", "PEntry", - "XIt", "XWhen", "XContext", "XDescribe", "XDescribeTable", "XEntry": - return true - } - return isFocusContainer(name) -} - -func IsWrapContainer(name string) bool { - switch name { - case "When", "Context", "Describe", - "FWhen", "FContext", "FDescribe", - "PWhen", "PContext", "PDescribe", - "XWhen", "XContext", "XDescribe": - return true - } - - return false + return nil } diff --git a/internal/ginkgohandler/handler_test.go b/internal/ginkgohandler/handler_test.go index ff66984..5f9f327 100644 --- a/internal/ginkgohandler/handler_test.go +++ b/internal/ginkgohandler/handler_test.go @@ -113,12 +113,12 @@ func TestGetGinkgoHandler_no_ginkgo(t *testing.T) { func TestDotHandler_GetFocusContainerName_happy(t *testing.T) { exp := &ast.CallExpr{ - Fun: ast.NewIdent("FIt"), + Fun: ast.NewIdent(fit), } h := dotHandler{} - isFocus, name := h.GetFocusContainerName(exp) + isFocus, name := h.getFocusContainerName(exp) if !isFocus { t.Error("h.GetFocusContainerName(exp) should return true") @@ -126,25 +126,25 @@ func TestDotHandler_GetFocusContainerName_happy(t *testing.T) { if name == nil { t.Error("should return valid ast.Ident object") - } else if name.Name != "FIt" { + } else if name.Name != fit { t.Error("function name should be 'FIt'") } } func TestDotHandler_GetFocusContainerName_no_focus(t *testing.T) { exp := &ast.CallExpr{ - Fun: ast.NewIdent("It"), + Fun: ast.NewIdent(it), } h := dotHandler{} - isFocus, name := h.GetFocusContainerName(exp) + isFocus, name := h.getFocusContainerName(exp) if isFocus { t.Error("h.GetFocusContainerName(exp) should return false") } if name == nil { t.Error("should return valid ast.Ident object") - } else if name.Name != "It" { + } else if name.Name != it { t.Error("function name should be 'It'") } } @@ -154,13 +154,13 @@ func TestDotHandler_GetFocusContainerName_selector(t *testing.T) { Fun: &ast.SelectorExpr{ Sel: ast.NewIdent("ginkgo"), X: &ast.CallExpr{ - Fun: ast.NewIdent("FIt"), + Fun: ast.NewIdent(fit), }, }, } h := dotHandler{} - isFocus, name := h.GetFocusContainerName(exp) + isFocus, name := h.getFocusContainerName(exp) if isFocus { t.Error("h.GetFocusContainerName(exp) should return false") } @@ -173,20 +173,20 @@ func TestDotHandler_GetFocusContainerName_selector(t *testing.T) { func TestNameHandler_GetFocusContainerName_happy(t *testing.T) { exp := &ast.CallExpr{ Fun: &ast.SelectorExpr{ - Sel: ast.NewIdent("FIt"), + Sel: ast.NewIdent(fit), X: ast.NewIdent("ginkgo"), }, } h := nameHandler("ginkgo") - isFocus, name := h.GetFocusContainerName(exp) + isFocus, name := h.getFocusContainerName(exp) if !isFocus { t.Error("h.GetFocusContainerName(exp) should return true") } if name == nil { t.Error("should return a valid ast.Ident object") - } else if name.Name != "FIt" { + } else if name.Name != fit { t.Error("function name should be 'FIt'") } } @@ -194,31 +194,31 @@ func TestNameHandler_GetFocusContainerName_happy(t *testing.T) { func TestNameHandler_GetFocusContainerName_no_focus(t *testing.T) { exp := &ast.CallExpr{ Fun: &ast.SelectorExpr{ - Sel: ast.NewIdent("It"), + Sel: ast.NewIdent(it), X: ast.NewIdent("ginkgo"), }, } h := nameHandler("ginkgo") - isFocus, name := h.GetFocusContainerName(exp) + isFocus, name := h.getFocusContainerName(exp) if isFocus { t.Error("h.GetFocusContainerName(exp) should return false") } if name == nil { t.Error("should return a valid ast.Ident object") - } else if name.Name != "It" { + } else if name.Name != it { t.Error("function name should be 'FIt'") } } func TestNameHandler_GetFocusContainerName_ident(t *testing.T) { exp := &ast.CallExpr{ - Fun: ast.NewIdent("FIt"), + Fun: ast.NewIdent(fit), } h := nameHandler("ginkgo") - isFocus, name := h.GetFocusContainerName(exp) + isFocus, name := h.getFocusContainerName(exp) if isFocus { t.Error("h.GetFocusContainerName(exp) should return false") } diff --git a/internal/ginkgohandler/handling.go b/internal/ginkgohandler/handling.go new file mode 100644 index 0000000..4b6de57 --- /dev/null +++ b/internal/ginkgohandler/handling.go @@ -0,0 +1,195 @@ +package ginkgohandler + +import ( + "fmt" + "go/ast" + "go/token" + + "golang.org/x/tools/go/analysis" + + "github.com/nunnatsa/ginkgolinter/types" +) + +const ( + linterName = "ginkgo-linter" + focusContainerFound = linterName + ": Focus container found. This is used only for local debug and should not be part of the actual source code. Consider to replace with %q" + focusSpecFound = linterName + ": Focus spec found. This is used only for local debug and should not be part of the actual source code. Consider to remove it" + useBeforeEachTemplate = "use BeforeEach() to assign variable %s" +) + +func handleGinkgoSpecs(expr ast.Expr, config types.Config, pass *analysis.Pass, ginkgoHndlr Handler) bool { + goDeeper := false + if exp, ok := expr.(*ast.CallExpr); ok { + if bool(config.ForbidFocus) && checkFocusContainer(pass, ginkgoHndlr, exp) { + goDeeper = true + } + + if bool(config.ForbidSpecPollution) && checkAssignmentsInContainer(pass, ginkgoHndlr, exp) { + goDeeper = true + } + } + return goDeeper +} + +func checkAssignmentsInContainer(pass *analysis.Pass, ginkgoHndlr Handler, exp *ast.CallExpr) bool { + foundSomething := false + if ginkgoHndlr.isWrapContainer(exp) { + for _, arg := range exp.Args { + if fn, ok := arg.(*ast.FuncLit); ok { + if fn.Body != nil { + if checkAssignments(pass, fn.Body.List) { + foundSomething = true + } + break + } + } + } + } + + return foundSomething +} + +func checkAssignments(pass *analysis.Pass, list []ast.Stmt) bool { + foundSomething := false + for _, stmt := range list { + switch st := stmt.(type) { + case *ast.DeclStmt: + if checkAssignmentDecl(pass, st) { + foundSomething = true + } + + case *ast.AssignStmt: + if checkAssignmentAssign(pass, st) { + foundSomething = true + } + + case *ast.IfStmt: + if checkAssignmentIf(pass, st) { + foundSomething = true + } + } + } + + return foundSomething +} + +func checkAssignmentsValues(pass *analysis.Pass, names []*ast.Ident, values []ast.Expr) bool { + foundSomething := false + for i, val := range values { + if !is[*ast.FuncLit](val) { + reportNoFix(pass, names[i].Pos(), useBeforeEachTemplate, names[i].Name) + foundSomething = true + } + } + + return foundSomething +} + +func checkAssignmentDecl(pass *analysis.Pass, ds *ast.DeclStmt) bool { + foundSomething := false + if gen, ok := ds.Decl.(*ast.GenDecl); ok { + if gen.Tok != token.VAR { + return false + } + for _, spec := range gen.Specs { + if valSpec, ok := spec.(*ast.ValueSpec); ok { + if checkAssignmentsValues(pass, valSpec.Names, valSpec.Values) { + foundSomething = true + } + } + } + } + + return foundSomething +} + +func checkAssignmentAssign(pass *analysis.Pass, as *ast.AssignStmt) bool { + foundSomething := false + for i, val := range as.Rhs { + if !is[*ast.FuncLit](val) { + if id, isIdent := as.Lhs[i].(*ast.Ident); isIdent && id.Name != "_" { + reportNoFix(pass, id.Pos(), useBeforeEachTemplate, id.Name) + foundSomething = true + } + } + } + return foundSomething +} + +func checkAssignmentIf(pass *analysis.Pass, is *ast.IfStmt) bool { + foundSomething := false + + if is.Body != nil { + if checkAssignments(pass, is.Body.List) { + foundSomething = true + } + } + if is.Else != nil { + if block, isBlock := is.Else.(*ast.BlockStmt); isBlock { + if checkAssignments(pass, block.List) { + foundSomething = true + } + } + } + + return foundSomething +} + +func checkFocusContainer(pass *analysis.Pass, handler Handler, exp *ast.CallExpr) bool { + foundFocus := false + isFocus, id := handler.getFocusContainerName(exp) + if isFocus { + reportNewName(pass, id, id.Name[1:], id.Name) + foundFocus = true + } + + if id != nil && isContainer(id.Name) { + for _, arg := range exp.Args { + if handler.isFocusSpec(arg) { + reportNoFix(pass, arg.Pos(), focusSpecFound) + foundFocus = true + } else if callExp, ok := arg.(*ast.CallExpr); ok { + if checkFocusContainer(pass, handler, callExp) { // handle table entries + foundFocus = true + } + } + } + } + + return foundFocus +} + +func reportNewName(pass *analysis.Pass, id *ast.Ident, newName string, oldExpr string) { + pass.Report(analysis.Diagnostic{ + Pos: id.Pos(), + Message: fmt.Sprintf(focusContainerFound, newName), + SuggestedFixes: []analysis.SuggestedFix{ + { + Message: fmt.Sprintf("should replace %s with %s", oldExpr, newName), + TextEdits: []analysis.TextEdit{ + { + Pos: id.Pos(), + End: id.End(), + NewText: []byte(newName), + }, + }, + }, + }, + }) +} + +func reportNoFix(pass *analysis.Pass, pos token.Pos, message string, args ...any) { + if len(args) > 0 { + message = fmt.Sprintf(message, args...) + } + + pass.Report(analysis.Diagnostic{ + Pos: pos, + Message: message, + }) +} + +func is[T any](x any) bool { + _, matchType := x.(T) + return matchType +} diff --git a/internal/ginkgohandler/namehandler.go b/internal/ginkgohandler/namehandler.go new file mode 100644 index 0000000..2ef9fe7 --- /dev/null +++ b/internal/ginkgohandler/namehandler.go @@ -0,0 +1,49 @@ +package ginkgohandler + +import ( + "go/ast" + + "golang.org/x/tools/go/analysis" + + "github.com/nunnatsa/ginkgolinter/types" +) + +// nameHandler is used when importing ginkgo without name; i.e. +// import "github.com/onsi/ginkgo" +// +// or with a custom name; e.g. +// import customname "github.com/onsi/ginkgo" +type nameHandler string + +func (h nameHandler) HandleGinkgoSpecs(expr ast.Expr, config types.Config, pass *analysis.Pass) bool { + return handleGinkgoSpecs(expr, config, pass, h) +} + +func (h nameHandler) getFocusContainerName(exp *ast.CallExpr) (bool, *ast.Ident) { + if sel, ok := exp.Fun.(*ast.SelectorExpr); ok { + if id, ok := sel.X.(*ast.Ident); ok && id.Name == string(h) { + return isFocusContainer(sel.Sel.Name), sel.Sel + } + } + return false, nil +} + +func (h nameHandler) isWrapContainer(exp *ast.CallExpr) bool { + if sel, ok := exp.Fun.(*ast.SelectorExpr); ok { + if id, ok := sel.X.(*ast.Ident); ok && id.Name == string(h) { + return isWrapContainer(sel.Sel.Name) + } + } + return false + +} + +func (h nameHandler) isFocusSpec(exp ast.Expr) bool { + if selExp, ok := exp.(*ast.SelectorExpr); ok { + if x, ok := selExp.X.(*ast.Ident); ok && x.Name == string(h) { + return selExp.Sel.Name == focusSpec + } + } + + return false +} diff --git a/linter/ginkgo_linter.go b/linter/ginkgo_linter.go index 3ece993..838f893 100644 --- a/linter/ginkgo_linter.go +++ b/linter/ginkgo_linter.go @@ -1,9 +1,7 @@ package linter import ( - "fmt" "go/ast" - "go/token" "golang.org/x/tools/go/analysis" "github.com/nunnatsa/ginkgolinter/internal/expression" @@ -19,13 +17,6 @@ import ( // // For more details, look at the README.md file -const ( - linterName = "ginkgo-linter" - focusContainerFound = linterName + ": Focus container found. This is used only for local debug and should not be part of the actual source code. Consider to replace with %q" - focusSpecFound = linterName + ": Focus spec found. This is used only for local debug and should not be part of the actual source code. Consider to remove it" - useBeforeEachTemplate = "use BeforeEach() to assign variable %s" -) - type GinkgoLinter struct { config *types.Config } @@ -59,7 +50,7 @@ func (l *GinkgoLinter) Run(pass *analysis.Pass) (any, error) { spec, ok := n.(*ast.ValueSpec) if ok { for _, val := range spec.Values { - goDeeper = handleGinkgoSpecs(val, fileConfig, pass, ginkgoHndlr) || goDeeper + goDeeper = ginkgoHndlr.HandleGinkgoSpecs(val, fileConfig, pass) || goDeeper } } if goDeeper { @@ -84,7 +75,7 @@ func (l *GinkgoLinter) Run(pass *analysis.Pass) (any, error) { } if ginkgoHndlr != nil { - if handleGinkgoSpecs(assertionExp, config, pass, ginkgoHndlr) { + if ginkgoHndlr.HandleGinkgoSpecs(assertionExp, config, pass) { return true } } @@ -106,121 +97,6 @@ func (l *GinkgoLinter) Run(pass *analysis.Pass) (any, error) { return nil, nil } -func handleGinkgoSpecs(expr ast.Expr, config types.Config, pass *analysis.Pass, ginkgoHndlr ginkgohandler.Handler) bool { - goDeeper := false - if exp, ok := expr.(*ast.CallExpr); ok { - if bool(config.ForbidFocus) && checkFocusContainer(pass, ginkgoHndlr, exp) { - goDeeper = true - } - - if bool(config.ForbidSpecPollution) && checkAssignmentsInContainer(pass, ginkgoHndlr, exp) { - goDeeper = true - } - } - return goDeeper -} - -func checkAssignmentsInContainer(pass *analysis.Pass, ginkgoHndlr ginkgohandler.Handler, exp *ast.CallExpr) bool { - foundSomething := false - if ginkgoHndlr.IsWrapContainer(exp) { - for _, arg := range exp.Args { - if fn, ok := arg.(*ast.FuncLit); ok { - if fn.Body != nil { - if checkAssignments(pass, fn.Body.List) { - foundSomething = true - } - break - } - } - } - } - - return foundSomething -} - -func checkAssignments(pass *analysis.Pass, list []ast.Stmt) bool { - foundSomething := false - for _, stmt := range list { - switch st := stmt.(type) { - case *ast.DeclStmt: - if gen, ok := st.Decl.(*ast.GenDecl); ok { - if gen.Tok != token.VAR { - continue - } - for _, spec := range gen.Specs { - if valSpec, ok := spec.(*ast.ValueSpec); ok { - if checkAssignmentsValues(pass, valSpec.Names, valSpec.Values) { - foundSomething = true - } - } - } - } - - case *ast.AssignStmt: - for i, val := range st.Rhs { - if !is[*ast.FuncLit](val) { - if id, isIdent := st.Lhs[i].(*ast.Ident); isIdent && id.Name != "_" { - reportNoFix(pass, id.Pos(), useBeforeEachTemplate, id.Name) - foundSomething = true - } - } - } - - case *ast.IfStmt: - if st.Body != nil { - if checkAssignments(pass, st.Body.List) { - foundSomething = true - } - } - if st.Else != nil { - if block, isBlock := st.Else.(*ast.BlockStmt); isBlock { - if checkAssignments(pass, block.List) { - foundSomething = true - } - } - } - } - } - - return foundSomething -} - -func checkAssignmentsValues(pass *analysis.Pass, names []*ast.Ident, values []ast.Expr) bool { - foundSomething := false - for i, val := range values { - if !is[*ast.FuncLit](val) { - reportNoFix(pass, names[i].Pos(), useBeforeEachTemplate, names[i].Name) - foundSomething = true - } - } - - return foundSomething -} - -func checkFocusContainer(pass *analysis.Pass, ginkgoHndlr ginkgohandler.Handler, exp *ast.CallExpr) bool { - foundFocus := false - isFocus, id := ginkgoHndlr.GetFocusContainerName(exp) - if isFocus { - reportNewName(pass, id, id.Name[1:], focusContainerFound, id.Name) - foundFocus = true - } - - if id != nil && ginkgohandler.IsContainer(id.Name) { - for _, arg := range exp.Args { - if ginkgoHndlr.IsFocusSpec(arg) { - reportNoFix(pass, arg.Pos(), focusSpecFound) - foundFocus = true - } else if callExp, ok := arg.(*ast.CallExpr); ok { - if checkFocusContainer(pass, ginkgoHndlr, callExp) { // handle table entries - foundFocus = true - } - } - } - } - - return foundFocus -} - func checkGomegaExpression(gexp *expression.GomegaExpression, config types.Config, reportBuilder *reports.Builder, pass *analysis.Pass) bool { goNested := false if rules.GetMissingAssertionRule().Apply(gexp, config, reportBuilder) { @@ -242,41 +118,6 @@ func checkGomegaExpression(gexp *expression.GomegaExpression, config types.Confi return goNested } -func reportNewName(pass *analysis.Pass, id *ast.Ident, newName string, messageTemplate, oldExpr string) { - pass.Report(analysis.Diagnostic{ - Pos: id.Pos(), - Message: fmt.Sprintf(messageTemplate, newName), - SuggestedFixes: []analysis.SuggestedFix{ - { - Message: fmt.Sprintf("should replace %s with %s", oldExpr, newName), - TextEdits: []analysis.TextEdit{ - { - Pos: id.Pos(), - End: id.End(), - NewText: []byte(newName), - }, - }, - }, - }, - }) -} - -func reportNoFix(pass *analysis.Pass, pos token.Pos, message string, args ...any) { - if len(args) > 0 { - message = fmt.Sprintf(message, args...) - } - - pass.Report(analysis.Diagnostic{ - Pos: pos, - Message: message, - }) -} - -func is[T any](x any) bool { - _, matchType := x.(T) - return matchType -} - func getTimePkg(file *ast.File) string { timePkg := "time" for _, imp := range file.Imports {