# xl 诊断码与 `xl check` > 本文是 **诊断码表的唯一事实来源**:每个码的级别、触发条件、报错位置与修复建议都在这里。 > 检查的对象是 [`xl-syntax.md`](./xl-syntax.md)(下称「语法」)定义的 `*.xl.md`;命令行接口见 [`xl-cli.md`](./xl-cli.md);ts 产物映射见 [`xl-emit-ts.md`](./xl-emit-ts.md);完整输入与期望产物见 [`xl-base-case.md`](./xl-base-case.md)。 > `xl check` **不联网、不调用模型、不起子进程**:只做语法 / 结构 / 引用的静态检查,耗时毫秒级。 > 实现侧的码表在 `src/core/diagnostics.js` 的 `DIAG_CODES`,`pnpm codes` 会核对「表里每个码都有产生它的地方」。 --- ## 1. 命令 ```text xl check [paths...] [options] ``` | 分组 | 选项 | 作用 | | --- | --- | --- | | 目标 | `-t, --target ` | 按目标语言检查「生成依据是否齐备」,可重复;缺省 `ts`。它决定 `W3010` / `W3104` 针对哪些目标语言报告 | | 判定 | `--strict` | warning 视为 error(退出码 1) | | 判定 | `--max-warnings ` | warning 超过 n 条即失败(缺省不限) | | 过滤 | `--ignore ` | 忽略指定规则码,逗号分隔,如 `--ignore W3011,E1107`;可重复,累加 | | 输出 | `--format ` | `pretty`(缺省)\| `compact` \| `json` | | 输出 | `--json` | NDJSON 诊断流(等价 `--format json`) | | 输出 | `--color ` | `auto`(缺省,跟随 TTY)\| `always` \| `never`;`NO_COLOR` / `FORCE_COLOR` 同样生效 | | 全局 | `--cwd ` | 以指定目录为基准解析路径与配置 | | 全局 | `-q, --quiet` | 只输出 error(warning 级诊断不打印,计数与退出码不变) | | 全局 | `--verbose` | 接受但**当前不产生任何额外输出** | | 全局 | `-h, --help` / `-v, --version` | 帮助 / 版本 | * 输入解析与 `build` 完全一致([`xl-cli.md`](./xl-cli.md) §3.2):文件 / 目录 / glob 展开,排除 `node_modules`、`.git`、`dist`、`build`、`.xl` 与 `.xlignore` 的模式,永不把产物(首行含 `@generated by xl`)当输入。 * 规则码来自 `xl.json` 的 `check.ignore` 与 `--ignore` 的并集;被忽略的码不参与 `--strict` 与 `--max-warnings` 计数。解析 / 检查层的码直接从结果里移除(不打印、不计数);`xl build` 的产物层码(`E2001` / `E2002` / `E2003`)仍会打印,但同样不计数(§6)。 * `xl build` 在生成前隐式跑同一份检查:**有 error 立即终止**,退出码 1,没有任何选项能关掉它。`xl build` 只读取 `check.ignore`;`--strict` / `--max-warnings` 对 `xl build` **无效**(它们是 `xl check` 的判定项)。 ### 1.1 退出码 | 码 | 条件 | | --- | --- | | 0 | 无 error(允许 warning;`--strict` 下不允许) | | 1 | 至少 1 个 error;或 `--strict` 且有 warning;或 warning 数超过 `--max-warnings` | | 2 | 用法错误:路径不存在(`E0001`)、没有匹配到 `*.xl.md`(`E0002`)、非法 `--target`(`E0003`)、未声明扩展名 / 部件表的自定义目标(`E0004`)、未知命令或未知选项 | > 用法错误由 CLI 直接打印 `error: `(参数问题)或 `xl: `(`UsageError`),**不回显规则码**:`E0001`–`E0004` 只在码表里占位。它们也不进诊断计数。 > `xl` 从不返回 3 或 4。 ### 1.2 严重级别 | 级别 | 语义 | | --- | --- | | `error` | 语法 / 结构非法,产物无法确定;`build` 会因此终止 | | `warning` | 合法但会削弱生成质量(缺说明、缺语言指示、跨文件重名、接口未实现等);`build` 只提示 | 全部 51 个码里恰好 11 个是 warning:`E1105` `E1107` `E2003` `W3010` `W3011` `W3012` `W3013` `W3101` `W3102` `W3103` `W3104`。 > **码段与级别解耦**:`E` / `W` 前缀只表示码的归属段(`E0` 输入、`E1` 语法与结构、`E2` 产物、`E4` 计划通道),**级别**一律由本表决定而非前缀——如 `E1105` / `E1107` / `E2003` 是以 `E` 段编号的 warning。未知码按 error 处理。 --- ## 2. 诊断格式 **pretty**(缺省):三到四行一组,含源码行与 `^` 下划线: ```text ::: []: ^^^^^^^^^^^^ help: <修复建议> ``` 示例(第三段为单行摘录,`help:` 行只在有修复建议时出现): ````console $ xl check pkg/demo.xl.md pkg/demo.xl.md:105:3: error[E1302]: default body must use the default language 'ts', got 'js' ```js ^^^^^^ help: 把默认语言块的围栏改成 'ts';或放进 `### csharp` 之类的语言子标题下 pkg/demo.xl.md:142:3: warning[W3010]: member 'tick' has no ts body and no '### csharp' hint ## method tick:()=>int ^^^^^^^^^^^^^^^^^^^^^^ help: 补一个 ts 默认语言代码块,或加 `### csharp` 指示 pkg/demo.xl.md:73:1: error[E1202]: modifier 'readonly' is not valid on property ## readonly property ORIGIN:string ```` **compact**:每行一条诊断,无源码片段与 help: ```text ::: []: ``` **json**(`--format json` / `--json`):NDJSON,`ev` 是事件名;此时不再有任何人类可读输出。 | `ev` | 字段 | | --- | --- | | `diag` | `file` `line` `col` `severity` `code` `msg`,可选 `help` `endLine` `endCol` | | `check.end` | `ok` `files` `errors` `warnings` | ```json {"ev":"diag","file":"pkg/demo.xl.md","line":105,"col":3,"severity":"error","code":"E1302","msg":"default body must use the default language 'ts', got 'js'","help":"把默认语言块的围栏改成 'ts'"} {"ev":"check.end","ok":false,"files":2,"errors":1,"warnings":3} ``` * 诊断**稳定排序**:`文件 → 行 → 列 → 规则码 → 消息`;同一处被解析器与检查器各报一次时(同文件、同码、同列、同消息)只保留 span 最大的一条。 * `help` 在构造诊断时就从码表取定,因此打印的内容永远与本表一致。 * 路径一律相对 `--cwd`、POSIX 分隔符(`pkg/demo.xl.md`),保证跨平台输出可比。 * 末尾汇总行:`✔|✖ error(s), warning(s) in file(s)`(`--quiet` / `--json` 下不打印)。 * `xl build` 的诊断格式相同,只是不附源码片段(build 不回读源码行)。 --- ## 3. 规则码表 「位置」一列指报错落在哪一行:**标题行**=该段落 / 成员的标题,**围栏行**=代码块的开围栏,**import 行**=`# dependencies` 的 ```` ```xl ```` 块里的那一行。 ### 3.1 `E0xxx` 输入与文件层 | 码 | 级别 | 规则 | 位置 | help | | --- | --- | --- | --- | --- | | `E0001` | error(用法错误,退出码 2) | 显式给出的输入路径不存在(glob 匹配为空不算) | — | 检查路径拼写,或省略 `paths` 让 xl 递归当前目录 | | `E0002` | error(用法错误,退出码 2) | 展开后没有匹配到任何 `*.xl.md` | — | 指定文件 / 目录 / glob,或确认后缀是 `.xl.md` | | `E0003` | error(用法错误,退出码 2) | `--target` 非法(既不是内置语言,也不是 `xl.json` 的 `targets.`) | — | 用 `xl targets` 查看已知目标 | | `E0004` | error(用法错误,退出码 2) | 自定义目标声明不可用:`parts` 不是非空数组、部件不是对象、`role` 重复、缺 `ext`、`requires` 不是 `"bodies"`、`scope` 不是 `declaration` / `definition`,或既没有 `ext` 也没有 `parts` | — | 在 `xl.json` 补 `"targets": { "": { "ext": ".kt" } }` | | `E0005` | error(退出码 1) | 文件以 UTF-8 BOM 开头、不是合法 UTF-8,或含 `CR`(源文件要求 UTF-8 无 BOM、LF) | 首个违规位置 | 另存为 UTF-8(无 BOM)+ LF 换行 | > `E0001`–`E0004` 是**用法错误**(退出码 2),CLI 只打印消息、不回显码;`E0005` 属于文件内容错误(退出码 1)。 ### 3.2 `E1xxx` 语法与结构层 **A. 文件层 —— 骨架与段落** | 码 | 级别 | 规则 | 触发示例 | help | | --- | --- | --- | --- | --- | | `E1001` | error | 文件为空,或只有空白 / 只有散文,没有任何标题 | 空 `*.xl.md` | 写 `# namespace` 或直接写一级声明 | | `E1002` | error | 未知一级段落种类(合法:`dependencies` / `namespace` / `type` / `const` / `method` / `enum` / `interface` / `class` / `statement`) | `# struct point` | 换成合法关键字,见语法 §2 | | `E1003` | error | `# namespace` 名字缺失或非法 | `# namespace` / `# namespace 9a` | 名字用小写标识符,如 `# namespace demo` | | `E1004` | error | `# dependencies` 不是第一个段落,或出现两次(语法 §0:必须第一个、至多一次) | `# namespace demo` 之后又出现 `# dependencies` | 把 `# dependencies` 移到文件最前面 | | `E1005` | error | `# namespace` 出现两次。声明之间**没有顺序要求**,所以一条 `# method` 排在 `# class` 之后是合法的 | `# namespace a` 之后又出现 `# namespace b` | 只保留一个 `# namespace`;`# dependencies` 的顺序问题由 `E1004` 报 | | `E1006` | error | `# dependencies` 的 `xl` 块行非法:不是 `import { … } from "./x.xl.md"`、目标是绝对路径或不是 `*.xl.md`、依赖文件读不到 / 不是合法的 `*.xl.md`、或导入名在目标文件里没有对应的一级声明 | `import { nope } from "./util.xl.md"` | 检查相对路径与导出名;依赖文件本身不参与构建,但必须可解析 | **B. 声明层** | 码 | 级别 | 规则 | 触发示例 | help | | --- | --- | --- | --- | --- | | `E1101` | error | `# type` / `# enum` / `# class` / `# interface` 名字缺失或非法 | `# class` / `# interface 2fa` | 名字用标识符(与产物 `export class point` 一致) | | `E1102` | error | interface 相关:`# interface … implements …`;`## constructor` 出现在 interface;interface 成员种类不是 `field` / `property` / `method`;interface 成员带初始值 | `# interface a implements b` | interface 只描述签名;实现放到 `# class` | | `E1103` | error | 泛型参数非法:括号不配对、形态不是 `` / `` / ``、约束或默认值为空 | `# class c` / `# interface a` | 每个参数写成 `名字 [extends 约束] [= 默认值]` | | `E1104` | error | `extends` 写了但基类型为空;或 `extends` / `implements` 的目标在本次检查范围内不存在(同文件与依赖都算;`int` / `Array` 等内置类型名除外) | `# class box extends pointt` / `# class box extends` | 补声明,或修正拼写 | | `E1105` | **warning** | `implements` 后没有名字;interface 的 `extends` 指向 class;`implements` 指向的不是 interface;class 未实现 interface 的成员(可选字段不强制);成员参数个数不一致 | `# class box implements marker` 且 `marker` 是 class | 让目标成为 `# interface`,并补齐 `print(): string` 之类的成员 | | `E1106` | error | 同一文件内一级声明重名(`# statement` 不声明名字,不参与) | 两个 `# class point` | 改名;同一源文件内符号必须唯一 | | `E1107` | **warning** | 同一次运行里某个类型名(`enum` / `interface` / `class`)被声明两次以上(跨文件,或同一文件内两次);只在排序最靠前的那个文件上报一次 | `pkg/a.xl.md` 与 `pkg/b.xl.md` 都有 `point` | 改名,避免产物撞名 | | `E1108` | error | `# type` 缺少 `= <原文>` | `# type MemberKind` | 写成 `# type MemberKind = "field" \| "method"` | | `E1109` | error | `# enum` 没有 `- case` 成员(报在标题行),或成员重名(报在该 `- case` 行) | 两个 `- case red` | 删除重复项,或补 `- case ` | | `E1110` | error | `# statement` 既没有非空的默认语言(ts)块,也没有任何带非空代码块的 `## ` 段(语法 §17) | 光秃秃的 `# statement` | 补一个 `ts` 默认语言代码块,或加 `## ` 段;没有正文的语句段没有生成依据 | **C. 成员层** | 码 | 级别 | 规则 | 触发示例 | help | | --- | --- | --- | --- | --- | | `E1201` | error | 未知成员 / 子标题种类(合法成员:`field` / `property` / `method` / `constructor`) | `## attr x:int`;类下的 `## csharp`(类没有类级语言段,语法 §16) | 换成合法关键字,见语法 §2 | | `E1202` | error | 修饰符非法:`# statement` 带任意修饰符;一级声明带 `static` / `readonly` / `protected`;`constructor` 带 `static` / `readonly`;`property` 带 `readonly` | `## readonly property ORIGIN:string` | 只保留该成员种类允许的修饰符(语法 §3、§8、§12–§15、§17) | | `E1203` | error | 名字缺失或非法(`# const` / `## field` / `## property` / `## method`),或 `# statement` 后面多写了名字 | `## field :int = 0` / `# statement entry` | 补合法标识符名字;`# statement` 是匿名段,关键字后面什么都不写 | | `E1204` | error | 方法形态或类型标注非法:缺 `:`、缺 `=>`、`=>` 后为空、类型标注缺失或括号 / 尖括号不配对、无法解析 | `## method m:(a:int)=>` / `## field x:Arrayint` | 返回类型固定 `void`,一个类至多一个 | | `E1207` | error | 内联初始值与标量声明类型明显不符(只判 `int` / `float` / `double` / `number`、`bool` / `boolean`、`string` 三类) | `## field x:int = "0"` / `## field flag:bool = 3` | 改正初始值或类型标注 | | `E1208` | error | 参数非法:缺 `(`、括号不配对、参数缺类型或名字、`?` 与默认值同时写、必选参数排在可选参数之后 | `# method m:(a:int, b?)=>void` | 写成 `(a:int, b?:int)`,必选参数在前 | | `E1209` | error | `### get` / `### set` 出现在非 `property` 成员下(`property` 本身不挂访问器是合法的,等价于读写都开) | `## method m:()=>void` 之后的 `### get` | 删掉该访问器;它只能挂在 `## property` 下 | | `E1210` | error | `### get` / `### set` 重复(顺序任意,`set` 写在 `get` 之前不报错) | 两个 `### get` | 每个访问器至多一次 | | `E1211` | error | 同一处写了多个可见性修饰符(`public` / `protected` / `private` 互斥,至多一个) | `## public private field x:int = 0` | 只保留一个 | | `E1212` | error | enum 成员行以 `-` 开头但不是 `- case ` / `- case = <原文>` | `- red` / `- case` / `- case green =` | 写成 `- case ` 或 `- case = <原文>`(语法 §9) | > interface 成员上的 `private` / `protected` / `static` **不报错**(interface 只描述签名,产物按 `xl-emit-ts.md` §6.2 忽略这些修饰符);`# dependencies` / `# namespace` 标题上的修饰符被静默忽略。 **D. 代码块与语言覆盖层** | 码 | 级别 | 规则 | 触发示例 | help | | --- | --- | --- | --- | --- | | `E1301` | error | 一个成员 / 声明下出现多个默认语言(`ts`)代码块(`# dependencies` 例外,它允许重复) | 同一个 `## method` 下两个 `ts` 围栏块 | 合并成一个默认语言块,其余改用 `### ` 子标题 | | `E1302` | error | 默认语言块的围栏不是 `ts`;或 ```` ```xl ```` 出现在 `# dependencies` 之外 | 成员体用 `js` 围栏 | 把默认语言块的围栏改成 `'ts'`;或放进 `### csharp` 之类的语言子标题下 | | `E1303` | error | 同一父标题下语言名重复(`## ` / `### ` / `#### `);**只保留第一个,其余丢弃** | 同一个 `## method` 下两个 `### csharp` | 合并同类语言段 | | `E1304` | error | interface 成员(含访问器)带函数体 | `# interface p` 的 `## method print:()=>string` 下带 `ts` 代码块 | 删除函数体,或改用 `# class` | | `E1305` | error | 未知子标题:既不是 `get` / `set`,也不是语言名(不在已知语言表里,也没有同名围栏) | `## method m:()=>void` 下的 `### note` | 改成 `### get` / `### set`(仅在 `property` 下)或 `### `;说明散文直接写在成员标题下 | ### 3.3 `W3xxx` 生成质量提示(warning) | 码 | 级别 | 规则 | help | | --- | --- | --- | --- | | `W3010` | warning | 只对**请求的非 ts 目标**逐个报告:模块级 `# method`、类的 `## method` / `## constructor`、以及「无初始值、显式写了访问器但访问器无体」的 `property` 访问器,在没有默认语言(ts)代码块、也没有该目标的 `### ` 段时触发。**不挂访问器的 property 默认就是读写直通,不报**。`# statement` 的规则略有不同:无 ts 块但有语言段时,目标里含 `ts` 也报(那一段会从 ts 产物里整段消失) | 补一个 `ts` 默认语言代码块,或加 `### csharp` / `## csharp` 指示 | | `W3011` | warning | `### ` / `## ` 只有说明文字、没有代码块——合法但生成信息量低 | 补该语言的代码块,或删掉空壳子标题 | | `W3012` | warning | 代码块为空(围栏之间只有空白) | 补内容,或删除该代码块 | | `W3013` | warning | `# statement` 的默认语言块里出现静态顶层 `import`(或带 `from` 的 `export`):它会被提升到产物头之前 | 把依赖写进 `# dependencies`(语法 §4),默认语言块里只留可执行语句 | | `W3101` | warning | 某个非 ts 覆盖段引用了 ts 依赖块**实际绑定**的名字(`import x` / `import { x as y }`),而 `# dependencies` 里没有该语言的说明段 | 在 `# dependencies` 的 `## ` 段说明该依赖的来源 | | `W3102` | warning | class / interface 成员的说明(标题下的散文)为空 | 补一句用途说明:它会进生成上下文,并成为产出代码注释 | | `W3103` | warning | `# namespace` 的说明为空 | 补包用途说明,供计划通道使用 | | `W3104` | warning | 请求的某个非 ts 目标在文件里没有任何对应覆盖段(`## ` / `### ` / `#### `) | 只为确实需要人工指示的目标补 `## ` 段;不需要时用 `--ignore W3104` | > `W3010`–`W3012` 是「块层」提示,`W3101`–`W3104` 是「语义层」提示;两者都只影响计划通道的生成质量,不影响 ts 直出。 > `xl check` 缺省只请求 `ts`,因此针对非 ts 目标的 `W3010` 与 `W3104` **缺省不会出现**:要让它们报出来,得给 `-t csharp` 这样的非 ts 目标。唯一的例外是 `# statement`:它在「没有 ts 块、但有语言段」时,只要目标里含 `ts`(缺省即含)就报。 --- ## 4. 覆盖范围与不检查的内容 | 项 | 说明 | | --- | --- | | `# type` 右侧 | 是**原文**,不参与类型解析(语法 §6),因此不检查其内部语法 | | `- case green = 2` 右侧 | 同上,按目标语言直译 | | 默认语言块内部 | **不做代码语义检查**:`ts` 块只被当作字符串包装进产物,语法错误由使用方的 `tsc` / 编辑器暴露 | | `### ` 块内部 | 同上,且它只作为生成上下文 | | 计划通道产物 | 由 `xl_emit` / `xl_verify` 的结构回读校验负责([`xl-cli.md`](./xl-cli.md) §3.4),不属于 `xl check` | | 依赖文件 | 只被解析以支撑 `E1006` / `E1104` / `E1107`;依赖文件自身的诊断**不会**出现在本次输出里(它只有在被显式列为输入时才会被检查) | | 未解析的初始值 / 代码 | `E1207` 只判三种标量类型的内联字面量;联合类型、`Map<…>` 之类的初始值一律照原样接受 | --- ## 5. `xl build` 期间的诊断 `xl build` 复用同一份码表(§3),另外在产物层与计划通道报以下码: | 码 | 级别 | 触发 | help | | --- | --- | --- | --- | | `E2001` | error | 输出冲突:同一产物路径被计划写多次(`--flat` 压平或跨源文件撞名);目标位置已存在非 xl 产物且未给 `--force`;`xl_emit` 提交了计划外的路径 | 去掉 `--flat`、改名,或加 `--force`;xl 绝不静默覆盖 | | `E2002` | error | 源文件读不到,或写盘失败(目录不可写、磁盘满、路径被占用) | 检查 `--out` 权限与磁盘状态 | | `E2003` | **warning** | 目标产物存在 xl 头、但内容哈希与 cache 记录不符(被手工改过),即将被覆盖;`--dry-run` 时只提示;`--no-cache` 时无从判断,不报 | 不要手改产物;改动写回 `*.xl.md` | | `E4001` | error | **故意不产生**:它描述「harness 失败」,而本实现没有子进程通道,计划通道的失败由 `xl_emit` 以 `E4002` 表达 | 查看 dsh 会话日志与 profile 组合 | | `E4002` | error | `xl_emit` 提交的内容过不了结构回读:计划内的某个产物没有提交,或产物的类型集合 / 成员名集合 / 参数个数与 IR 不一致(定义部件只校验它提到的成员) | 收敛生成上下文(补 `## ` 指示),或放宽该目标的校验 | * 退出码:`E2001` / `E2002` 为 1;`E2003` 只提示;`E4002` 由 `xl_emit` 返回给会话(`xl build` 本身不产生它,也从不返回 3 / 4)。 * `E2001` / `E2002` / `E2003` **不受 `--ignore` 影响**:写盘失败与输出冲突不是作者可以豁免的规则,`xl build --ignore E2002` 仍然以 1 退出([`xl-cli.md`](./xl-cli.md) §3.8)。 * `xl_emit` 只要有一个文件不通过就**一个都不写**,agent 修完再调一次即可。 * 一个源文件失败不影响其它源文件,也不影响已成功产物的写入。 --- ## 6. 抑制与配置 ```jsonc { "check": { "ignore": ["W3011", "W3104"], "strict": false, "maxWarnings": null } } ``` | 手段 | 作用 | | --- | --- | | `--ignore ` | 本次忽略指定规则码(解析 / 检查层的码直接从结果里移除,不打印也不计数) | | `check.ignore` | 配置文件里持久忽略 | | `--strict` / `check.strict` | warning 提升为失败(只影响 `xl check`) | | `--max-warnings ` / `check.maxWarnings` | warning 数量上限(只影响 `xl check`) | > 产物层的 `E2001` / `E2002` / `E2003` 不受忽略影响:即使写进 `--ignore` / `check.ignore` 也照常计数,`xl build` 该失败就失败([`xl-cli.md`](./xl-cli.md) §3.8)。 * 行内抑制**不存在**:xl 不支持 `// xl-ignore` 之类的注释——注释放进默认语言块会被原样带进产物。需要豁免时用 `--ignore` / `check.ignore`,或直接修正源文件。 * `--quiet` 只影响输出(只留 error 级诊断),不影响退出码与 `--json` 的事件流。 ```console $ xl check . ✖ 3 errors, 4 warnings in 2 file(s) $ xl check . --strict --format compact pkg/a.xl.md:12:3: warning[W3010]: member 'tick' has no ts body and no '### csharp' hint ✖ 3 errors, 5 warnings in 2 file(s) ```