LarsArtmann/gogenfilter
GitHub: LarsArtmann/gogenfilter
一个用于检测和过滤 Go 自动生成代码文件的库,帮助 linter 和静态分析工具快速跳过生成代码,减少误报和不必要的分析开销。
Stars: 0 | Forks: 0
gogenfilter
检测和过滤自动生成的 Go 代码 —— 快速、零配置、表驱动。
一个用于检测和过滤自动生成代码文件的 Go 库。专为需要跳过而非处理生成输出的 **linters**、**静态分析工具**和 **CI pipelines** 而构建。 ## 为什么选择 gogenfilter? 你的 linter 不应在它未编写的文件上浪费时间。来自 sqlc、protobuf、templ、mockgen 及相关工具生成的代码会使 `golangci-lint` 的输出变得混乱,减慢分析速度,并产生误报。 **gogenfilter 解决了这个问题**,它通过两阶段检测引擎快速捕获生成文件 —— 先检查文件名(零 I/O),再检查内容(读取文件)。无需费力处理正则表达式。无需调整 `.golangci.yml`。只需清晰的分离。 ## 支持的生成器 || 工具 | 检测方式 | || --------------------------------------------------------------------- | --------------------------------------------------------------------------------------- | || [sqlc](https://sqlc.dev) | 文件名模式 + 内容标记 | || [templ](https://templ.guide) | `_templ.go` 后缀 + 内容 | || [go-enum](https://github.com/abice/go-enum) | `_enum.go` 后缀 + 内容 | || [protobuf](https://grpc.io/docs/languages/go/quickstart/) | `.pb.go` / `_grpc.pb.go` 后缀 + 内容 | || [oapi-codegen](https://github.com/oapi-codegen/oapi-codegen) | 内容标记 | || [deepcopy-gen](https://pkg.go.dev/k8s.io/gengo/examples/deepcopy-gen) | `zz_generated.*` 前缀 + 内容 | || [wire](https://github.com/google/wire) | `wire_gen.go` 后缀 + 内容 | || [moq](https://github.com/matryer/moq) | `_moq.go` 后缀 + 内容 | || [mockgen](https://github.com/uber-go/mock) | `_mock.go` / `mock_` 前缀 + 内容 | || [stringer](https://pkg.go.dev/golang.org/x/tools/cmd/stringer) | 内容标记 | || **通用兜底** | 任何符合 [go.dev/s/generatedcode](https://go.dev/s/generatedcode) 的 `// Code generated by` | ## 安装 ``` go get github.com/LarsArtmann/gogenfilter ``` ## 快速开始 ``` 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]) } } } ``` ## 配置 `NewFilter` 接受函数选项。请注意,`WithFilterOptions` 返回 `(FilterConfig, error)` —— 在传递给 `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() ``` ## 过滤器选项 || 选项 | 检测方式 | || ---------------- | -------------------------------------------------------------------- | || `FilterSQLC` | 文件名 + `"Code generated by sqlc"` 内容 | || `FilterTempl` | `_templ.go` 后缀 + `templ.Component` 内容 | || `FilterGoEnum` | `_enum.go` 后缀 + `"Code generated by go-enum"` 内容 | || `FilterProtobuf` | `.pb.go` / `_grpc.pb.go` 后缀 + 内容注释 | || `FilterOapi` | `"oapi-codegen"` 内容标记 | || `FilterDeepcopy` | `zz_generated.*` 前缀 + `"Code generated by deepcopy-gen"` 内容 | || `FilterWire` | `wire_gen.go` 后缀 + `"Code generated by Wire"` 内容 | || `FilterMoq` | `_moq.go` 后缀 + `"Code generated by moq"` 内容 | || `FilterMockgen` | `_mock.go` / `mock_` 前缀 + `"Code generated by MockGen"` 内容 | || `FilterStringer` | `"Code generated by \"stringer\""` 内容 | || `FilterGeneric` | 任何 `// Code generated by` 注释(兜底方案) | || `FilterAll` | 启用以上所有选项 | ## 模式匹配 包含和排除模式支持标准的 glob 语法: || 模式 | 含义 | || ------- | --------------------------------------------------------- | || `*` | 任何非分隔符字符序列(单个段落) | || `**` | 零个或多个完整路径段落(跨过 `/`) | **规则:** - **包含模式** 限制范围 —— 仅检查匹配的文件。如果未设置,则考虑所有文件。 - **排除模式** 扩大范围 —— 无论检测结果如何,匹配的文件总是被跳过。 - **不带 `/`** 的模式仅匹配文件名:`*.pb.go` 匹配 `any/path/user.pb.go` - **带 `/`** 的模式匹配完整路径:`internal/*.go` 匹配 `internal/handler.go` ``` f, _ := gogenfilter.NewFilter( gogenfilter.WithIncludePatterns("pkg/**"), gogenfilter.WithExcludePatterns("**/*.pb.go"), ) ``` ## 底层检测 API 跳过 `Filter` 结构体,直接调用检测函数: ``` // 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) ``` ## 过滤器 API 参考 ``` 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 ``` ## 错误处理 所有错误都带有结构化代码,并支持 `errors.Is` 匹配: ``` 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 配置发现 查找 sqlc 配置文件并提取输出目录: ``` // 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"}, ) ``` ## 设计决策 - **两阶段检测** —— 文件名检查几乎是零开销的。内容检查仅在文件名模式不匹配时运行。默认即快速。 - **表驱动检测器** —— 所有 11 个生成器都定义在一个 `[]detector` 表中。添加新生成器只需添加一个结构体字面量。 - **不可变 Filter** —— `NewFilter` 返回一个完全构造好的、线程安全的 Filter。没有变更方法。 - **`fs.FS` 抽象** —— 使用 `fstest.MapFS` 进行测试,使用 `os.DirFS` 运行。没有文件系统耦合。 - **派生常量** —— `AllFilterOptions()`、`AllFilterReasons()`、`AllGeneratorOptions()` 均派生自检测器表。不会遗漏任何内容。 ## API 稳定性 本库遵循 [Go 模块版本控制](https://go.dev/doc/modules/version-numbers): - **v1.0.0 之前** (`v0.x.y`):API 可能会在次要版本之间发生变化。我们会尽量减少破坏性更新,但保留根据用户反馈重命名或调整公共符号的权利。 - **v1.0.0 之后**:适用标准的 Go 兼容性保证。没有主版本号升级就不会有破坏性更新。 核心的 `Filter` / `DetectReason` API 是稳定的,不太可能发生改变。 ## 贡献 欢迎贡献!请随时提交 Pull Request。 ## 许可证 [MIT](LICENSE) © Lars Artmann标签:EVTX分析, Go, Golang, golangci-lint, Lint, mockgen, protobuf, Ruby工具, sqlc, stringer, templ, wire, 云安全监控, 代码分析, 代码检测, 代码生成, 代码过滤, 凭证管理, 威胁情报, 安全编程, 开发者工具, 开源库, 开源框架, 开源组件, 持续集成, 搜索引擎爬虫, 数据管道, 日志审计, 渗透测试工具, 自动生成代码, 软件工程, 零配置, 静态分析