spurious SA5009 with fmt.Formatter
go version go1.20 windows/amd64
using this code:
package main
import "fmt"
type export int
func (e export) Format(f fmt.State, verb rune) {
if verb == '_' {
fmt.Fprint(f, "_")
}
fmt.Fprint(f, int(e))
}
func main() {
var e export = 1
fmt.Printf("%v %_\n", e, e)
}
it runs fine:
> go run exports.go
1 _1
but I get a message:
> staticcheck exports.go
exports.go:16:15: couldn't parse format string (SA5009)
running staticcheck from current master.
Our format string parser currently assumes that only the letters a-z and A-Z are valid verbs. Seeing how both fmt and go vet accept your format string, that assumption is probably wrong.
OK from my testing, these are all valid:
fmt.Printf("%!", export{}); fmt.Println()
fmt.Printf("%$", export{}); fmt.Println()
fmt.Printf("%%%v", export{}); fmt.Println()
fmt.Printf("%&", export{}); fmt.Println()
fmt.Printf("%'", export{}); fmt.Println()
fmt.Printf("%(", export{}); fmt.Println()
fmt.Printf("%)", export{}); fmt.Println()
fmt.Printf("%,", export{}); fmt.Println()
fmt.Printf("%.", export{}); fmt.Println()
fmt.Printf("%/", export{}); fmt.Println()
fmt.Printf("%:", export{}); fmt.Println()
fmt.Printf("%;", export{}); fmt.Println()
fmt.Printf("%<", export{}); fmt.Println()
fmt.Printf("%=", export{}); fmt.Println()
fmt.Printf("%>", export{}); fmt.Println()
fmt.Printf("%?", export{}); fmt.Println()
fmt.Printf("%@", export{}); fmt.Println()
fmt.Printf("%A", export{}); fmt.Println()
fmt.Printf("%B", export{}); fmt.Println()
fmt.Printf("%C", export{}); fmt.Println()
fmt.Printf("%D", export{}); fmt.Println()
fmt.Printf("%E", export{}); fmt.Println()
fmt.Printf("%F", export{}); fmt.Println()
fmt.Printf("%G", export{}); fmt.Println()
fmt.Printf("%H", export{}); fmt.Println()
fmt.Printf("%I", export{}); fmt.Println()
fmt.Printf("%J", export{}); fmt.Println()
fmt.Printf("%K", export{}); fmt.Println()
fmt.Printf("%L", export{}); fmt.Println()
fmt.Printf("%M", export{}); fmt.Println()
fmt.Printf("%N", export{}); fmt.Println()
fmt.Printf("%O", export{}); fmt.Println()
fmt.Printf("%P", export{}); fmt.Println()
fmt.Printf("%Q", export{}); fmt.Println()
fmt.Printf("%R", export{}); fmt.Println()
fmt.Printf("%S", export{}); fmt.Println()
fmt.Printf("%T", export{}); fmt.Println()
fmt.Printf("%U", export{}); fmt.Println()
fmt.Printf("%V", export{}); fmt.Println()
fmt.Printf("%W", export{}); fmt.Println()
fmt.Printf("%X", export{}); fmt.Println()
fmt.Printf("%Y", export{}); fmt.Println()
fmt.Printf("%Z", export{}); fmt.Println()
fmt.Printf("%\"", export{}); fmt.Println()
fmt.Printf("%\\", export{}); fmt.Println()
fmt.Printf("%]", export{}); fmt.Println()
fmt.Printf("%^", export{}); fmt.Println()
fmt.Printf("%_", export{}); fmt.Println()
fmt.Printf("%`", export{}); fmt.Println()
fmt.Printf("%a", export{}); fmt.Println()
fmt.Printf("%b", export{}); fmt.Println()
fmt.Printf("%c", export{}); fmt.Println()
fmt.Printf("%d", export{}); fmt.Println()
fmt.Printf("%e", export{}); fmt.Println()
fmt.Printf("%f", export{}); fmt.Println()
fmt.Printf("%g", export{}); fmt.Println()
fmt.Printf("%h", export{}); fmt.Println()
fmt.Printf("%i", export{}); fmt.Println()
fmt.Printf("%j", export{}); fmt.Println()
fmt.Printf("%k", export{}); fmt.Println()
fmt.Printf("%l", export{}); fmt.Println()
fmt.Printf("%m", export{}); fmt.Println()
fmt.Printf("%n", export{}); fmt.Println()
fmt.Printf("%o", export{}); fmt.Println()
fmt.Printf("%p", export{}); fmt.Println()
fmt.Printf("%q", export{}); fmt.Println()
fmt.Printf("%r", export{}); fmt.Println()
fmt.Printf("%s", export{}); fmt.Println()
fmt.Printf("%t", export{}); fmt.Println()
fmt.Printf("%u", export{}); fmt.Println()
fmt.Printf("%v", export{}); fmt.Println()
fmt.Printf("%w", export{}); fmt.Println()
fmt.Printf("%x", export{}); fmt.Println()
fmt.Printf("%y", export{}); fmt.Println()
fmt.Printf("%z", export{}); fmt.Println()
fmt.Printf("%{", export{}); fmt.Println()
fmt.Printf("%|", export{}); fmt.Println()
fmt.Printf("%}", export{}); fmt.Println()
fmt.Printf("%~", export{}); fmt.Println()
and these gave errors:
// %!(NOVERB)%!(EXTRA main.export=v)
fmt.Printf("%#", export{}); fmt.Println()
// %!(BADWIDTH)%!(NOVERB)
fmt.Printf("%*", export{}); fmt.Println()
// %!(NOVERB)%!(EXTRA main.export=v)
fmt.Printf("%+", export{}); fmt.Println()
// %!(NOVERB)%!(EXTRA main.export=v)
fmt.Printf("%-", export{}); fmt.Println()
// %!(NOVERB)%!(EXTRA main.export=v)
fmt.Printf("%0", export{}); fmt.Println()
fmt.Printf("%1", export{}); fmt.Println()
fmt.Printf("%2", export{}); fmt.Println()
fmt.Printf("%3", export{}); fmt.Println()
fmt.Printf("%4", export{}); fmt.Println()
fmt.Printf("%5", export{}); fmt.Println()
fmt.Printf("%6", export{}); fmt.Println()
fmt.Printf("%7", export{}); fmt.Println()
fmt.Printf("%8", export{}); fmt.Println()
fmt.Printf("%9", export{}); fmt.Println()
// %!(NOVERB)
fmt.Printf("%[", export{}); fmt.Println()
so maybe you could test if %!(NOVERB) is found in the output or something? or just expand the current valid values.
You can use any codepoint; see doPrintf() in fmt/print.go. This is valid:
type export int
func (e export) Format(f fmt.State, verb rune) {
switch verb {
case '🤷':
fmt.Fprint(f, 0)
case '🤑':
fmt.Fprint(f, int(e)*10)
case '𓂺':
fmt.Fprint(f, int(e)*100)
default:
fmt.Fprintf(f, "%%(unknown verb '%c')", verb)
}
}
func main() {
fmt.Printf("%🤷 %[1]🤑 %[1]𓂺\n", export(42))
}
At the very least it should relax the check only if the argument implements fmt.Formatter(). Although even then, I'm not sure. Realistically, using a non-standard verb is pretty much always an error.
Unfortunately OP is gone so can't ask them to describe their use case.