aboutsummaryrefslogtreecommitdiffstats
path: root/vendor/github.com/blizzy78/varnamelen/varnamelen.go
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/github.com/blizzy78/varnamelen/varnamelen.go')
-rw-r--r--vendor/github.com/blizzy78/varnamelen/varnamelen.go891
1 files changed, 891 insertions, 0 deletions
diff --git a/vendor/github.com/blizzy78/varnamelen/varnamelen.go b/vendor/github.com/blizzy78/varnamelen/varnamelen.go
new file mode 100644
index 000000000..a5b960311
--- /dev/null
+++ b/vendor/github.com/blizzy78/varnamelen/varnamelen.go
@@ -0,0 +1,891 @@
+package varnamelen
+
+import (
+ "go/ast"
+ "go/token"
+ "go/types"
+ "sort"
+ "strings"
+
+ "golang.org/x/tools/go/analysis"
+ "golang.org/x/tools/go/analysis/passes/inspect"
+ "golang.org/x/tools/go/ast/inspector"
+)
+
+// varNameLen is an analyzer that checks that the length of a variable's name matches its usage scope.
+// It will create a report for a variable's assignment if that variable has a short name, but its
+// usage scope is not considered "small."
+type varNameLen struct {
+ // maxDistance is the longest distance, in source lines, that is being considered a "small scope."
+ maxDistance int
+
+ // minNameLength is the minimum length of a variable's name that is considered "long."
+ minNameLength int
+
+ // ignoreNames is an optional list of variable names that should be ignored completely.
+ ignoreNames stringsValue
+
+ // checkReceiver determines whether method receivers should be checked.
+ checkReceiver bool
+
+ // checkReturn determines whether named return values should be checked.
+ checkReturn bool
+
+ // ignoreTypeAssertOk determines whether "ok" variables that hold the bool return value of a type assertion should be ignored.
+ ignoreTypeAssertOk bool
+
+ // ignoreMapIndexOk determines whether "ok" variables that hold the bool return value of a map index should be ignored.
+ ignoreMapIndexOk bool
+
+ // ignoreChannelReceiveOk determines whether "ok" variables that hold the bool return value of a channel receive should be ignored.
+ ignoreChannelReceiveOk bool
+
+ // ignoreDeclarations is an optional list of variable declarations that should be ignored completely.
+ ignoreDeclarations declarationsValue
+
+ // checkTypeParameters determines whether type parameters should be checked.
+ checkTypeParameters bool
+}
+
+// variable represents a declared variable.
+type variable struct {
+ // name is the name of the variable.
+ name string
+
+ // constant is true if the variable is actually a constant.
+ constant bool
+
+ // typ is the type of the variable.
+ typ string
+
+ // assign is the assign statement that declares the variable.
+ assign *ast.AssignStmt
+
+ // valueSpec is the value specification that declares the variable.
+ valueSpec *ast.ValueSpec
+}
+
+// parameter represents a declared function or method parameter.
+type parameter struct {
+ // name is the name of the parameter.
+ name string
+
+ // typ is the type of the parameter.
+ typ string
+
+ // field is the declaration of the parameter.
+ field *ast.Field
+}
+
+// typeParam represents a declared type parameter.
+type typeParam struct {
+ // name is the name of the type parameter.
+ name string
+
+ // typ is the type of the type parameter.
+ typ string
+
+ // field is the field that declares the type parameter.
+ field *ast.Field
+}
+
+// declaration is a variable declaration.
+type declaration struct {
+ // name is the name of the variable.
+ name string
+
+ // constant is true if the variable is actually a constant.
+ constant bool
+
+ // typ is the type of the variable. Not used for constants.
+ typ string
+}
+
+// importDeclaration is an import declaration.
+type importDeclaration struct {
+ // name is the short name or alias for the imported package. This is either the package's default name,
+ // or the alias specified in the import statement.
+ // Not used if self is true.
+ name string
+
+ // path is the full path to the imported package.
+ path string
+
+ // self is true when this is an implicit import declaration for the current package.
+ self bool
+}
+
+const (
+ // defaultMaxDistance is the default value for the maximum distance between the declaration of a variable and its usage
+ // that is considered a "small scope."
+ defaultMaxDistance = 5
+
+ // defaultMinNameLength is the default value for the minimum length of a variable's name that is considered "long."
+ defaultMinNameLength = 3
+)
+
+// conventionalDecls is a list of conventional variable declarations.
+var conventionalDecls = []declaration{
+ parseDeclaration("ctx context.Context"),
+
+ parseDeclaration("b *testing.B"),
+ parseDeclaration("f *testing.F"),
+ parseDeclaration("m *testing.M"),
+ parseDeclaration("pb *testing.PB"),
+ parseDeclaration("t *testing.T"),
+ parseDeclaration("tb testing.TB"),
+}
+
+// NewAnalyzer returns a new analyzer.
+func NewAnalyzer() *analysis.Analyzer {
+ vnl := varNameLen{
+ maxDistance: defaultMaxDistance,
+ minNameLength: defaultMinNameLength,
+ ignoreNames: stringsValue{},
+ ignoreDeclarations: declarationsValue{},
+ }
+
+ analyzer := analysis.Analyzer{
+ Name: "varnamelen",
+ Doc: "checks that the length of a variable's name matches its scope\n\n" +
+ "A variable with a short name can be hard to use if the variable is used\n" +
+ "over a longer span of lines of code. A longer variable name may be easier\n" +
+ "to comprehend.",
+
+ Run: func(pass *analysis.Pass) (interface{}, error) {
+ (&vnl).run(pass)
+ return nil, nil
+ },
+
+ Requires: []*analysis.Analyzer{
+ inspect.Analyzer,
+ },
+ }
+
+ analyzer.Flags.IntVar(&vnl.maxDistance, "maxDistance", defaultMaxDistance, "maximum number of lines of variable usage scope considered 'short'")
+ analyzer.Flags.IntVar(&vnl.minNameLength, "minNameLength", defaultMinNameLength, "minimum length of variable name considered 'long'")
+ analyzer.Flags.Var(&vnl.ignoreNames, "ignoreNames", "comma-separated list of ignored variable names")
+ analyzer.Flags.BoolVar(&vnl.checkReceiver, "checkReceiver", false, "check method receivers")
+ analyzer.Flags.BoolVar(&vnl.checkReturn, "checkReturn", false, "check named return values")
+ analyzer.Flags.BoolVar(&vnl.ignoreTypeAssertOk, "ignoreTypeAssertOk", false, "ignore 'ok' variables that hold the bool return value of a type assertion")
+ analyzer.Flags.BoolVar(&vnl.ignoreMapIndexOk, "ignoreMapIndexOk", false, "ignore 'ok' variables that hold the bool return value of a map index")
+ analyzer.Flags.BoolVar(&vnl.ignoreChannelReceiveOk, "ignoreChanRecvOk", false, "ignore 'ok' variables that hold the bool return value of a channel receive")
+ analyzer.Flags.Var(&vnl.ignoreDeclarations, "ignoreDecls", "comma-separated list of ignored variable declarations")
+ analyzer.Flags.BoolVar(&vnl.checkTypeParameters, "checkTypeParam", false, "check type parameters")
+
+ return &analyzer
+}
+
+// Run applies v to a package, according to pass.
+func (v *varNameLen) run(pass *analysis.Pass) {
+ varToDist, paramToDist, returnToDist, typeParamToDist := v.distances(pass)
+
+ v.checkVariables(pass, varToDist)
+ v.checkParams(pass, paramToDist)
+ v.checkReturns(pass, returnToDist)
+ v.checkTypeParams(pass, typeParamToDist)
+}
+
+// checkVariables applies v to variables in varToDist.
+func (v *varNameLen) checkVariables(pass *analysis.Pass, varToDist map[variable]int) { //nolint:gocognit // it's not that complicated
+ for variable, dist := range varToDist {
+ if v.ignoreNames.contains(variable.name) {
+ continue
+ }
+
+ if v.ignoreDeclarations.matchVariable(variable) {
+ continue
+ }
+
+ if v.checkNameAndDistance(variable.name, dist) {
+ continue
+ }
+
+ if v.checkTypeAssertOk(variable) {
+ continue
+ }
+
+ if v.checkMapIndexOk(variable) {
+ continue
+ }
+
+ if v.checkChannelReceiveOk(variable) {
+ continue
+ }
+
+ if variable.isConventional() {
+ continue
+ }
+
+ if variable.assign != nil {
+ pass.Reportf(variable.assign.Pos(), "%s name '%s' is too short for the scope of its usage", variable.kindName(), variable.name)
+ continue
+ }
+
+ pass.Reportf(variable.valueSpec.Pos(), "%s name '%s' is too short for the scope of its usage", variable.kindName(), variable.name)
+ }
+}
+
+// checkParams applies v to parameters in paramToDist.
+func (v *varNameLen) checkParams(pass *analysis.Pass, paramToDist map[parameter]int) {
+ for param, dist := range paramToDist {
+ if v.ignoreNames.contains(param.name) {
+ continue
+ }
+
+ if v.ignoreDeclarations.matchParameter(param) {
+ continue
+ }
+
+ if v.checkNameAndDistance(param.name, dist) {
+ continue
+ }
+
+ if param.isConventional() {
+ continue
+ }
+
+ pass.Reportf(param.field.Pos(), "parameter name '%s' is too short for the scope of its usage", param.name)
+ }
+}
+
+// checkReturns applies v to named return values in returnToDist.
+func (v *varNameLen) checkReturns(pass *analysis.Pass, returnToDist map[parameter]int) {
+ for returnValue, dist := range returnToDist {
+ if v.ignoreNames.contains(returnValue.name) {
+ continue
+ }
+
+ if v.ignoreDeclarations.matchParameter(returnValue) {
+ continue
+ }
+
+ if v.checkNameAndDistance(returnValue.name, dist) {
+ continue
+ }
+
+ pass.Reportf(returnValue.field.Pos(), "return value name '%s' is too short for the scope of its usage", returnValue.name)
+ }
+}
+
+// checkTypeParams applies v to type parameters in paramToDist.
+func (v *varNameLen) checkTypeParams(pass *analysis.Pass, paramToDist map[typeParam]int) {
+ for param, dist := range paramToDist {
+ if v.ignoreNames.contains(param.name) {
+ continue
+ }
+
+ if v.ignoreDeclarations.matchTypeParameter(param) {
+ continue
+ }
+
+ if v.checkNameAndDistance(param.name, dist) {
+ continue
+ }
+
+ pass.Reportf(param.field.Pos(), "type parameter name '%s' is too short for the scope of its usage", param.name)
+ }
+}
+
+// checkNameAndDistance returns true if name or dist are considered "short".
+func (v *varNameLen) checkNameAndDistance(name string, dist int) bool {
+ if len(name) >= v.minNameLength {
+ return true
+ }
+
+ if dist <= v.maxDistance {
+ return true
+ }
+
+ return false
+}
+
+// checkTypeAssertOk returns true if "ok" variables that hold the bool return value of a type assertion
+// should be ignored, and if vari is such a variable.
+func (v *varNameLen) checkTypeAssertOk(vari variable) bool {
+ return v.ignoreTypeAssertOk && vari.isTypeAssertOk()
+}
+
+// checkMapIndexOk returns true if "ok" variables that hold the bool return value of a map index
+// should be ignored, and if vari is such a variable.
+func (v *varNameLen) checkMapIndexOk(vari variable) bool {
+ return v.ignoreMapIndexOk && vari.isMapIndexOk()
+}
+
+// checkChannelReceiveOk returns true if "ok" variables that hold the bool return value of a channel receive
+// should be ignored, and if vari is such a variable.
+func (v *varNameLen) checkChannelReceiveOk(vari variable) bool {
+ return v.ignoreChannelReceiveOk && vari.isChannelReceiveOk()
+}
+
+// distances returns maps of variables, parameters, return values, and type parameters mapping to their longest usage distances.
+func (v *varNameLen) distances(pass *analysis.Pass) (map[variable]int, map[parameter]int, map[parameter]int, map[typeParam]int) {
+ assignIdents, valueSpecIdents, paramIdents, returnIdents, typeParamIdents, imports, switches := v.identsAndImports(pass)
+
+ varToDist := map[variable]int{}
+
+ for _, ident := range assignIdents {
+ assign := ident.Obj.Decl.(*ast.AssignStmt) //nolint:forcetypeassert // check is done in identsAndImports
+
+ var typ string
+ if isTypeSwitchAssign(assign, switches) {
+ typ = "<type-switched>"
+ } else {
+ typ = shortTypeName(pass.TypesInfo.TypeOf(ident), imports)
+ }
+
+ variable := variable{
+ name: ident.Name,
+ typ: typ,
+ assign: assign,
+ }
+
+ useLine := pass.Fset.Position(ident.NamePos).Line
+ declLine := pass.Fset.Position(assign.Pos()).Line
+ varToDist[variable] = useLine - declLine
+ }
+
+ for _, ident := range valueSpecIdents {
+ valueSpec := ident.Obj.Decl.(*ast.ValueSpec) //nolint:forcetypeassert // check is done in identsAndImports
+
+ variable := variable{
+ name: ident.Name,
+ constant: ident.Obj.Kind == ast.Con,
+ typ: shortTypeName(pass.TypesInfo.TypeOf(ident), imports),
+ valueSpec: valueSpec,
+ }
+
+ useLine := pass.Fset.Position(ident.NamePos).Line
+ declLine := pass.Fset.Position(valueSpec.Pos()).Line
+ varToDist[variable] = useLine - declLine
+ }
+
+ paramToDist := map[parameter]int{}
+
+ for _, ident := range paramIdents {
+ field := ident.Obj.Decl.(*ast.Field) //nolint:forcetypeassert // check is done in identsAndImports
+
+ param := parameter{
+ name: ident.Name,
+ typ: shortTypeName(pass.TypesInfo.TypeOf(field.Type), imports),
+ field: field,
+ }
+
+ useLine := pass.Fset.Position(ident.NamePos).Line
+ declLine := pass.Fset.Position(field.Pos()).Line
+ paramToDist[param] = useLine - declLine
+ }
+
+ returnToDist := map[parameter]int{}
+
+ for _, ident := range returnIdents {
+ field := ident.Obj.Decl.(*ast.Field) //nolint:forcetypeassert // check is done in identsAndImports
+
+ param := parameter{
+ name: ident.Name,
+ typ: shortTypeName(pass.TypesInfo.TypeOf(ident), imports),
+ field: field,
+ }
+
+ useLine := pass.Fset.Position(ident.NamePos).Line
+ declLine := pass.Fset.Position(field.Pos()).Line
+ returnToDist[param] = useLine - declLine
+ }
+
+ typeParamToDist := map[typeParam]int{}
+
+ for _, ident := range typeParamIdents {
+ field := ident.Obj.Decl.(*ast.Field) //nolint:forcetypeassert // check is done in identsAndImports
+
+ param := typeParam{
+ name: ident.Name,
+ typ: shortTypeName(pass.TypesInfo.TypeOf(field.Type), imports),
+ field: field,
+ }
+
+ useLine := pass.Fset.Position(ident.NamePos).Line
+ declLine := pass.Fset.Position(field.Pos()).Line
+ typeParamToDist[param] = useLine - declLine
+ }
+
+ return varToDist, paramToDist, returnToDist, typeParamToDist
+}
+
+// identsAndImports returns Idents referencing assign statements, value specifications, parameters,
+// return values, and type parameters, respectively, as well as import declarations, and type switch statements.
+func (v *varNameLen) identsAndImports(pass *analysis.Pass) ([]*ast.Ident, []*ast.Ident, []*ast.Ident, []*ast.Ident, //nolint:gocognit,cyclop // this is complex stuff
+ []*ast.Ident, []importDeclaration, []*ast.TypeSwitchStmt) {
+ inspector := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector) //nolint:forcetypeassert // inspect.Analyzer always returns *inspector.Inspector
+
+ filter := []ast.Node{
+ (*ast.ImportSpec)(nil),
+ (*ast.FuncDecl)(nil),
+ (*ast.FuncLit)(nil),
+ (*ast.CompositeLit)(nil),
+ (*ast.TypeSwitchStmt)(nil),
+ (*ast.Ident)(nil),
+ }
+
+ assignIdents := []*ast.Ident{}
+ valueSpecIdents := []*ast.Ident{}
+ paramIdents := []*ast.Ident{}
+ returnIdents := []*ast.Ident{}
+ typeParamIdents := []*ast.Ident{}
+ imports := []importDeclaration{}
+ switches := []*ast.TypeSwitchStmt{}
+
+ funcs := []*ast.FuncDecl{}
+ methods := []*ast.FuncDecl{}
+ funcLits := []*ast.FuncLit{}
+ compositeLits := []*ast.CompositeLit{}
+
+ inspector.Preorder(filter, func(node ast.Node) {
+ switch node2 := node.(type) {
+ case *ast.ImportSpec:
+ decl, ok := importSpecToDecl(node2, pass.Pkg.Imports())
+ if !ok {
+ return
+ }
+
+ imports = append(imports, decl)
+
+ case *ast.FuncDecl:
+ funcs = append(funcs, node2)
+
+ if node2.Recv == nil {
+ return
+ }
+
+ methods = append(methods, node2)
+
+ case *ast.FuncLit:
+ funcLits = append(funcLits, node2)
+
+ case *ast.CompositeLit:
+ compositeLits = append(compositeLits, node2)
+
+ case *ast.TypeSwitchStmt:
+ switches = append(switches, node2)
+
+ case *ast.Ident:
+ if node2.Obj == nil {
+ return
+ }
+
+ if isCompositeLitKey(node2, compositeLits) {
+ return
+ }
+
+ switch objDecl := node2.Obj.Decl.(type) {
+ case *ast.AssignStmt:
+ assignIdents = append(assignIdents, node2)
+
+ case *ast.ValueSpec:
+ valueSpecIdents = append(valueSpecIdents, node2)
+
+ case *ast.Field:
+ switch {
+ case isReceiver(objDecl, methods):
+ if !v.checkReceiver {
+ return
+ }
+
+ paramIdents = append(paramIdents, node2)
+
+ case isReturn(objDecl, funcs, funcLits):
+ if !v.checkReturn {
+ return
+ }
+
+ returnIdents = append(returnIdents, node2)
+
+ case isTypeParam(objDecl, funcs, funcLits):
+ if !v.checkTypeParameters {
+ return
+ }
+
+ typeParamIdents = append(typeParamIdents, node2)
+
+ case isParam(objDecl, funcs, funcLits, methods):
+ paramIdents = append(paramIdents, node2)
+ }
+ }
+ }
+ })
+
+ imports = append(imports, importDeclaration{
+ path: pass.Pkg.Path(),
+ self: true,
+ })
+
+ sort.Slice(imports, func(a, b int) bool {
+ // reversed: longest path first
+ return len(imports[a].path) > len(imports[b].path)
+ })
+
+ return assignIdents, valueSpecIdents, paramIdents, returnIdents, typeParamIdents, imports, switches
+}
+
+func importSpecToDecl(spec *ast.ImportSpec, imports []*types.Package) (importDeclaration, bool) {
+ path := strings.TrimSuffix(strings.TrimPrefix(spec.Path.Value, "\""), "\"")
+
+ if spec.Name != nil {
+ return importDeclaration{
+ name: spec.Name.Name,
+ path: path,
+ }, true
+ }
+
+ for _, imp := range imports {
+ if imp.Path() == path {
+ return importDeclaration{
+ name: imp.Name(),
+ path: path,
+ }, true
+ }
+ }
+
+ return importDeclaration{}, false
+}
+
+// isTypeAssertOk returns true if v is an "ok" variable that holds the bool return value of a type assertion.
+func (v variable) isTypeAssertOk() bool {
+ if v.name != "ok" {
+ return false
+ }
+
+ if v.assign == nil {
+ return false
+ }
+
+ if len(v.assign.Lhs) != 2 {
+ return false
+ }
+
+ ident, ok := v.assign.Lhs[1].(*ast.Ident)
+ if !ok {
+ return false
+ }
+
+ if ident.Name != "ok" {
+ return false
+ }
+
+ if len(v.assign.Rhs) != 1 {
+ return false
+ }
+
+ if _, ok := v.assign.Rhs[0].(*ast.TypeAssertExpr); !ok {
+ return false
+ }
+
+ return true
+}
+
+// isMapIndexOk returns true if v is an "ok" variable that holds the bool return value of a map index.
+func (v variable) isMapIndexOk() bool {
+ if v.name != "ok" {
+ return false
+ }
+
+ if v.assign == nil {
+ return false
+ }
+
+ if len(v.assign.Lhs) != 2 {
+ return false
+ }
+
+ ident, ok := v.assign.Lhs[1].(*ast.Ident)
+ if !ok {
+ return false
+ }
+
+ if ident.Name != "ok" {
+ return false
+ }
+
+ if len(v.assign.Rhs) != 1 {
+ return false
+ }
+
+ if _, ok := v.assign.Rhs[0].(*ast.IndexExpr); !ok {
+ return false
+ }
+
+ return true
+}
+
+// isChannelReceiveOk returns true if v is an "ok" variable that holds the bool return value of a channel receive.
+func (v variable) isChannelReceiveOk() bool {
+ if v.name != "ok" {
+ return false
+ }
+
+ if v.assign == nil {
+ return false
+ }
+
+ if len(v.assign.Lhs) != 2 {
+ return false
+ }
+
+ ident, ok := v.assign.Lhs[1].(*ast.Ident)
+ if !ok {
+ return false
+ }
+
+ if ident.Name != "ok" {
+ return false
+ }
+
+ if len(v.assign.Rhs) != 1 {
+ return false
+ }
+
+ unary, ok := v.assign.Rhs[0].(*ast.UnaryExpr)
+ if !ok {
+ return false
+ }
+
+ if unary.Op != token.ARROW {
+ return false
+ }
+
+ return true
+}
+
+// isConventional returns true if v matches a conventional Go variable/parameter name and type,
+// such as "ctx context.Context" or "t *testing.T".
+func (v variable) isConventional() bool {
+ for _, decl := range conventionalDecls {
+ if v.match(decl) {
+ return true
+ }
+ }
+
+ return false
+}
+
+// match returns true if v matches decl.
+func (v variable) match(decl declaration) bool {
+ if v.name != decl.name {
+ return false
+ }
+
+ if v.constant != decl.constant {
+ return false
+ }
+
+ if v.constant {
+ return true
+ }
+
+ if v.typ == "" {
+ return false
+ }
+
+ return decl.matchType(v.typ)
+}
+
+// kindName returns "constant" if v.constant==true, else "variable".
+func (v variable) kindName() string {
+ if v.constant {
+ return "constant"
+ }
+
+ return "variable"
+}
+
+// isReceiver returns true if field is a receiver parameter of any of the given methods.
+func isReceiver(field *ast.Field, methods []*ast.FuncDecl) bool {
+ for _, m := range methods {
+ for _, recv := range m.Recv.List {
+ if recv == field {
+ return true
+ }
+ }
+ }
+
+ return false
+}
+
+// isReturn returns true if field is a return value of any of the given funcs.
+func isReturn(field *ast.Field, funcs []*ast.FuncDecl, funcLits []*ast.FuncLit) bool { //nolint:gocognit // it's not that complicated
+ for _, f := range funcs {
+ if f.Type.Results == nil {
+ continue
+ }
+
+ for _, r := range f.Type.Results.List {
+ if r == field {
+ return true
+ }
+ }
+ }
+
+ for _, f := range funcLits {
+ if f.Type.Results == nil {
+ continue
+ }
+
+ for _, r := range f.Type.Results.List {
+ if r == field {
+ return true
+ }
+ }
+ }
+
+ return false
+}
+
+// isParam returns true if field is a parameter of any of the given funcs.
+func isParam(field *ast.Field, funcs []*ast.FuncDecl, funcLits []*ast.FuncLit, methods []*ast.FuncDecl) bool { //nolint:gocognit,cyclop // it's not that complicated
+ for _, f := range funcs {
+ if f.Type.Params == nil {
+ continue
+ }
+
+ for _, p := range f.Type.Params.List {
+ if p == field {
+ return true
+ }
+ }
+ }
+
+ for _, f := range funcLits {
+ if f.Type.Params == nil {
+ continue
+ }
+
+ for _, p := range f.Type.Params.List {
+ if p == field {
+ return true
+ }
+ }
+ }
+
+ for _, m := range methods {
+ if m.Type.Params == nil {
+ continue
+ }
+
+ for _, p := range m.Type.Params.List {
+ if p == field {
+ return true
+ }
+ }
+ }
+
+ return false
+}
+
+// isCompositeLitKey returns true if ident is a key of any of the given composite literals.
+func isCompositeLitKey(ident *ast.Ident, compositeLits []*ast.CompositeLit) bool {
+ for _, cl := range compositeLits {
+ if _, ok := cl.Type.(*ast.MapType); ok {
+ continue
+ }
+
+ for _, kvExpr := range cl.Elts {
+ kv, ok := kvExpr.(*ast.KeyValueExpr)
+ if !ok {
+ continue
+ }
+
+ if kv.Key == ident {
+ return true
+ }
+ }
+ }
+
+ return false
+}
+
+// isTypeSwitchAssign returns true if assign is an assign statement of any of the given type switch statements.
+func isTypeSwitchAssign(assign *ast.AssignStmt, switches []*ast.TypeSwitchStmt) bool {
+ for _, s := range switches {
+ if s.Assign == assign {
+ return true
+ }
+ }
+
+ return false
+}
+
+// isConventional returns true if v matches a conventional Go variable/parameter name and type,
+// such as "ctx context.Context" or "t *testing.T".
+func (p parameter) isConventional() bool {
+ for _, decl := range conventionalDecls {
+ if p.match(decl) {
+ return true
+ }
+ }
+
+ return false
+}
+
+// match returns whether p matches decl.
+func (p parameter) match(decl declaration) bool {
+ if p.name != decl.name {
+ return false
+ }
+
+ return decl.matchType(p.typ)
+}
+
+// match returns whether p matches decl.
+func (p typeParam) match(decl declaration) bool {
+ if p.name != decl.name {
+ return false
+ }
+
+ return decl.matchType(p.typ)
+}
+
+// parseDeclaration parses and returns a variable declaration parsed from decl.
+func parseDeclaration(decl string) declaration {
+ if strings.HasPrefix(decl, "const ") {
+ return declaration{
+ name: strings.TrimPrefix(decl, "const "),
+ constant: true,
+ }
+ }
+
+ parts := strings.SplitN(decl, " ", 2)
+
+ return declaration{
+ name: parts[0],
+ typ: parts[1],
+ }
+}
+
+// matchType returns true if typ matches d.typ.
+func (d declaration) matchType(typ string) bool {
+ return d.typ == typ
+}
+
+// shortTypeName returns the short name of typ, with respect to imports.
+// For example, if package github.com/matryer/is is imported with alias "x",
+// and typ represents []*github.com/matryer/is.I, shortTypeName will return "[]*x.I".
+// For imports without aliases, the package's default name will be used.
+func shortTypeName(typ types.Type, imports []importDeclaration) string {
+ if typ == nil {
+ return ""
+ }
+
+ typStr := typ.String()
+
+ for _, imp := range imports {
+ prefix := imp.path + "."
+
+ replace := ""
+ if !imp.self {
+ replace = imp.name + "."
+ }
+
+ typStr = strings.ReplaceAll(typStr, prefix, replace)
+ }
+
+ return typStr
+}