A Go linter checks that untyped constant expressions are used as values of defined type (a.k.a. named type).
# as a standalone linter
go install github.com/jiftechnify/untypedconst/cmd/untypedconst@latest
untypedconst ./...
# with `go vet`
go install github.com/jiftechnify/untypedconst/cmd/untypedconst@latest
go vet -vettool=$(which untypedconst) ./...
Run as a golangci-lint's custom linter
You can also run untypedconst as a custom linter of golangci-lint with the following steps. Please refer to the official document for details.
-
Create
.custom-gcl.yml
under the root directory of your project:version: v2.5.0 plugins: - module: "github.com/jiftechnify/untypedconst" import: "github.com/jiftechnify/untypedconst/cmd/gciplugin" version: "latest" # or specify a specific version e.g. "v0.2.0"
-
Run
golangci-lint custom
to build a customgolangci-lint
binary with untypedconst included as a plugin.- By default, the output path of the binary is
./custom-gcl
. You can change it by specifyingdestination
field in.custom-gcl.yml
.
- By default, the output path of the binary is
-
Add lines of configuration to your
.golangci.yml
to enable untypedconst:# for golangci-lint v2.x version: "2" linters: enable: - untypedconst settings: custom: untypedconst: type: "module" description: "Detects suspicious usage of untyped constants"
# for golangci-lint v1.x linters: enable: - untypedconst linters-settings: custom: untypedconst: type: "module" description: "Detects suspicious usage of untyped constants"
-
Run the custom
golangci-lint
executable against your codebase:./custom-gcl run ./...
When you want to define enums in Go, you should go through following steps:
- Define an "defined type" for the enum.
- Define enum variants as values of the type you defined in 1.
Or, you should write a code like this:
type TrafficLight int
const (
None TrafficLight = iota // 0
Green // 1
Yellow // 2
Red // 3
)
Then you use it like:
func next(tl TrafficLight) TrafficLight {
switch tl {
case Green:
return Yellow
case Yellow:
return Red
case Red:
return Green
case None:
return None
default:
panic("boom!")
}
}
func good() {
tl := next(Green) // tl == Yellow
}
So far so good, until you write a code like this by accident:
func bad() {
tl := next(4) // it compiles, unfortunately
// => boom!
}
In the code above, 4
is an "untyped constant", and when you "use" it (assign it to a variable, pass it to a function, etc.), it is assigned the most suited type from the context, i.e. TrafficLight
. So types match and it compiles despite the actual value is out of the range of TrafficLight
! It's a pity that Go compiler doesn't come with a feature that prevents us from breaking codes in such a way.
untypedconst (indirectly) detects such a mistake by checking whether you don't use untyped constants in places that require values of some defined type:
func bad() {
tl := next(4)
// => passing untyped constant to parameter of defined type "TrafficLight"
}
MIT