Skip to content

Commit

Permalink
Allow //exhaustive:ignore on potential enums (#78)
Browse files Browse the repository at this point in the history
Fixes #76
  • Loading branch information
iwahbe authored May 4, 2024
1 parent 7ff5642 commit 80ad0aa
Show file tree
Hide file tree
Showing 5 changed files with 173 additions and 4 deletions.
3 changes: 3 additions & 0 deletions comment.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
57 changes: 55 additions & 2 deletions enum.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"go/types"
"strings"

"golang.org/x/tools/go/analysis"
"golang.org/x/tools/go/ast/inspector"
)

Expand Down Expand Up @@ -63,16 +64,33 @@ 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)

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
}

if hasIgnoreDecl(pass, gen.Doc) {
return
}

for _, s := range gen.Specs {
for _, name := range s.(*ast.ValueSpec).Names {
s := s.(*ast.ValueSpec)
if hasIgnoreDecl(pass, s.Doc) {
continue
}

for _, name := range s.Names {

if _, ignored := ignoredTypes[info.Defs[name].Type()]; ignored {
continue
}

enumTyp, memberName, val, ok := possibleEnumMember(name, info)
if !ok {
continue
Expand Down Expand Up @@ -140,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 := hasIgnoreDecl(pass, gen.Doc)

for _, s := range gen.Specs {
t := s.(*ast.TypeSpec)

doIgnoreSpec := doIgnoreDecl || hasIgnoreDecl(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())
Expand All @@ -157,6 +201,15 @@ func validBasic(basic *types.Basic) bool {
return false
}

func hasIgnoreDecl(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.
Expand Down
62 changes: 61 additions & 1 deletion enum_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
})
}
Expand Down Expand Up @@ -366,6 +366,66 @@ func checkEnums(t *testing.T, got []checkEnum, pkgOnly bool) {
`1`: {"Float64B"},
},
}},
{"DeclGroupIgnoredEnum", enumMembers{
[]string{"DeclGroupIgnoredMemberC"},
map[string]token.Pos{
"DeclGroupIgnoredMemberC": 0,
},
map[string]constantValue{
"DeclGroupIgnoredMemberC": `3`,
},
map[constantValue][]string{
`3`: {"DeclGroupIgnoredMemberC"},
},
}},
{"DeclIgnoredEnum", enumMembers{
[]string{"DeclIgnoredMemberB"},
map[string]token.Pos{
"DeclIgnoredMemberB": 0,
},
map[string]constantValue{
"DeclIgnoredMemberB": `2`,
},
map[constantValue][]string{
`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"},
},
}},
{"DeclTypePartialIgnore", enumMembers{
[]string{"DeclTypePartialIgnoreNotIgnored"},
map[string]token.Pos{
"DeclTypePartialIgnoreNotIgnored": 0,
},
map[string]constantValue{
"DeclTypePartialIgnoreNotIgnored": `2`,
},
map[constantValue][]string{
`2`: {"DeclTypePartialIgnoreNotIgnored"},
},
}},
}

for _, c := range wantPkg {
Expand Down
2 changes: 1 addition & 1 deletion exhaustive.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}

Expand Down
53 changes: 53 additions & 0 deletions testdata/src/enum/enum.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,3 +86,56 @@ const (
)

func (WithMethod) String() string { return "whatever" }

type DeclGroupIgnoredEnum int // want DeclGroupIgnoredEnum:"^DeclGroupIgnoredMemberC$"

//exhaustive:ignore
const (
DeclGroupIgnoredMemberA DeclGroupIgnoredEnum = 1
DeclGroupIgnoredMemberB DeclGroupIgnoredEnum = 2
)

const DeclGroupIgnoredMemberC DeclGroupIgnoredEnum = 3

type DeclIgnoredEnum int // want DeclIgnoredEnum:"^DeclIgnoredMemberB$"

//exhaustive:ignore
const DeclIgnoredMemberA DeclIgnoredEnum = 1

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
)

type DeclTypePartialIgnore int // want DeclTypePartialIgnore:"^DeclTypePartialIgnoreNotIgnored$"

const (
//exhaustive:ignore
DeclTypePartialIgnoreIgnored DeclTypePartialIgnore = 1
DeclTypePartialIgnoreNotIgnored DeclTypePartialIgnore = 2
)

0 comments on commit 80ad0aa

Please sign in to comment.