Detect and filter auto-generated Go code — fast, zero-config, table-driven.
A Go library for detecting and filtering auto-generated code files. Built for linters, static analysis tools, and CI pipelines that need to skip generated output — not fight it.
Your linter shouldn't waste time on files it didn't write. Generated code from sqlc, protobuf, templ, mockgen, and friends clutters golangci-lint output, slows down analysis, and creates false positives.
gogenfilter solves this with a two-phase detection engine that catches generated files fast — filename first (zero I/O), content second (reads the file). No regex hacking. No .golangci.yml tuning. Just clean separation.
|| Tool | Detection |
|| --------------------------------------------------------------------- | --------------------------------------------------------------------------------------- |
|| sqlc | Filename patterns + content markers |
|| templ | _templ.go suffix + content |
|| go-enum | _enum.go suffix + content |
|| protobuf | .pb.go / _grpc.pb.go suffix + content |
|| oapi-codegen | Content marker |
|| deepcopy-gen | zz_generated.* prefix + content |
|| wire | wire_gen.go suffix + content |
|| moq | _moq.go suffix + content |
|| mockgen | _mock.go / mock_ prefix + content |
|| stringer | Content marker |
|| Generic fallback | Any // Code generated by per go.dev/s/generatedcode |
go get github.com/LarsArtmann/gogenfilterpackage main
import (
"fmt"
"log"
"github.com/LarsArtmann/gogenfilter"
)
func main() {
opts, err := gogenfilter.WithFilterOptions(gogenfilter.FilterAll)
if err != nil {
log.Fatal(err)
}
f, err := gogenfilter.NewFilter(opts)
if err != nil {
log.Fatal(err)
}
// Batch filter multiple files
paths := []string{"db/models.go", "api/user.pb.go", "handler.go"}
results, err := f.FilterPaths(paths)
if err != nil {
log.Fatal(err)
}
for i, filtered := range results {
if filtered {
fmt.Println("skipping generated:", paths[i])
}
}
}NewFilter accepts functional options. Note that WithFilterOptions returns (FilterConfig, error) — check the error before passing to NewFilter:
// Filter all known generated code
opts, _ := gogenfilter.WithFilterOptions(gogenfilter.FilterAll)
f, _ := gogenfilter.NewFilter(opts)
// Filter specific generators only
opts, _ = gogenfilter.WithFilterOptions(gogenfilter.FilterSQLC, gogenfilter.FilterTempl)
f, _ = gogenfilter.NewFilter(opts)
// Include/exclude patterns with ** glob support
opts, _ = gogenfilter.WithFilterOptions(gogenfilter.FilterAll)
f, _ = gogenfilter.NewFilter(
opts,
gogenfilter.WithIncludePatterns("pkg/**", "internal/*.go"),
gogenfilter.WithExcludePatterns("**/*.pb.go", "mocks/*"),
)
// Pluggable filesystem for testing
opts, _ = gogenfilter.WithFilterOptions(gogenfilter.FilterAll)
f, _ = gogenfilter.NewFilter(opts, gogenfilter.WithFS(myFS))
// Disabled — passes everything through
f, _ := gogenfilter.NewFilter()|| Option | Detection |
|| ---------------- | -------------------------------------------------------------------- |
|| FilterSQLC | Filename + "Code generated by sqlc" content |
|| FilterTempl | _templ.go suffix + templ.Component content |
|| FilterGoEnum | _enum.go suffix + "Code generated by go-enum" content |
|| FilterProtobuf | .pb.go / _grpc.pb.go suffix + content comment |
|| FilterOapi | "oapi-codegen" content marker |
|| FilterDeepcopy | zz_generated.* prefix + "Code generated by deepcopy-gen" content |
|| FilterWire | wire_gen.go suffix + "Code generated by Wire" content |
|| FilterMoq | _moq.go suffix + "Code generated by moq" content |
|| FilterMockgen | _mock.go / mock_ prefix + "Code generated by MockGen" content |
|| FilterStringer | "Code generated by \"stringer\"" content |
|| FilterGeneric | Any // Code generated by comment (catch-all) |
|| FilterAll | Enables all of the above |
Include and exclude patterns support standard glob syntax:
|| Pattern | Meaning |
|| ------- | --------------------------------------------------------- |
|| * | Any sequence of non-separator characters (single segment) |
|| ** | Zero or more complete path segments (crosses /) |
Rules:
- Include patterns restrict scope — only matching files are checked. If none are set, all files are considered.
- Exclude patterns broaden scope — matching files are always skipped, regardless of detection.
- Patterns without
/match the filename only:*.pb.gomatchesany/path/user.pb.go - Patterns with
/match the full path:internal/*.gomatchesinternal/handler.go
f, _ := gogenfilter.NewFilter(
gogenfilter.WithIncludePatterns("pkg/**"),
gogenfilter.WithExcludePatterns("**/*.pb.go"),
)Skip the Filter struct and call detection functions directly:
// Filename + content (two-phase)
gogenfilter.IsSQLCGenerated("db/models.go", content)
gogenfilter.IsTemplGenerated("page_templ.go", content)
gogenfilter.IsProtobufGenerated("user.pb.go", content)
// Combined detection with variadic options (no I/O — caller provides content)
reason := gogenfilter.DetectReason("file.go", content,
gogenfilter.FilterSQLC,
gogenfilter.FilterGeneric,
)
// reason == gogenfilter.ReasonSQLC or gogenfilter.ReasonNotFiltered
// From an io.Reader
reason, err := gogenfilter.DetectReasonReader("file.go", reader,
gogenfilter.FilterSQLC,
)
// Detailed result with trace info
opts, _ := gogenfilter.WithFilterOptions(gogenfilter.FilterAll)
f, _ := gogenfilter.NewFilter(opts, gogenfilter.WithFS(myFS))
result, err := f.FilterDetailed("db/models.go")
fmt.Printf("filtered=%v reason=%s trace=%s\n",
result.Filtered, result.Reason, result.Trace)opts, _ := gogenfilter.WithFilterOptions(gogenfilter.FilterAll)
f, _ := gogenfilter.NewFilter(opts)
filtered, err := f.Filter("db/models.go") // (bool, error)
result, err := f.FilterDetailed("db/models.go") // (FilterResult, error)
results, err := f.FilterPaths(paths) // ([]bool, error)
detailed, err := f.FilterPathsDetailed(paths) // ([]FilterResult, error)
f.IsEnabled() // bool
f.FilterReasons() // []FilterReason
f.String() // human-readable debug stateAll errors carry structured codes and support errors.Is matching:
import "errors"
root, err := gogenfilter.FindProjectRoot(
"/some/path",
[]string{"go.mod"},
)
if err != nil {
// Check by sentinel error
if errors.Is(err, gogenfilter.ErrProjectRootNotFound) {
// handle not found
}
// Get the structured error code
fmt.Println(err.ErrorCode()) // project_root_not_found
}Find sqlc configuration files and extract output directories:
// Find sqlc.yaml files in the project
configs, err := gogenfilter.FindSQLCConfigs([]string{"."}) // map[string]string
configs, err = gogenfilter.FindSQLCConfigsFS(fsys, []string{"."})
// Extract output directories from configs
dirs, err := gogenfilter.GetSQLOutputDirs([]string{"."}) // []string
dirs, err = gogenfilter.GetSQLOutputDirsFS(fsys, []string{"."})
// Find the project root by walking up for marker files
root, err := gogenfilter.FindProjectRoot(
".",
[]string{"go.mod", "sqlc.yaml"},
)- Two-phase detection — Filename checks are free. Content checks only run when filename patterns don't match. Fast by default.
- Table-driven detectors — All 11 generators are defined in a single
[]detectortable. Adding a new generator is one struct literal. - Immutable Filter —
NewFilterreturns a fully constructed, thread-safe Filter. No mutating methods. fs.FSabstraction — Test withfstest.MapFS, run withos.DirFS. No filesystem coupling.- Derived constants —
AllFilterOptions(),AllFilterReasons(),AllGeneratorOptions()are all derived from the detector table. Nothing to forget.
This library follows Go module versioning:
- Pre-v1.0.0 (
v0.x.y): The API may change between minor versions. We minimize breaking changes, but reserve the right to rename or adjust public symbols based on user feedback. - Post-v1.0.0: Standard Go compatibility guarantees apply. No breaking changes without a major version bump.
The core Filter / DetectReason API is stable and unlikely to change.
Contributions are welcome! Please feel free to submit a Pull Request.
MIT © Lars Artmann