Skip to content

jiftechnify/untypedconst

Repository files navigation

untypedconst

A Go linter checks that untyped constant expressions are used as values of defined type (a.k.a. named type).

Usage

# 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

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.

  1. 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"
  2. Run golangci-lint custom to build a custom golangci-lint binary with untypedconst included as a plugin.

    • By default, the output path of the binary is ./custom-gcl. You can change it by specifying destination field in .custom-gcl.yml.
  3. 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"
  4. Run the custom golangci-lint executable against your codebase:

    ./custom-gcl run ./...

Motivation

When you want to define enums in Go, you should go through following steps:

  1. Define an "defined type" for the enum.
  2. 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"
}

License

MIT

About

A Go linter checks suspicious usage of untyped const

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Contributors 3

  •  
  •  
  •  

Languages