KrishRVH/boringlint
GitHub: KrishRVH/boringlint
boringlint 是一个强制使用受限 Go 方言的静态分析器,通过禁止迭代器和方法局部泛型等特性来保持代码的过程化与简洁。
Stars: 0 | Forks: 0
# boringlint
[](https://pkg.go.dev/github.com/KrishRVH/boringlint)
`boringlint` 强制使用一种刻意受限的 Go 方言:推崇直接的数据和
控制流,而非掩盖这两者的语言机制。
其策略受 ThePrimeagen 的
[*I am done with Golang*](https://youtu.be/WqSWZuGS9pc) 启发:保持 Go 的过程化特性,
让控制流和开销可见,并保持代码库的熟悉感。
这是一种带有主观倾向的策略,并不意味着被拒绝的特性是无效的
Go。v0.9.5 是 stable 版本 Go 1.27 发布之前,针对 v1 规则范围和公共
analyzer API 的最终发布候选版本。v1.0 版本的发布仍然
取决于其能否通过 stable 版本 Go 1.27 toolchain 的兼容性测试。
## 安装与运行
使用 Go 1.25 或更新版本安装此命令,且使用的 toolchain 版本不得低于
将要分析的代码版本:
```
go install github.com/KrishRVH/boringlint/cmd/boringlint@latest
boringlint ./...
```
该命令还实现了 `go vet` 工具协议:
```
go vet ./...
go vet -vettool="$(command -v boringlint)" ./...
```
运行这两个命令。`-vettool` 会使用 `boringlint` 替代标准的 vet
analyzer;它不会在标准集合中添加检查。
## 抑制
刻意去除了针对单个位置的抑制:`//nolint` 是 golangci-lint 的
约定,在 standalone 和 vettool 模式下会被忽略,而且一个带有
站点级逃生舱的策略 linter 将不再成其为策略。要仅运行一个 analyzer,可以在
调用时指定它:
```
boringlint -noiterator ./...
go vet -vettool="$(command -v boringlint)" -noiterator ./...
```
显式的 package 模式会在两种模式下限制分析范围。一个自定义的
[golangci-lint v2 module-plugin 适配器](https://golangci-lint.run/docs/plugins/module-plugins/)
可以根据规则名称注册导出的 analyzer,之后 golangci-lint 将
识别 `//nolint:noiterator`;`boringlint` 并不内置该适配器。
## Go 版本支持
`boringlint` 的源码项目最低版本要求为 Go 1.23,该版本引入了
[range-over-function 和 `iter`](https://go.dev/doc/go1.23)。新的 Go 版本
只有在两种命令模式都通过兼容性测试后才会受支持;
当前的上限是 Go 1.27,已通过 Go 1.27rc2 toolchain 进行了测试。构建、
安装或运行 `boringlint` 需要 Go 1.25 或更新版本。将其
analyzer 导入到自定义 driver 中具有相同的最低版本限制,因为 module 的
[`go` 指令是强制性的最低版本要求](https://go.dev/ref/mod#go-mod-file-go)。
只要 standalone 或 vettool 二进制程序是使用受支持的、且版本不低于
所分析源码的 toolchain 构建的,它就可以分析声明使用 Go 1.23 或 1.24 的项目。
`nogenericmethod` 会分析 Go 1.27 语法,因此其二进制程序必须使用 Go
1.27 或更新版本构建。该代码仓库目前使用 Go 1.27rc2 测试该规则:
```
GOTOOLCHAIN=go1.27rc2 go install github.com/KrishRVH/boringlint/cmd/boringlint@latest
```
Go 1.27 目前仍是发布候选版本。v0.9.5 验证了其当前的语法和
工具行为,但在最终的 toolchain 通过相同的兼容性测试之前,v1.0 不会声明
提供稳定的 Go 1.27 支持。
设定 Go 1.25 toolchain 的最低版本要求是刻意为之。分析 driver 会消耗
[compiler 导出数据](https://github.com/golang/tools/blob/v0.48.0/go/gcexportdata/gcexportdata.go#L5-L23)
并实现 [`go vet -vettool` 协议](https://github.com/golang/tools/blob/v0.48.0/go/analysis/unitchecker/unitchecker.go#L82-L96),
因此普通的 Go 源码兼容性并不能使旧的 driver
具备向前兼容性。[`x/tools` v0.36.0](https://github.com/golang/tools/blob/v0.36.0/go.mod)
是在 [v0.37.0 提高该要求之前](https://github.com/golang/tools/blob/v0.37.0/go.mod),最后一个最低版本要求为 Go 1.23 的发布版本,但 Go 的
[子仓库通常仅支持之前的两个 Go 发布版本和 tip](https://go.dev/wiki/X-Repositories)。
将 driver 冻结在旧版本会为了更低的构建版本要求而牺牲掉不受支持的
compiler 集成。兼容的
[`x/tools` v0.48.0](https://github.com/golang/tools/blob/v0.48.0/go.mod) 要求
使用 Go 1.25。
只有当正确支持新发布的 Go 版本需要更新的分析 driver 时,最低版本要求才会
提高。每次提高都必须在 `go.mod` 和本节中明确说明,
并且在声明支持该源码版本之前,standalone 和 vettool 兼容性测试都必须通过。
## 规则
### `noiterator`
拒绝:
- 直接导入 `iter`;
- Go 1.23 的 range-over-function;
- 项目类型、函数和方法声明中具有 iterator 形状的类型,
包括 constraint、字段、参数和结果,即使
允许的 iterator 类型具有不同的有效 yield 签名;
- 项目类型声明中包含具有 iterator 形状 term 的 constraint 名称,包括
消除了这些 term 的混合 union 和 intersection。
每个独立命名的隐藏 constraint term 都会在该 term 处被报告。
针对整个类型参数的诊断不会取代对其 constraint 内部不同命名 term 的诊断。
```
import "iter" // rejected
type Sequence func(func(int) bool) // rejected
type MaybeSequence = dependency.SequenceOrSlice // rejected
func Values(yield func(int) bool) { // rejected
// ...
}
for value := range dependency.Values() { // rejected
// ...
}
```
对 array、slice、string、map、channel 和 integer 的 range 仍然是
允许的。依赖项返回的值也是允许的,这样就可以在
不命名 iterator 类型的情况下将它们实体化:
```
values := slices.Collect(dependency.Values())
```
该规则有意控制项目类型、函数和方法声明中的 import、range 语句和
具有 iterator 形状的类型。变量声明和函数字面量签名中
具有 iterator 形状的类型仍然是被允许的;该规则
并不试图在表达式层面上,禁止所有碰巧属于 iterator 形状类型的值。
#### 为什么
[`iter.Seq` 协议](https://pkg.go.dev/iter#Seq) 是基于推送的:生产者通过
调用调用者提供的 `yield` 函数来推进序列。
range-over-function 将该 callback 呈现为普通的循环语法;
[`iter.Pull`](https://pkg.go.dev/iter#Pull) 通过 `next` 恢复了由调用者驱动的推进方式,
但放弃序列的调用者必须调用 `stop`。
在边界处将依赖项的 iterator 实体化,可以将具有 iterator 形状的类型排除在
项目类型、函数和方法声明之外,并恢复对具体集合的普通 range 操作。
### `nogenericmethod`
拒绝在 Go 1.27 中引入的方法局部类型参数,无论是在方法
声明还是使用中。泛型函数仍然是被允许的,仅使用其 receiver 类型参数的
方法也同样被允许。
```
func (box Box[T]) Map[U any](convert func(T) U) U { // rejected
return convert(box.value)
}
func Map[T, U any](box Box[T], convert func(T) U) U { // allowed
return convert(box.value)
}
```
#### 为什么
[Go 1.27](https://go.dev/doc/go1.27#language) 既不允许 interface 方法带有类型参数,
也不允许将泛型方法作为 interface 方法的实现。在
泛型 receiver 上,方法局部的类型参数会在 receiver 实例化之上叠加方法实例化。
package 级别的泛型函数会将 receiver 作为普通的参数,并将泛型操作保留在单一的
package 级别命名空间中。
## 作为 package 使用
analyzer 是普通的 `go/analysis` 值,可以被组合到
自定义 driver 中:
```
package main
import (
"github.com/KrishRVH/boringlint"
"golang.org/x/tools/go/analysis/multichecker"
)
func main() {
multichecker.Main(boringlint.NoIterator, boringlint.NoGenericMethod)
}
```
## 开发
[mise](https://mise.jdx.dev/) 是开发者接口:
```
mise run tasks
mise run standards
mise run standards:check
```
完整的测试门会检查格式化、module 完整性、静态分析、
漏洞、密钥、竞态行为、命令集成、Go 1.25
兼容性以及 Go 1.27 行为。
### 发布
仅发布确切通过了本地和远程测试门的 commit:
1. 检查已锁定的 mise 工具和 CI mise runtime。如果工具锁定发生变化,请运行
`mise run lock`;切勿手动编辑 lockfile。
2. 运行 `mise run standards:check`,检查完整的 diff,并使用
Conventional Commit 进行 commit。
3. 确认 `git status --short` 为空,推送 commit,并等待 CI 在
该确切 SHA 上通过。
4. 从通过 CI 的确切 SHA 创建 annotated tag,命令为
`git tag -a -m vX.Y.Z vX.Y.Z `,然后运行
`git push origin vX.Y.Z`。
5. 从该 tag 发布 GitHub Release,例如使用
`gh release create vX.Y.Z --verify-tag --generate-notes`。
在其对应 commit 的 CI 运行仍处于挂起状态时,请勿发布 tag 或 release。
## 许可证
MIT
标签:EVTX分析, Go, Ruby工具, SOC Prime, 代码规范, 开发工具, 日志审计, 错误基检测, 静态代码分析