
# Zlinter - Zig 的 Linter
[](http://github.com/kurtwagner/what-the-zig)
[](https://github.com/KurtWagner/zlinter/actions/workflows/linux.yml)
[](https://github.com/KurtWagner/zlinter/actions/workflows/windows.yml)
[](https://coveralls.io/github/KurtWagner/zlinter?branch=master)
[](https://opensource.org/licenses/MIT)
一个可扩展且可定制的 **Zig linter**(带有 [AST explorer](https://kurtwagner.github.io/zlinter/explorer/)),从源码集成到你的 `build.zig` 中。
**linter** 是一种用来自动检查源代码是否存在风格问题、bug 或可能导致错误的模式的工具,
帮助开发者编写更整洁、更可靠的代码。

## 目录
- [快速入门](#getting-started)
- [自动修复](#autofix)
- [自定义规则](#custom-rules)
- [内置规则](RULES.md)
- [declaration_naming](RULES.md#declaration_naming)
- [field_ordering](RULES.md#field_ordering)
- [field_naming](RULES.md#field_naming)
- [file_naming](RULES.md#file_naming)
- [function_naming](RULES.md#function_naming)
- [import_ordering](RULES.md#import_ordering)
- [max_positional_args](RULES.md#max_positional_args)
- [no_comment_out_code](RULES.md#no_comment_out_code)
- [no_deprecated](RULES.md#no_deprecated)
- [no_empty_block](RULES.md#no_empty_block)
- [no_global_vars](RULES.md#no_global_vars)
- [no_hidden_allocations](RULES.md#no_hidden_allocations)
- [no_inferred_error_unions](RULES.md#no_inferred_error_unions)
- [no_literal_args](RULES.md#no_literal_args)
- [no_literal_only_bool_expression](RULES.md#no_literal_only_bool_expression)
- [no_orelse_unreachable](RULES.md#no_orelse_unreachable)
- [no_panic](RULES.md#no_panic)
- [no_redundant_comptime](RULES.md#no_redundant_comptime)
- [no_swallow_error](RULES.md#no_swallow_error)
- [no_todo](RULES.md#no_todo)
- [no_unsafe_undefined](RULES.md#no_unsafe_undefined)
- [no_unused](RULES.md#no_unused)
- [require_braces](RULES.md#require_braces)
- [require_exhaustive_enum_switch](RULES.md#require_exhaustive_enum_switch)
- [require_fmt](RULES.md#require_fmt)
- [require_labeled_continue](RULES.md#require_labeled_continue)
- [require_doc_comment](RULES.md#require_doc_comment)
- [require_errdefer_dealloc](RULES.md#require_errdefer_dealloc)
- [switch_case_ordering](RULES.md#switch_case_ordering)
- [配置](#configuration)
- [路径](#configure-paths)
- [build.zig 中的规则](#configure-rules-in-buildzig)
- [按目录配置规则](#configure-rules-by-directory)
- [通过注释禁用](#disable-with-comments)
- [命令行参数](#command-line-arguments)
- [优化](#configure-optimization)
- [支持的 zig 版本](#supported-zig-versions)
- [背景](#background)
- [版本控制](#versioning)
- [贡献](CONTRIBUTING.md)
- [发布说明](RELEASES.md)
## 快速入门
`zlinter` 不是一个独立的二进制文件——它被内置到你项目的 `build.zig` 中。
这使得它可以灵活地适应每个项目的需求。只需添加依赖项并
将其连接到一个构建步骤,比如 `zig build lint`:
**1. 将依赖项保存到你的 zig 项目:**
对于 0.14.x:
```
zig fetch --save git+https://github.com/kurtwagner/zlinter#0.14.x
```
对于 0.15.x:
```
zig fetch --save git+https://github.com/kurtwagner/zlinter#0.15.x
```
对于 0.16.x:
```
zig fetch --save git+https://github.com/kurtwagner/zlinter#0.16.x
```
对于 master (0.17.x-dev):
```
zig fetch --save git+https://github.com/kurtwagner/zlinter#master
```
**2. 在你的 `build.zig` 中配置 `lint` 步骤:**
```
const zlinter = @import("zlinter");
// ...
const lint_cmd = b.step("lint", "Lint source code.");
lint_cmd.dependOn(step: {
// Swap in and out whatever rules you see fit from RULES.md
var builder = zlinter.builder(b, .{});
builder.addRule(.{ .builtin = .field_naming }, .{});
builder.addRule(.{ .builtin = .declaration_naming }, .{});
builder.addRule(.{ .builtin = .function_naming }, .{});
builder.addRule(.{ .builtin = .file_naming }, .{});
builder.addRule(.{ .builtin = .switch_case_ordering }, .{});
builder.addRule(.{ .builtin = .no_unused }, .{});
builder.addRule(.{ .builtin = .no_deprecated }, .{});
builder.addRule(.{ .builtin = .no_orelse_unreachable }, .{});
break :step builder.build();
});
```
**3. 运行 linter:**
请记住,第一次运行会比较慢,因为缓存还没有预热:
```
zig build lint
```
你也可以指定路径(有关更多选项,请参见[命令行参数](#command-line-arguments)):
```
zig build lint -- --include src/ file.zig
```
### 替代方案:启用所有内置规则
如果你只是想测试一下 zlinter,你也可以启用所有规则,然后
从命令行选择性地运行规则。很多规则都非常严苛,
所以除了为你的项目测试 zlinter 的规则外,不建议这样做:
1. 在 `build.zig` 中启用所有内置规则
```
const zlinter = @import("zlinter");
const lint_cmd = b.step("lint", "Lint source code.");
lint_cmd.dependOn(step: {
var builder = zlinter.builder(b, .{});
inline for (@typeInfo(zlinter.BuiltinLintRule).@"enum".field_values) |field_value| {
builder.addRule(.{ .builtin = @enumFromInt(field_value) }, .{});
}
break :step builder.build();
});
```
1. 选择性地运行规则:
```
zig build lint -- --rule no_unused no_deprecated
```
## 自动修复
一些 linter 规则支持自动修复某些问题。
例如,假设你的项目配置了这些规则,要自动修复未使用的声明和字段排序问题:
```
# 首先确保你的工作分支是干净的(或者备份你的代码!)
$ git status
# 然后运行修复命令(你可能需要多次运行)
$ zig build lint -- --rule field_ordering --rule no_unused --fix
```
有时可能需要多次运行才能完全解决所有可修复的问题。即,使用 `--fix` 运行,直到它报告应用了 0 个修复。
## 自定义规则
可以将定制的规则添加到你的项目中。例如,也许你真的不喜欢 cats,并拒绝在任何标识符中存在任何 `cats`。参见示例规则 [`no_cats`](./integration_tests/src/no_cats.zig),然后像内置规则一样将其集成到你的 `build.zig` 中:
```
builder.addRule(.{
.custom = .{
.name = "no_cats",
.path = "src/no_cats.zig",
},
}, .{});
```
或者,看看
,这是一个带有 zig 项目的最小自定义规则示例。
## 配置
### 配置路径
`build.zig` 中使用的构建器有一个 `addPaths` 方法,可用于
添加包含和排除的文件及目录。例如,
```
builder.addPaths(.{
.include_dirs = &.{ b.path("engine-src"), b.path("src") },
.exclude_dirs = &.{ b.path("src/android") },
.exclude_files = &.{ b.path("engine-src/generated.zig") },
});
```
将 lint `engine-src/` 和 `src/` 下的 zig 文件,但不包括 `engine-src/generated.zig` 和 `src/android/` 下的任何 zig 文件。
### 在 `build.zig` 中配置规则
`addRule` 接受一个匿名结构体,表示要添加的规则的 `Config`。这些是应用的基础配置。例如,
```
builder.addRule(.{ .builtin = .field_naming }, .{
.enum_field = .{ .warning = .snake_case },
.union_field = .off,
.struct_field_that_is_type = .{ .@"error" = .title_case },
.struct_field_that_is_fn = .{ .@"error" = .camel_case },
});
builder.addRule(.{ .builtin = .no_deprecated }, .{
.severity = .warning,
});
```
其中 `Config` 结构体定义在规则源文件 [`no_deprecated.Config`](./src/rules/no_deprecated.zig) 和 [`field_naming.Config`](./src/rules/field_naming.zig) 中。
### 按目录配置规则
通过在该目录中添加 `zlinter.zon` 文件,可以为该目录及其后代源文件覆盖在 `build.zig` 中配置的规则。例如,
```
// src/lib/zlinter.zon
.{
.rules = .{
.field_naming = .{
.enum_field = .off,
},
},
}
```
将为 `src/lib/` 及其下任何目录中的枚举字段关闭 `field_naming`。其他规则配置字段将继续使用 `build.zig` 中的基础配置。
这要求该规则已经在你的 `build.zig` 中启用并配置。
### 通过注释禁用
#### 禁用下一行
为下一行源代码禁用所有规则或一组明确的规则。
语法:
```
zlinter-disable-next-line [rule_1] [rule_n] [- comment]
```
例如,
```
// zlinter-disable-next-line no_deprecated - not updating so safe
const a = this.is.deprecated();
```
#### 禁用当前行
为当前行源代码禁用所有规则或一组明确的规则。
语法:
```
zlinter-disable-current-line [rule_1] [rule_n] [- comment]
```
例如,
```
const a = this.is.deprecated(); // zlinter-disable-current-line
```
#### 禁用多行
为多行源代码禁用所有规则或一组明确的规则。
语法:
```
zlinter-disable [rule_1] [rule_n] [- comment]
zlinter-enable [rule_1] [rule_n] [- comment]
```
例如,为给定的一组规则禁用多行:
```
// zlinter-disable rule_a rule_b - rationale
var something = doSomethin();
var something_else = doSomethingElse();
// zlinter-disable rule_a rule_b
```
例如,为所有规则禁用多行:
```
// zlinter-disable - rationale
var something = doSomethin();
var something_else = doSomethingElse();
// zlinter-disable
```
如果省略 `zlinter-enable`,直到 EOF 的所有行都将被禁用。
### 命令行参数
```
zig build lint -- [--include ...] [--exclude ...] [--filter ...] [--rule ...] [--fix] [--quiet] [--max-warnings ]
```
- `--include` 在这些路径上运行 linter,忽略 `build.zig` 中定义的包含和排除项,强制解析并 lint 这些路径(如果它们存在)。
- `--exclude` 将这些路径从 lint 中排除。除非与 `--include` 一起使用,否则此参数将与 `build.zig` 中定义的排除项结合使用。
- `--filter` 用于将运行过滤到一组特定的已解析路径。与 `--include` 不同,这会保持 `build.zig` 中定义的包含和排除项不变。
- `--quiet` 仅报告错误(不报告警告)。
- `--max-warnings` 如果警告数量超过此数值则失败。
- `--fix` 用于自动修复某些问题(例如,移除未使用的容器声明) - **仅当你使用源代码控制系统时才使用此功能,因为它可能导致代码丢失!**
例如
```
zig build lint -- --include src/ android/ --exclude src/generated.zig --rule no_deprecated no_unused
```
- 将解析 `src/` 和 `android/` 下的所有 zig 文件,但会排除对 `src/generated.zig` 的 lint;并且
- 只会运行规则 `no_deprecated` 和 `no_unused`。
### 配置优化
`zlinter.builder` 接受 `.optimize`(默认为 `.ReleaseSafe`)。例如,
```
var builder = zlinter.builder(b, .{.optimize = .ReleaseFast });
```
如果你的项目很大,可能值得将 optimize 设置为 `.ReleaseFast`。请记住,第一次运行可能会比较慢,因为它要使用新的优化首次构建模块。
从 0.16.x 开始,`.Debug` 的运行速度要慢得多,因为它使用调试分配器。除非你正在开发 zlinter 或自定义规则,否则应避免使用它。
### 编译单元
大型项目通常有许多具有重叠模块图的编译单元。默认情况下,`zlinter` 首先使用可执行文件,然后是库,接着是测试,然后是目标单元,最后是测试对象。这通常会给 linter 提供你期望的模块/import 上下文,而无需解析构建中的每个编译单元,这样速度较慢。
编译单元用于从你 import 图中的依赖项解析声明。
你可以使用显式选择器覆盖该选择。例如,执行所有可执行文件和一个特定的测试:
```
const exe = b.addExecutable(.{
.name = "my_app",
.root_module = app_module,
});
const unit_tests = b.addTest(.{
.name = "unit_tests",
.root_module = test_module,
});
var builder = zlinter.builder(b, .{});
builder.setCompileUnits(&.{
.exe,
.{ .explicit = unit_tests },
});
```
如果你只想对上下文解析单个可执行文件,而你的项目注册了多个,那么你应该:
```
builder.setCompileUnits(&.{
.{ .explicit = your_exe },
});
```
仅当你有意让每个发现的编译单元都提供上下文时才使用 `.all`。对于大型项目来说,它可能会慢得多,并且可能无法提供多少额外的上下文解析。
## 支持的 zig 版本
计划是支持 `master`(主要是因为这是跟上 zig 变化的一项重要练习)以及最新的前一个版本。
目前,支持 [`0.14.x`](https://github.com/KurtWagner/zlinter/tree/0.14.x)、[`0.15.x`](https://github.com/KurtWagner/zlinter/tree/0.15.x)、[`0.16.x`](https://github.com/KurtWagner/zlinter/tree/0.16.x) 和 [`master`](https://github.com/KurtWagner/zlinter/tree/master)。
如果没有 API 兼容性问题,对规则的修复和改进可能会被 cherry-pick 到旧版本。
一旦 zig 发布 `1.x` 版本,这种情况可能会改变。
## 背景
编写 `zlinter` 的初衷是在我的个人项目中使用。主要的动机是通过构建步骤从源码集成它,以便它可以
1. 在构建时进行定制(例如,自定义规则);并且
2. 与你项目的源代码控制一起进行版本控制(无需兼顾单独的二进制文件)
我将其开源以防它更具有通用价值,并很乐意让它
围绕需求有机地发展,如果这样做有价值的话。
它使用 `std.zig` 和 `std.Build.Configuration` 来构建和分析 zig 源文件。
## 版本控制
`zlinter` 将:
- 遵循与 `zig` 相同的语义化版本控制;
- 为 `zig` `master` 版本发布使用 `master` 分支;并且
- 为 `zig` `0.14.x` 版本发布使用 `0.14.x` 分支。
这可能会改变,特别是当 `zig` 在 `1.x` 处于“稳定”状态时。如果你对此有意见,欢迎在 [#20](https://github.com/KurtWagner/zlinter/issues/20) 上发表评论。