smith check, fmt, lint 和 lsp
smith fmt、smith lint 和 smith lsp 分别是 clang-format、clang-tidy 和 clangd,它们通过来自与编译器相同 LLVM 的 smith 链接,因此每台机器都能以相同方式格式化和检查。
针对项目:无参数
这些动词在每个项目中含义相同,并读取其 build.c 声明的配置;项目无需为它们编写任务。
smith fmt重写 git 能看到的每个 C、C++、Objective-C 和.smith文件(已跟踪和未跟踪但未被忽略的;.c .h .m .mm .cpp .cc .cxx .hpp .hh .smith .metal),使其符合.clang-format规范(.metal文件作为 C++ 格式化,因为 clang-format 对其不识别的扩展名会使用 C++ 语言)。smith fmt --dry-run(或-n,配合--Werror或-i)和smith lint --quiet也是项目形式的命令:仅使用选项,不含文件名,因此它们从不等待标准输入;任何其他选项或文件名都会使它们变成工具(smith fmt --assume-filename=x.c会读取标准输入)。.clang-format-ignore中列出的文件(clang-format 自身会读取它:例如生成的parser.c、测试夹具)将被跳过。在 git 仓库之外,它会提示需要指定文件名(smith fmt -i <files>)。smith lint首先构建编译器会读取的内容(生成的头文件),然后使用.clang-tidy对项目自身的源文件(在构建编译中的smith_object和构建从未编译的源的smith_compile_command)进行 lint 检查,两者并行进行。不会检查.smith/、build/输出或包文件。每个.smith源文件都会像smith lint x.smith -- <flags>那样被 lint:构建自身的文件使用其编译命令,git 能看到的其他.smith文件使用最近的编译命令,但.clang-format-ignore中列出的文件除外(这是唯一一个列出非普通源文件的列表:测试输入、故意错误的夹具、生成和复制文件;smith fmt也会读取它,因此没有第二个列表)。发现的问题会使 lint 失败;其最后一行显示linted N files(smith check下显示lint ok: N files),如果一个项目有.smith源文件但 lint 检查了零个,则会失败。文件夹中的.clang-tidy(例如设置了InheritParentConfig: true并包含其他检查)会在该文件夹中生效。smith lint是增量的:clang-tidy 对某个文件的结果会缓存在~/.cache/smith/lint中,当文件、其包含的内容(对于.smith文件,还包括其导入模块的生成头文件)、其.clang-tidy和 smith 本身未发生变化时,会直接重放缓存结果,因此最后一行会显示smith lint: linted 180 files (3 run, 177 cached, 1.2 s),未更改的树不会对任何文件运行 clang-tidy。SMITH_LINT_CACHE=0可关闭缓存,SMITH_LINT_CACHE=why会说明文件为何运行,smith cache clean lint会清空缓存。clang-tidy 失败的文件会在最后几行中列出(smith lint: c.c: exited with status 1,或was killed by signal 9,这种情况不会打印其自身的输出),并且在其自身输出的末尾也会有所体现。smith lint --changed[=<ref>]仅检查自与<ref>合并基准以来发生变更的文件(默认:分支的上游,否则为origin/HEAD的分支或origin/main,绝不会是本地main;如果都不存在,则报错并命名--changed=<ref>;打印的行会指明基准名称;编辑和未跟踪的文件都算在内),以及包含或导入它们的文件;变更的.clang-tidy、构建文件、shim/或patches/会选择所有文件,如果没有文件受到影响,则会打印相关信息并成功退出。它也只构建那些文件所依赖的内容。普通smith lint会检查所有文件(CI)。一个等待网络无法提供的包(例如云沙箱中的 tree-sitter 解析器)的文件会按名称跳过(smith lint: skipped runtime/tree_sitter_data.c: the tree-sitter parser it embeds cannot be fetched (<url>)),其余文件继续被 lint;CI 环境(设置了CI或SMITH_PRIORITY=ci)则会报错退出。在尚未构建的项目(没有compile_commands.json)中执行smith lint FILE会停止并提示先运行一次smith build smith_needs,而不是将缺失的生成头文件报告为错误。smith docs检查knowledge/和技能(smith-docs)。smith check按顺序运行 fmt(clang-format 的 dry run)、docs、lint、test 和 prove,并且不会在失败时停止:每个部分会打印一行,格式为smith check: <part> ok: <detail> (<n> s wall, <n> s cpu)或FAILED (status N),并附带失败信息的有限摘录和完整日志的路径(.smith/log/<part>.log;通过部分的输出也会保存在那里;smith check -v会实时打印所有内容),然后打印smith check: promises: N(smith 信任的内容,来自一个必要时创建的默认构建),最后一行是smith check: ok或smith check: failed: <parts>;如果有任何部分失败,退出状态为 1。prove 行会显示覆盖率,例如smith check: prove ok (4 of 23 functions): ...(函数的数量是指项目中具有自身合约的函数;0 of N functions表示没有需要证明的内容,这是成功的;覆盖率低不会导致失败)。每个部分的行以其 wall 时间和 CPU 时间结束((91.2 s wall, 480.1 s cpu):CPU 时间是用户加系统时间,包括该部分及其等待的进程)。它不接受除了-D选项和-v之外的参数。在非 macOS 主机上,它会使用--skip-macos运行测试,并打印相关信息(the macOS-only tests do not run on this host)。smith test以表格形式结束,列出构建的任务(.smith/task/<name>步骤,作为 smith 自身的test-<name>),按耗时从高到低排序:wall、CPU 和系统时间,以及每个任务启动的 smith 进程数(通过SMITH_START_LOG记录,smith 会在main开始时追加内容;清空环境的任务不会被计数);默认显示最慢的八个任务,smith check -v或SMITH_ACCOUNT=all会显示全部,SMITH_ACCOUNT=no则不显示任何任务。任务通过smith --smith-account <file> <name> <command...>运行,该命令会写入相应信息。- 新语法中的
.smith文件不由 clang-format 处理 (#1890, #1886, #2040):smith fmt会将.smith文件发送给 smith 自身的格式化程序(front::format),除非该文件使用的是当前语法(某行以只有当前语法或 C 才有的形式开头,当前解析器无错误地读取它,或者它在新型语法中的第一个错误有修复方案,例如//注释)或者存在解析错误但没有一行是只有新型语法才有的(fn、let、pub、#):它使用统一的样式,没有.clang-format(任何一致的缩进都会转换为 4 个空格,每行一个语句,保留#注释)。具有解析错误的新型语法文件会以path:line:column: message格式报告,保持字节原样不动(即使在--dry-run模式下也是如此),并使smith fmt退出 1:它绝不会被发送到 clang-format,因为那会破坏文件。该操作是幂等的,遵循.clang-format-ignore,并且--dry-run/-n会列出将要更改的文件(path: would be formatted)而不实际写入。当前语法的.smith文件(以及每个.c文件)在过渡 (#1631) 移除该回退之前,仍由 clang-format 处理。通过tests/fmt_smith_test.smith进行测试。 smith fmt FILE会拒绝扩展名不在上述列表中的文件(如.sh、.md),并报错指出文件名且不写入任何内容,因为 clang-format 会将其作为 C 读取并破坏它;可以使用--assume-filename=x.c来指定其类型。- 提交前:先运行
smith fmt,然后运行smith check。 - 您自己的任务(自定义检查、生成器)在
build.c中定义为smith_task,并通过smith build <task>运行;任何任务或别名都不能命名为fmt、lint、docs、prove、check、run或test。smith build check和其他动词名称会被拒绝:请使用动词本身。
针对文件:带参数
smith lint DIR(参数中的文件夹)会对该目录下 git 能看到的且 .clang-format-ignore 未列出的 C、C++、Objective-C 和 .smith 源文件进行 lint,与项目级 lint 类似(不包括头文件,它们通过其源文件读取);smith lint front -- <flags> 和 smith lint front/ main.c 可以混合使用文件和文件夹。如果文件夹中没有此类源文件,则会报错。
clang-tidy 的分析器(clang-analyzer-*)会将 <smith/contract.h> 中的 smith_requires、smith_ensures、smith_invariant 和 smith_assert 作为假设读取(通过 target::static_analyzer),因此受合约约束的索引不会被 clang-analyzer-security.ArrayBound 报告。
smith 的默认 .clang-tidy 会关闭两个曾经产生噪声的分析器检查(在游戏、学习者和 smith 上:616 个发现中的 546 个):clang-analyzer-security.insecureAPI.DeprecatedOrUnsafeBufferHandling(每个 memcpy)和 clang-analyzer-optin.performance.Padding。要在项目自身的 .clang-tidy 中重新启用其中一个(smith lint --dump-config > .clang-tidy 会写入有效的配置文件),只需删除其对应的 -clang-analyzer-... 行。
smith lint --fix FILE.smith...(不带 --)会应用 smith 自身前端在其解析错误中携带的修复(旧形式或其 smith 拼写的 C 风格形式:一个修复的编辑会全部应用或全部不应用,重叠的修复会被跳过,并且文件会重新解析直到没有修复可应用,最多 16 次)并打印 path: N fixes applied;第二次运行会打印 0 fixes applied。它只读取 .smith 文件,并且需要完整的 smith 支持。使用 -- <flags> 时,则是调用 clang-tidy 的 --fix,如上所述。目前还没有解析错误携带修复:旧形式的修复将按工单逐个进行 (#1428)。
smith lint FILE 会在该文件夹存在时,为 build/<os>/front-project/.smith/include 添加 --extra-arg=-I(前端模块通过该目录相互导入),并且在非 macOS 主机上会打印一行:CI 在 Mac 上进行 lint,其中 u64 是 unsigned long long,usize 是 unsigned long,因此使用 usize 的 (U64)(i + 1) 只会在 Mac 上产生 bugprone-misplaced-widening-cast 的发现;通过编写 (U64)i + 1 来解决此类转换问题。
带参数时,它们就是工具:smith fmt main.c 会就地重写文件(clang-format 会打印结果;smith 在您指定文件名且没有选项选择其他模式时会添加 -i:-n、--dry-run、--lines、--assume-filename 保持 clang-format 的含义,而 smith fmt --assume-filename=x.c < x.c 会打印结果),smith fmt --dry-run --Werror main.c,smith lint --quiet main.c -- <flags>,smith lsp --check=main.c。项目的 .clang-format 和 .clang-tidy 都会生效。stage0 smith 会运行 Homebrew 的工具,或 $SMITH_CLANG_FORMAT、$SMITH_CLANG_TIDY、$SMITH_CLANGD。
smith 的默认配置,当项目没有自己的文件时(问题 #43)。一个没有 .clang-format(或 _clang-format)和 .clang-tidy 的项目(从每个文件的文件夹向上查找,如同 clang-format 和 clang-tidy 所做的那样)会使用 smith 自身嵌入的配置进行格式化和 lint(这些文件就是 smith new 今天所写入的);无需编写任何东西。项目中的文件会完全替换 smith 的配置,而不会合并,并且显式的 --style(或 --fallback-style)、--config 或 --config-file 会优先。对于标准输入,--assume-filename 指定了查找配置的位置。在一条命令中同时指定带有和不带有项目文件的文件是可行的(smith 会将第二种情况作为另一个进程运行)。smith fmt --dump-config > .clang-format 和 smith lint --dump-config > .clang-tidy 会打印有效的配置(项目的,否则是 smith 的),因此项目可以通过转存并编辑它们来采用默认配置;默认配置很少且有意地变更,因此想要固定配置的项目可以通过转存它们来实现。smith 将其副本写入 $XDG_DATA_HOME/smith/defaults/(否则是 ~/.local/share/...)。smith cc 永远不受影响:它始终保持纯穿透模式。
.smith 文件
smith fmt x.smith(以及 -i、--dry-run、与 --assume-filename=x.smith 配合的标准输入)会将 .smith 文件作为 C 文件格式化,保留其中的 :: 路径、module a::b; 和 import a::b as x; 行以及 import <sys/wait.h>; 完整不变;不带参数的 smith fmt 也会格式化项目中 git 能看到的 .smith 文件。函数内部的 import 和 module 会被视为名称,如同在 C 中一样。需要 clang-format 包含 .smith 补丁的 smith 版本(stage0 运行 Homebrew 的版本,它将 .smith 作为 C++ 读取,因此会误读作为 C++ 关键字的 C 名称,例如 throw)。clangd 以相同方式格式化 .smith 文件。
smith lint x.smith -- <flags>(clang-tidy,使用项目的 .clang-tidy)会像 smith cc 读取它那样(-x c,以及 -std=gnu2y,除非您命名了标准;编译数据库自身的命令会原样使用)对 .smith 文件进行 lint,并按照源代码编写的方式报告:在 .smith 文本的行和列处显示发现、注释和 fix-it,使用编写时的名称(模块中的 area 和 Box,导入处的 box::area,绝不会是 geometry_box_area)。readability-identifier-naming 会判断模块文件作用域名称的编写名称,因此模板的规则(snake_case 函数和变量,PascalCase 类型)适用于 area 和 Box,而 --fix 会重写源代码中的 token;sized 类型(U32、I32)、降级后的原型副本以及 smith build 写入的头文件永远不会被报告。关于导出的发现会在模块的文件中报告,而不是在每个导入者中,并且其 fix-it 不会重命名导入者。其他检查会在其消息中显示编写的名称,但比较名称本身的检查(例如长度或保留名称检查)仍然读取降级后的名称。需要 clang 包含 token 钩子的 smith 版本(smith cc --smith-which x.smith 会显示 token-hook yes);stage0 运行 Homebrew 的 clang-tidy,它无法读取 .smith。
属于同一家族的还有:smith-check-lints。