leighmcculloch/gochecknoglobals

How to handle `regexp.MustCompile`

cloudlena opened this issue ยท 8 comments

I have a couple of calls to regexp.MustCompile which, in my opinion, make sense to be stored in a global value because they ensure that the regex is valid at compile time. Would it make sense to add an exception for cases like that? So to whitelist a couple of functions that work like regexp.MustCompile from the standard library?

An example can be found here: https://github.com/mastertinner/latest/blob/master/internal/app/latest/brew/upgrade.go#L13

I agree, this is probably a good thing to add as something that shouldn't error. But I think the motivation comes from the fact that these are typically used as constants. As far as I'm aware the regex.MustCompile function won't actually check the regex at the time the go compiler compiles the application, because the contents of the string won't be evaluated until the application runs but the function is named MustCompile because it is a companion function to Compile which panics instead of returning an error.

Would you be up for opening a PR with this change?

because they ensure that the regex is valid at compile time

As noted by @leighmcculloch this is not the case. It will compile fine, but instantly panics once run:

$ cat main.go
package main

import (
	"regexp"
)

var (
	upgradeRegex = regexp.MustCompile(`[`)
)

func main() {
	println("Hello World")
}

$ go build -o main # Works fine
$ ./main
panic: regexp: Compile(`[`): error parsing regexp: missing closing ]: `[`
[..]

Given that, I see no reason in supporting regexp.MustCompile for global vars.

Having regex patterns as global variables is actually quite useful. Consider you're making an API where you need to validate a bunch of fields. Then you could compile all your regex patterns once at init and use them all over.

package main

import (
	"fmt"
	"regexp"
)

var RegexpPassword = regexp.MustCompile(`^[[:graph:]]+$`)
var RegexpUsername = regexp.MustCompile(`^[[:alpha:]]{1,15}$`)

func main() {
	fmt.Printf("Is valid password: %v\n", RegexpPassword.MatchString(`super secret pa55w0rd!`))
	fmt.Printf("Is valid username: %v\n", RegexpPassword.MatchString(`--neo--`))
}

Instead gochecknoglobals forces one to do compile the regex inline:

package main

import (
	"fmt"
	"regexp"
)

var RegexpPasswordStr = `^[[:graph:]]+$`
var RegexpUsernameStr = `^[[:alpha:]]{1,15}$`

func main() {
	RegexpPassword := regexp.MustCompile(RegexpPasswordStr)
	RegexpUsername := regexp.MustCompile(RegexpUsernameStr)
	fmt.Printf("Is valid password: %v\n", RegexpPassword.MatchString(`super secret pa55w0rd!`))
	fmt.Printf("Is valid username: %v\n", RegexpUsername.MatchString(`--neo--`))
}

The problem here is that now the regex pattern is compiled each time it is used.

The standard library have examples of both uses approaches.

I think allowing regex's with MustCompile in global vars makes sense. It's a common pattern and this tool shouldn't get in the way of common patterns that don't collide with the purpose of this tool.

#12 should get merged soon which adds support for this and I just need some personal time to go through it, get it merged, and released.

@leighmcculloch: Should DefaultXXX variables also be allowed? Ref DefaultClient and DefaultServeMux in the http package.

Should DefaultXXX variables also be allowed?

@palsivertsen I don't think so. Instead replace them with functions that create a default. Global variables like http.DefaultClient result in side-effects where code in one place can change the behavior and state of code in another, unintentionally.

This has been addressed and merged in #12.