Skip to content

LarsArtmann/gogenfilter

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

746 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

gogenfilter

Detect and filter auto-generated Go code — fast, zero-config, table-driven.

Go Reference CI

License: MIT

Documentation · API Reference


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.

Why gogenfilter?

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.

Supported Generators

Tool Detection
sqlc models.go, querier.go, query.sql.go, batch.go, *.sql.go + content
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 + .go suffix + content
wire wire_gen.go suffix + content
moq _moq.go, _moq_test.go suffix + content
mockgen _mock.go suffix + content
stringer Content marker
mockery mock_ prefix + content
ent Content marker
gqlgen Content marker
easyjson _easyjson.go suffix + content
msgp Content marker
counterfeiter fake_ prefix + content
go-swagger Content marker
Generic fallback Any // Code generated by comment

Install

go get github.com/LarsArtmann/gogenfilter/v3

Quick Start

package main

import (
    "fmt"
    "log"

    "github.com/LarsArtmann/gogenfilter/v3"
)

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])
        }
    }
}

Configuration

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()

Filter Options

Option Detection
FilterSQLC models.go, querier.go, query.sql.go, batch.go, *.sql.go + content
FilterTempl _templ.go suffix + content
FilterGoEnum _enum.go suffix + content
FilterProtobuf .pb.go, _grpc.pb.go suffix + content
FilterOapi Content marker
FilterDeepcopy zz_generated.* prefix + .go suffix + content
FilterWire wire_gen.go suffix + content
FilterMoq _moq.go, _moq_test.go suffix + content
FilterMockgen _mock.go suffix + content
FilterStringer Content marker
FilterMockery mock_ prefix + content
FilterEnt Content marker
FilterGqlgen Content marker
FilterEasyjson _easyjson.go suffix + content
FilterMsgp Content marker
FilterCounterfeiter fake_ prefix + content
FilterGoSwagger Content marker
FilterGeneric Any // Code generated by comment
FilterAll Enables all of the above

Pattern Matching

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.go matches any/path/user.pb.go
  • Patterns with / match the full path: internal/*.go matches internal/handler.go
f, _ := gogenfilter.NewFilter(
    gogenfilter.WithIncludePatterns("pkg/**"),
    gogenfilter.WithExcludePatterns("**/*.pb.go"),
)

Low-Level Detection API

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)
gogenfilter.IsMockeryGenerated("mock_service.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

// Two-phase detection in one call (reads file from filesystem)
reason := gogenfilter.DetectReasonFile("file.go", gogenfilter.FilterSQLC)
reason, err := gogenfilter.DetectReasonFileFS(fsys, "file.go", gogenfilter.FilterAll)

// 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)

// Filter with pre-read content — avoids double I/O
filtered, err := f.FilterWithContent("db/models.go", content)
result, err := f.FilterDetailedWithContent("db/models.go", content)

Filter API Reference

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)
filtered, err = f.FilterWithContent("file.go", data) // (bool, error) — avoids double I/O
result, err   = f.FilterDetailedWithContent("file.go", data) // (FilterResult, error)

f.IsEnabled()           // bool
f.FilterReasons()       // []FilterReason
f.String()              // human-readable debug state

Error Handling

All errors carry structured codes and support errors.Is matching:

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
}

SQLC Config Discovery

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"},
)

Project Scanning

Scan an entire project for generated files:

result, err := gogenfilter.ScanProject(os.DirFS("."))
if err != nil {
    log.Fatal(err)
}

fmt.Printf("Scanned %d files, found %d generated\n", result.ScannedFiles, len(result.Files))
for _, gen := range result.Generators {
    fmt.Printf("  %s: %d files\n", gen, len(result.ByGenerator[gen]))
}

// Get exclusion patterns for linter configs
for _, excl := range result.Exclusions {
    fmt.Printf("Pattern: %s (%s)\n", excl.Pattern, excl.Reason)
}

Design Decisions

  • Two-phase detection — Filename checks are free. Content checks only run when filename patterns don't match. Fast by default.
  • Table-driven detectors — All 18 generators are defined in a single []detector table. Adding a new generator is one struct literal.
  • Immutable FilterNewFilter returns a fully constructed, thread-safe Filter. No mutating methods.
  • fs.FS abstraction — Test with fstest.MapFS, run with os.DirFS. No filesystem coupling.
  • Derived constantsAllFilterOptions(), AllFilterReasons(), AllGeneratorOptions() are all derived from the detector table. Nothing to forget.

API Stability

This library is at v3 and follows Go module versioning. Standard Go compatibility guarantees apply — no breaking changes without a major version bump. The core Filter / DetectReason API is stable.

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

License

MIT © Lars Artmann

About

Detect and filter auto-generated Go code — fast, zero-config, table-driven. Supports sqlc, protobuf, templ, mockgen, wire, stringer, and more.

Topics

Resources

License

Contributing

Stars

3 stars

Watchers

0 watching

Forks

Packages

 
 
 

Contributors