A Go static analysis linter that enforces type safety for quasi-enum patterns, compensating for Go's lack of native enum support.
Go doesn't have native enum types. Developers typically emulate enums using type aliases with constants:
type Status int
const (
StatusActive Status = iota
StatusInactive
StatusPending
)However, Go's type system allows unsafe assignments that bypass enum safety:
var s Status = 5 // ❌ Should only use defined constants
s = Status(someVariable) // ❌ Bypasses enum safety
const myVal = 3
var s2 Status = myVal // ❌ Untyped constant, not part of enumenumsafety detects and prevents these violations, ensuring quasi-enum types are only assigned their defined constant values.
✅ Literal Detection - Catches direct literal assignments (var s Status = 5)
✅ Untyped Constant Detection - Prevents use of constants not part of enum definition
✅ Variable Conversion Detection - Blocks conversions from variables to enum types
✅ Cross-Enum Detection - Prevents conversions between different enum types
✅ Flexible Detection - 5 configurable techniques to identify quasi-enums
✅ Definition Validation - 5 constraints to ensure proper enum structure
✅ Quality-of-Life Checks - Suggests uint8 optimization, String(), and UnmarshalText() methods
✅ Configurable - 14 command-line flags for fine-grained control
✅ go vet Integration - Works seamlessly with standard Go tooling
go install github.com/Djarvur/go-enumsafety/cmd/enumsafety@latest// Status enum
type Status int
const (
StatusActive Status = iota
StatusInactive
StatusPending
)# Analyze current package
enumsafety ./...
# Or use with go vet
go vet -vettool=$(which enumsafety) ./...// ❌ Before
var s Status = 5
// ✅ After
var s Status = StatusActiveThe linter identifies quasi-enums using multiple techniques (all enabled by default):
Detects types with 2+ constants of the same type:
type Priority uint8
const (
PriorityLow Priority = 1
PriorityHigh Priority = 2
)Detects types with "enum" suffix (case-insensitive):
type StatusEnum intDetects types with inline // enum comment:
type Color uint8 // enumDetects types with preceding // enum comment:
// enum
type Level intDetects types with // TypeName enum pattern:
// Status enum
type Status intPrevent detection with // not enum comment:
type NotAnEnum int // not enumThe linter validates that detected quasi-enums follow best practices:
Enums must have at least 2 constants.
All enum constants must be in the same const block.
Type and constants must be in the same file.
The const block should only contain constants of the enum type.
Type declaration and const block should be close together (within 10 lines).
var s Status = 5 // ❌ Error: literal value assigned to quasi-enum type Statusconst myValue = 3
var s Status = myValue // ❌ Error: untyped constant assigned to quasi-enum type Statusvar x uint8 = 5
s := Status(x) // ❌ Error: variable converted to quasi-enum type Statusvar c Color = ColorRed
l := Level(c) // ❌ Error: variable converted to quasi-enum type LevelSuggests using uint8 for memory efficiency:
type Status int // ⚠️ Suggestion: use uint8 (only 3 constants)Warns about missing String() method:
type Status int // ⚠️ Warning: lacks String() methodWarns about missing UnmarshalText() for JSON/config parsing:
type Status int // ⚠️ Warning: lacks UnmarshalText([]byte) error methodDisable specific detection techniques:
-disable-constants-detection # Disable DT-001
-disable-suffix-detection # Disable DT-002
-disable-inline-comment-detection # Disable DT-003
-disable-preceding-comment-detection # Disable DT-004
-disable-named-comment-detection # Disable DT-005Disable specific constraints:
-disable-min-constants-check # Disable DC-001 (minimum 2 constants)
-disable-same-block-check # Disable DC-002 (same const block)
-disable-same-file-check # Disable DC-003 (same file)
-disable-exclusive-block-check # Disable DC-004 (exclusive block)
-disable-proximity-check # Disable DC-005 (proximity)Disable quality-of-life checks:
-disable-uint8-suggestion # Disable uint8 optimization suggestions
-disable-string-method-check # Disable String() method warnings
-disable-unmarshal-method-check # Disable UnmarshalText() warningsCustomize the detection keyword (default: "enum"):
-enum-keyword=enumeration # Use "enumeration" instead of "enum"# Analyze current package
enumsafety ./...
# Analyze specific package
enumsafety ./internal/models
# Analyze with verbose output
enumsafety -v ./...# Use as vet tool
go vet -vettool=$(which enumsafety) ./...
# Combine with other vet checks
go vet -vettool=$(which enumsafety) -all ./...# Disable uint8 suggestions
enumsafety -disable-uint8-suggestion ./...
# Only check for violations, skip quality checks
enumsafety \
-disable-uint8-suggestion \
-disable-string-method-check \
-disable-unmarshal-method-check \
./...
# Use custom keyword
enumsafety -enum-keyword=enumeration ./...
# Minimal checks (only constants-based detection)
enumsafety \
-disable-suffix-detection \
-disable-inline-comment-detection \
-disable-preceding-comment-detection \
-disable-named-comment-detection \
./...Given this code:
package main
// Status enum
type Status int
const (
StatusActive Status = iota
StatusInactive
StatusPending
)
func main() {
var s Status = 5 // Violation
}The linter reports:
main.go:12:6: literal value assigned to quasi-enum type Status
main.go:4:6: quasi-enum type Status uses int but has only 3 constants; consider using uint8 for memory optimization
main.go:4:6: quasi-enum type Status lacks a String() method; consider using golang.org/x/tools/cmd/stringer or github.com/Djarvur/go-silly-enum to generate it
main.go:4:6: quasi-enum type Status lacks an UnmarshalText([]byte) error method; consider using github.com/Djarvur/go-silly-enum to generate it
Make enum intent clear:
// Status enum for user states
type Status uint8
const (
StatusActive Status = 1
StatusInactive Status = 2
)Add String() and UnmarshalText() for better DX:
func (s Status) String() string {
switch s {
case StatusActive:
return "Active"
case StatusInactive:
return "Inactive"
default:
return "Unknown"
}
}
func (s *Status) UnmarshalText(text []byte) error {
switch string(text) {
case "Active":
*s = StatusActive
case "Inactive":
*s = StatusInactive
default:
return fmt.Errorf("unknown status: %s", text)
}
return nil
}Optimize memory usage:
type Status uint8 // ✅ Better than int for 2-256 valuesFollow the constraints:
- Minimum 2 constants
- All constants in same block
- Type and constants in same file
- Exclusive const block
- Close proximity
name: Lint
on: [push, pull_request]
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-go@v4
with:
go-version: '1.22'
- name: Install enumsafety
run: go install github.com/Djarvur/go-enumsafety/cmd/enumsafety@latest
- name: Run linter
run: enumsafety ./...#!/bin/bash
# .git/hooks/pre-commit
enumsafety ./...
if [ $? -ne 0 ]; then
echo "enumsafety found violations. Please fix before committing."
exit 1
fiA: Type safety catches bugs at compile time. enumsafety enforces enum safety that Go's type system doesn't provide natively.
A: Yes! Start by running with all quality checks disabled, then gradually enable them:
enumsafety \
-disable-uint8-suggestion \
-disable-string-method-check \
-disable-unmarshal-method-check \
./...A: Use the opt-out comment:
type NotAnEnum int // not enumA: Yes! The linter fully supports iota-based enums.
A: Yes! Use -enum-keyword=yourword to use a different keyword than "enum".
Contributions are welcome! Please see CONTRIBUTING.md for guidelines.
MIT License - see LICENSE for details.
- golang.org/x/tools/cmd/stringer - Generate String() methods
- github.com/Djarvur/go-silly-enum - Generate enum helper methods
This project was developed using:
- AI Assistant: Google Gemini 2.0 Flash (Experimental)
- Development Framework: SpecKit - AI-assisted software specification and implementation toolkit
Built with golang.org/x/tools/go/analysis framework.