Skip to content

LarsArtmann/gogenfilter

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

572 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

gogenfilter

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

Go Reference CI Go Report Card 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 | 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 |

Install

go get github.com/LarsArtmann/gogenfilter

Quick Start

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

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

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)

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

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)

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

Error Handling

All 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
}

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

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 11 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 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.

Contributing

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

License

MIT © Lars Artmann

Packages

 
 
 

Contributors