From d9dc665917671737c1b855f7d83e4c659de4e9d2 Mon Sep 17 00:00:00 2001 From: Ian Wahbe Date: Wed, 10 Apr 2024 00:43:55 +0200 Subject: [PATCH 1/3] Allow applying ignore directives to value declarations Fixes https://github.com/nishanths/exhaustive/issues/76 This PR enables the syntax: ```go type DeclIgnoredEnum int // want no enum detected //exhaustive:ignore const ( DeclIgnoredMamberA DeclIgnoredEnum = 1 DeclIgnoredMamberB DeclIgnoredEnum = 2 ) //exhaustive:ignore const DeclIgnoredMamberC DeclIgnoredEnum = 3 ``` --- comment.go | 3 +++ enum.go | 14 +++++++++++++- enum_test.go | 26 +++++++++++++++++++++++++- exhaustive.go | 2 +- testdata/src/enum/enum.go | 17 +++++++++++++++++ 5 files changed, 59 insertions(+), 3 deletions(-) diff --git a/comment.go b/comment.go index caf7649..e3347d9 100644 --- a/comment.go +++ b/comment.go @@ -29,6 +29,9 @@ type directiveSet int64 func parseDirectives(commentGroups []*ast.CommentGroup) (directiveSet, error) { var out directiveSet for _, commentGroup := range commentGroups { + if commentGroup == nil { + continue + } for _, comment := range commentGroup.List { commentLine := comment.Text if !strings.HasPrefix(commentLine, exhaustiveComment) { diff --git a/enum.go b/enum.go index cabf1d8..e62f09a 100644 --- a/enum.go +++ b/enum.go @@ -7,6 +7,7 @@ import ( "go/types" "strings" + "golang.org/x/tools/go/analysis" "golang.org/x/tools/go/ast/inspector" ) @@ -63,7 +64,7 @@ func (em *enumMembers) factString() string { return buf.String() } -func findEnums(pkgScopeOnly bool, pkg *types.Package, inspect *inspector.Inspector, info *types.Info) map[enumType]enumMembers { +func findEnums(pass *analysis.Pass, pkgScopeOnly bool, pkg *types.Package, inspect *inspector.Inspector, info *types.Info) map[enumType]enumMembers { result := make(map[enumType]enumMembers) inspect.Preorder([]ast.Node{&ast.GenDecl{}}, func(n ast.Node) { @@ -71,6 +72,17 @@ func findEnums(pkgScopeOnly bool, pkg *types.Package, inspect *inspector.Inspect if gen.Tok != token.CONST { return } + + dirs, err := parseDirectives([]*ast.CommentGroup{gen.Doc}) + if err != nil { + pass.Report(makeInvalidDirectiveDiagnostic(gen, err)) + return + } + + if dirs.has(ignoreDirective) { + return + } + for _, s := range gen.Specs { for _, name := range s.(*ast.ValueSpec).Names { enumTyp, memberName, val, ok := possibleEnumMember(name, info) diff --git a/enum_test.go b/enum_test.go index bac3f08..68cccaf 100644 --- a/enum_test.go +++ b/enum_test.go @@ -96,7 +96,7 @@ func TestFindEnums(t *testing.T) { for _, pkgOnly := range [...]bool{false, true} { t.Run(fmt.Sprint("pkgOnly", pkgOnly), func(t *testing.T) { - result := findEnums(pkgOnly, testdataEnumPkg.Types, inspect, testdataEnumPkg.TypesInfo) + result := findEnums(nil, pkgOnly, testdataEnumPkg.Types, inspect, testdataEnumPkg.TypesInfo) checkEnums(t, transform(result), pkgOnly) }) } @@ -366,6 +366,30 @@ func checkEnums(t *testing.T, got []checkEnum, pkgOnly bool) { `1`: {"Float64B"}, }, }}, + {"DeclGroupIgnoredEnum", enumMembers{ + []string{"DeclGroupIgnoredMamberC"}, + map[string]token.Pos{ + "DeclGroupIgnoredMamberC": 0, + }, + map[string]constantValue{ + "DeclGroupIgnoredMamberC": `3`, + }, + map[constantValue][]string{ + `3`: {"DeclGroupIgnoredMamberC"}, + }, + }}, + {"DeclIgnoredEnum", enumMembers{ + []string{"DeclIgnoredMamberB"}, + map[string]token.Pos{ + "DeclIgnoredMamberB": 0, + }, + map[string]constantValue{ + "DeclIgnoredMamberB": `2`, + }, + map[constantValue][]string{ + `2`: {"DeclIgnoredMamberB"}, + }, + }}, } for _, c := range wantPkg { diff --git a/exhaustive.go b/exhaustive.go index 013ac47..470b56d 100644 --- a/exhaustive.go +++ b/exhaustive.go @@ -108,7 +108,7 @@ var Analyzer = &analysis.Analyzer{ func run(pass *analysis.Pass) (interface{}, error) { inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector) - for typ, members := range findEnums(fPackageScopeOnly, pass.Pkg, inspect, pass.TypesInfo) { + for typ, members := range findEnums(pass, fPackageScopeOnly, pass.Pkg, inspect, pass.TypesInfo) { exportFact(pass, typ, members) } diff --git a/testdata/src/enum/enum.go b/testdata/src/enum/enum.go index 4135243..72ae9f7 100644 --- a/testdata/src/enum/enum.go +++ b/testdata/src/enum/enum.go @@ -86,3 +86,20 @@ const ( ) func (WithMethod) String() string { return "whatever" } + +type DeclGroupIgnoredEnum int // want DeclGroupIgnoredEnum:"^DeclGroupIgnoredMamberC$" + +//exhaustive:ignore +const ( + DeclGroupIgnoredMamberA DeclGroupIgnoredEnum = 1 + DeclGroupIgnoredMamberB DeclGroupIgnoredEnum = 2 +) + +const DeclGroupIgnoredMamberC DeclGroupIgnoredEnum = 3 + +type DeclIgnoredEnum int // want DeclIgnoredEnum:"^DeclIgnoredMamberB$" + +//exhaustive:ignore +const DeclIgnoredMamberA DeclIgnoredEnum = 1 + +const DeclIgnoredMamberB DeclIgnoredEnum = 2 From ce37a1dbabb512ee977f74903efba51458a57425 Mon Sep 17 00:00:00 2001 From: Ian Wahbe Date: Wed, 10 Apr 2024 01:23:47 +0200 Subject: [PATCH 2/3] Allow applying ignore directives to type declarations Adds support for the syntax: ```go //exhaustive:ignore type DeclTypeIgnoredEnum int const ( DeclTypeIgnoredMamberA DeclTypeIgnoredEnum = 1 DeclTypeIgnoredMamberB DeclTypeIgnoredEnum = 2 ) ``` --- enum.go | 57 +++++++++++++++++++++++++++++++++------ enum_test.go | 40 +++++++++++++++++++++------ testdata/src/enum/enum.go | 42 ++++++++++++++++++++++++----- 3 files changed, 116 insertions(+), 23 deletions(-) diff --git a/enum.go b/enum.go index e62f09a..1241338 100644 --- a/enum.go +++ b/enum.go @@ -67,24 +67,30 @@ func (em *enumMembers) factString() string { func findEnums(pass *analysis.Pass, pkgScopeOnly bool, pkg *types.Package, inspect *inspector.Inspector, info *types.Info) map[enumType]enumMembers { result := make(map[enumType]enumMembers) + ignoredTypes := findIgnoredTypes(pass, inspect, info) + inspect.Preorder([]ast.Node{&ast.GenDecl{}}, func(n ast.Node) { gen := n.(*ast.GenDecl) if gen.Tok != token.CONST { return } - dirs, err := parseDirectives([]*ast.CommentGroup{gen.Doc}) - if err != nil { - pass.Report(makeInvalidDirectiveDiagnostic(gen, err)) - return - } - - if dirs.has(ignoreDirective) { + if isIgnoreDecl(pass, gen.Doc) { return } for _, s := range gen.Specs { - for _, name := range s.(*ast.ValueSpec).Names { + s := s.(*ast.ValueSpec) + if isIgnoreDecl(pass, s.Doc) { + return + } + + for _, name := range s.Names { + + if _, ignored := ignoredTypes[info.Defs[name].Type()]; ignored { + continue + } + enumTyp, memberName, val, ok := possibleEnumMember(name, info) if !ok { continue @@ -152,6 +158,32 @@ func possibleEnumMember(constName *ast.Ident, info *types.Info) (et enumType, na return enumType{tn}, obj.Name(), determineConstVal(constName, info), true } +func findIgnoredTypes(pass *analysis.Pass, inspect *inspector.Inspector, info *types.Info) map[types.Type]struct{} { + ignoredTypes := map[types.Type]struct{}{} + + inspect.Preorder([]ast.Node{&ast.GenDecl{}}, func(n ast.Node) { + gen := n.(*ast.GenDecl) + if gen.Tok != token.TYPE { + return + } + + doIgnoreDecl := isIgnoreDecl(pass, gen.Doc) + + for _, s := range gen.Specs { + t := s.(*ast.TypeSpec) + + doIgnoreSpec := doIgnoreDecl || isIgnoreDecl(pass, t.Doc) + if !doIgnoreSpec { + continue + } + + ignoredTypes[info.Defs[t.Name].Type()] = struct{}{} + } + }) + + return ignoredTypes +} + func determineConstVal(name *ast.Ident, info *types.Info) constantValue { c := info.ObjectOf(name).(*types.Const) return constantValue(c.Val().ExactString()) @@ -169,6 +201,15 @@ func validBasic(basic *types.Basic) bool { return false } +func isIgnoreDecl(pass *analysis.Pass, doc *ast.CommentGroup) bool { + dirs, err := parseDirectives([]*ast.CommentGroup{doc}) + if err != nil { + pass.Report(makeInvalidDirectiveDiagnostic(doc, err)) + return false + } + return dirs.has(ignoreDirective) +} + // validNamedBasic returns whether the type t is a named type whose underlying // type is a valid basic type to form an enum. A type that passes this check // meets the definition of an enum type. diff --git a/enum_test.go b/enum_test.go index 68cccaf..946bfd6 100644 --- a/enum_test.go +++ b/enum_test.go @@ -367,27 +367,51 @@ func checkEnums(t *testing.T, got []checkEnum, pkgOnly bool) { }, }}, {"DeclGroupIgnoredEnum", enumMembers{ - []string{"DeclGroupIgnoredMamberC"}, + []string{"DeclGroupIgnoredMemberC"}, map[string]token.Pos{ - "DeclGroupIgnoredMamberC": 0, + "DeclGroupIgnoredMemberC": 0, }, map[string]constantValue{ - "DeclGroupIgnoredMamberC": `3`, + "DeclGroupIgnoredMemberC": `3`, }, map[constantValue][]string{ - `3`: {"DeclGroupIgnoredMamberC"}, + `3`: {"DeclGroupIgnoredMemberC"}, }, }}, {"DeclIgnoredEnum", enumMembers{ - []string{"DeclIgnoredMamberB"}, + []string{"DeclIgnoredMemberB"}, map[string]token.Pos{ - "DeclIgnoredMamberB": 0, + "DeclIgnoredMemberB": 0, }, map[string]constantValue{ - "DeclIgnoredMamberB": `2`, + "DeclIgnoredMemberB": `2`, }, map[constantValue][]string{ - `2`: {"DeclIgnoredMamberB"}, + `2`: {"DeclIgnoredMemberB"}, + }, + }}, + {"DeclTypeInnerNotIgnore", enumMembers{ + []string{"DeclTypeInnerNotIgnoreMember"}, + map[string]token.Pos{ + "DeclTypeInnerNotIgnoreMember": 0, + }, + map[string]constantValue{ + "DeclTypeInnerNotIgnoreMember": `5`, + }, + map[constantValue][]string{ + `5`: {"DeclTypeInnerNotIgnoreMember"}, + }, + }}, + {"DeclTypeIgnoredValue", enumMembers{ + []string{"DeclTypeNotIgnoredValue"}, + map[string]token.Pos{ + "DeclTypeNotIgnoredValue": 0, + }, + map[string]constantValue{ + "DeclTypeNotIgnoredValue": `1`, + }, + map[constantValue][]string{ + `1`: {"DeclTypeNotIgnoredValue"}, }, }}, } diff --git a/testdata/src/enum/enum.go b/testdata/src/enum/enum.go index 72ae9f7..adf2861 100644 --- a/testdata/src/enum/enum.go +++ b/testdata/src/enum/enum.go @@ -87,19 +87,47 @@ const ( func (WithMethod) String() string { return "whatever" } -type DeclGroupIgnoredEnum int // want DeclGroupIgnoredEnum:"^DeclGroupIgnoredMamberC$" +type DeclGroupIgnoredEnum int // want DeclGroupIgnoredEnum:"^DeclGroupIgnoredMemberC$" //exhaustive:ignore const ( - DeclGroupIgnoredMamberA DeclGroupIgnoredEnum = 1 - DeclGroupIgnoredMamberB DeclGroupIgnoredEnum = 2 + DeclGroupIgnoredMemberA DeclGroupIgnoredEnum = 1 + DeclGroupIgnoredMemberB DeclGroupIgnoredEnum = 2 ) -const DeclGroupIgnoredMamberC DeclGroupIgnoredEnum = 3 +const DeclGroupIgnoredMemberC DeclGroupIgnoredEnum = 3 -type DeclIgnoredEnum int // want DeclIgnoredEnum:"^DeclIgnoredMamberB$" +type DeclIgnoredEnum int // want DeclIgnoredEnum:"^DeclIgnoredMemberB$" //exhaustive:ignore -const DeclIgnoredMamberA DeclIgnoredEnum = 1 +const DeclIgnoredMemberA DeclIgnoredEnum = 1 -const DeclIgnoredMamberB DeclIgnoredEnum = 2 +const DeclIgnoredMemberB DeclIgnoredEnum = 2 + +//exhaustive:ignore +type DeclTypeIgnoredEnum int + +const ( + DeclTypeIgnoredMemberA DeclTypeIgnoredEnum = 1 + DeclTypeIgnoredMemberB DeclTypeIgnoredEnum = 2 +) + +type ( + //exhaustive:ignore + DeclTypeInnerIgnore int + DeclTypeInnerNotIgnore int // want DeclTypeInnerNotIgnore:"^DeclTypeInnerNotIgnoreMember$" +) + +const ( + DeclTypeInnerIgnoreMemberA DeclTypeInnerIgnore = 3 + DeclTypeInnerIgnoreMemberB DeclTypeInnerIgnore = 4 + DeclTypeInnerNotIgnoreMember DeclTypeInnerNotIgnore = 5 +) + +type DeclTypeIgnoredValue int // want DeclTypeIgnoredValue:"^DeclTypeNotIgnoredValue$" + +const ( + DeclTypeNotIgnoredValue DeclTypeIgnoredValue = 1 + //exhaustive:ignore + DeclTypeIsIgnoredValue DeclTypeIgnoredValue = 2 +) From e2efb567f57c97ae8e4345859f8aaa52b1dd4669 Mon Sep 17 00:00:00 2001 From: Ian Wahbe Date: Fri, 3 May 2024 12:02:57 -1000 Subject: [PATCH 3/3] Handle an ignore within a `const (` decl --- enum.go | 12 ++++++------ enum_test.go | 12 ++++++++++++ testdata/src/enum/enum.go | 8 ++++++++ 3 files changed, 26 insertions(+), 6 deletions(-) diff --git a/enum.go b/enum.go index 1241338..e95e78e 100644 --- a/enum.go +++ b/enum.go @@ -75,14 +75,14 @@ func findEnums(pass *analysis.Pass, pkgScopeOnly bool, pkg *types.Package, inspe return } - if isIgnoreDecl(pass, gen.Doc) { + if hasIgnoreDecl(pass, gen.Doc) { return } for _, s := range gen.Specs { s := s.(*ast.ValueSpec) - if isIgnoreDecl(pass, s.Doc) { - return + if hasIgnoreDecl(pass, s.Doc) { + continue } for _, name := range s.Names { @@ -167,12 +167,12 @@ func findIgnoredTypes(pass *analysis.Pass, inspect *inspector.Inspector, info *t return } - doIgnoreDecl := isIgnoreDecl(pass, gen.Doc) + doIgnoreDecl := hasIgnoreDecl(pass, gen.Doc) for _, s := range gen.Specs { t := s.(*ast.TypeSpec) - doIgnoreSpec := doIgnoreDecl || isIgnoreDecl(pass, t.Doc) + doIgnoreSpec := doIgnoreDecl || hasIgnoreDecl(pass, t.Doc) if !doIgnoreSpec { continue } @@ -201,7 +201,7 @@ func validBasic(basic *types.Basic) bool { return false } -func isIgnoreDecl(pass *analysis.Pass, doc *ast.CommentGroup) bool { +func hasIgnoreDecl(pass *analysis.Pass, doc *ast.CommentGroup) bool { dirs, err := parseDirectives([]*ast.CommentGroup{doc}) if err != nil { pass.Report(makeInvalidDirectiveDiagnostic(doc, err)) diff --git a/enum_test.go b/enum_test.go index 946bfd6..8ee6772 100644 --- a/enum_test.go +++ b/enum_test.go @@ -414,6 +414,18 @@ func checkEnums(t *testing.T, got []checkEnum, pkgOnly bool) { `1`: {"DeclTypeNotIgnoredValue"}, }, }}, + {"DeclTypePartialIgnore", enumMembers{ + []string{"DeclTypePartialIgnoreNotIgnored"}, + map[string]token.Pos{ + "DeclTypePartialIgnoreNotIgnored": 0, + }, + map[string]constantValue{ + "DeclTypePartialIgnoreNotIgnored": `2`, + }, + map[constantValue][]string{ + `2`: {"DeclTypePartialIgnoreNotIgnored"}, + }, + }}, } for _, c := range wantPkg { diff --git a/testdata/src/enum/enum.go b/testdata/src/enum/enum.go index adf2861..37961be 100644 --- a/testdata/src/enum/enum.go +++ b/testdata/src/enum/enum.go @@ -131,3 +131,11 @@ const ( //exhaustive:ignore DeclTypeIsIgnoredValue DeclTypeIgnoredValue = 2 ) + +type DeclTypePartialIgnore int // want DeclTypePartialIgnore:"^DeclTypePartialIgnoreNotIgnored$" + +const ( + //exhaustive:ignore + DeclTypePartialIgnoreIgnored DeclTypePartialIgnore = 1 + DeclTypePartialIgnoreNotIgnored DeclTypePartialIgnore = 2 +)