Could go test make running static analyzers easier?

205 views
Skip to first unread message

Andrew Harris

unread,
Jul 19, 2025, 7:03:21 PMJul 19
to golang-nuts
x/tools/go/analysis states "[a]n analysis reports mistakes is informally called a 'checker'". While checkers are extensional - and Go is tangibly not sine qua non about extensions - the foundations are there and they seem appropriate in the right circumstances. Analyses employed by `go vet` or `gopls` demonstrate the utility of checkers within the standard Go distribution.

It seems like, while there are enough options out there, there's no really immediate way to run a checker from a third party from the standard distribution. One reasonable question is whether this really is a gap in the ecosystem, or if it's fine to leave alone. Static analyzers don't have to be checkers, and the ones that aren't that sharp don't make sense here. Another question, though: for well-formulated checkers, could `go test` be a platform for running them? I'm wondering if it'd be plausible to run a checker very much like a test function. Or if I'm missing something that makes the notion obviously implausible. As a very crude illustration:

```
-- local_test.go --
package local

import (
    "testing"

    "github.com/some/checker"
)

func TestChecker() {
    testing.Analyze(checker.Check()
}
```

The details would be a bit magic, with a fair amount of implicit behavior:
- There'd be no *testing.T argument (not sure about this, but just for illustration...)
- `checker.Check()` would not be an `analysis.Analyzer`, but eventually serves to partially initialize one with analysis logic. Roughly, I think the type of `checker.Check()` could be some interface. Indirection and assertions behind the scenes could be employed such that `analysis` isn't an explicit dependency in `local` or `testing`; `analysis` would likely be a dependency in `checker`.
- the `analysis.Analyzer` is employed by an `analysis.Pass` populated by `testing` - the set of files it examines are naturally described by the invocation of `testing`
- `testing` would arrange for this `analysis.Pass` to run once before other tests, aggregating all `testing.Analyze` inputs to run with that pass
- problems detected by checkers would manifest like other fatal `testing` outcomes: halting, failing, and logging a relevant message
- a really very, very magic thing would be for `gopls` to detect `testing.Analyze` calls ...

The closest prior art here I've found is https://github.com/surullabs/lint, it has a stated purpose of having "lint checks to be part of a regular go build + go test workflow". But it's also using `os/exec` to run linters - that seems like a red flag. It also doesn't stress checkers versus linting for style, etc. Also in terms of prior art, I don't think it's entirely unnatural to end up with something ad-hoc along the same lines when developing around code generation or reflection.

I should note I'm definitely seeing this because of discussion around struct tags on https://github.com/golang/go/issues/73787, https://github.com/golang/go/issues/74472#issuecomment-3061802569. https://github.com/golang/go/issues/74376 seems like an example of using static analysis over json tags in a way that would be possible to `go test`. It's not necessary with `go vet` and `gopls` coverage, but I think that putting equivalent pieces together isn't really convenient for third party solutions ... maybe it could be?

Brian Candler

unread,
Jul 20, 2025, 4:42:03 AMJul 20
to golang-nuts
When you do "go test", it compiles a binary then runs that binary.

Therefore, you're saying that
testing.Analyze(checker.Check())
should be run as part of a compiled binary, introspecting itself. Does the compiled binary even carry enough information about the original source to be able to do that, for a useful set of analyses?

I think that's why people are using `os/exec` to run linters - they generally work on source code, not object code.

Sean Liao

unread,
Jul 20, 2025, 6:03:12 AMJul 20
to golang-nuts
You can look at https://go.dev/issue/61336 and some research I did in https://go.dev/issue/71478

- sean

--
You received this message because you are subscribed to the Google Groups "golang-nuts" group.
To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts...@googlegroups.com.
To view this discussion visit https://groups.google.com/d/msgid/golang-nuts/8f68ed1a-1664-4db7-9773-d80ba1febc5bn%40googlegroups.com.

Axel Wagner

unread,
Jul 20, 2025, 7:12:48 AMJul 20
to Brian Candler, golang-nuts
On Sun, 20 Jul 2025 at 10:42, 'Brian Candler' via golang-nuts <golan...@googlegroups.com> wrote:
Does the compiled binary even carry enough information about the original source to be able to do that, for a useful set of analyses?

It could:

//go:embed *.go
var source embed.FS

`go test` can choose to embed whatever it wants into the binary.

--
You received this message because you are subscribed to the Google Groups "golang-nuts" group.
To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts...@googlegroups.com.

Andrew Harris

unread,
Jul 21, 2025, 3:18:07 PMJul 21
to golang-nuts
Thanks for the links, Sean. I was unaware of these issues and they seem to be working towards a very similar goal - I'll drop a note somewhere appropriate that links in Axel's typed struct tags issue.

Re: source text, it seems like embedding at some point in routines under src/cmd/go/internal/test/test.go and src/cmd/go/internal/load/test.go would work out. It seems like the ideas in issues Sean mentioned or my fingerpainting are last-mile stuff, because the changes are small, but they also would happen in a fair number of disparate places.

Reply all
Reply to author
Forward
0 new messages