Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow //exhaustive:ignore on potential enums #78

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
}

iwahbe marked this conversation as resolved.
Show resolved Hide resolved
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
)
Loading