Angular compiler-cli ErrorCode 错误码体系全解析:枚举定义、NG 编码机制与诊断实现
本文以 Angular 仓库公开 API 清单 error_code.api.md 中的 ErrorCode 枚举为骨架,结合其底层实现文件 packages/compiler-cli/src/ngtsc/diagnostics 下的源码,系统梳理 Angular 模板编译器(ngtsc)与 @angular/compiler-cli 使用的全部错误码:它们的数值分布规律、各错误族(装饰器、组件/指令、NgModule、模板类型检查等)的含义,以及"内部错误码如何被编码成你看到的 NGxxxx 形式"的底层原理。读完本文,你将能根据控制台中的 NG 前缀错误码快速反查对应的枚举成员与触发场景,也能理解 Angular 编译器错误诊断从抛出到格式化输出的完整链路。
一、ErrorCode 是什么:一份由 API Extractor 锁定的公开契约
ErrorCode 是 Angular compiler-cli 中 ngtsc(新一代 Angular TypeScript 编译器)统一定义的诊断错误码枚举,被标注为 @public(见 error_code.ts),并从诊断模块的公共出口 index.ts 中 export {ErrorCode} 导出。
error_code.api.md 则是通过 API Extractor 自动生成的 API 报告文件,其头部声明 "Do not edit this file. It is a report generated by API Extractor",意味着它的作用是锁死 ErrorCode 的公开成员名称与数值,防止在演进过程中意外改号或改名导致下游工具(如 Angular Language Service、编辑器插件、自定义扩展诊断)失配。
该文件展示的枚举拥有 120 个成员,数值覆盖约 20 个语义区间。源码中每个成员往往带有 JSDoc 注释说明触发条件,甚至配有错误模板示例(见 error_code.ts)。因此,本文后面每一个错误族的小结都可以视为"枚举 + 官方注释"的精读结果。
二、数值分布规律:读懂错误码的"分区"
从整体看,ErrorCode 的数值按编译器处理阶段做了分区,区间首位数字即代表诊断来源:
| 数值区间 | 覆盖领域 | 示例 |
|---|---|---|
| -1001 / 1002 ~ 1100 | 装饰器(Decorator)校验与 initializer API 使用 | DECORATOR_COLLISION = 1006 |
| -2003 / 2001 ~ 2028 | 组件 / 指令 / 宿主指令元数据处理 | COMPONENT_MISSING_TEMPLATE = 2001 |
| -3003 / 3001 ~ 3004 | 引用解析、符号导出与导入生成 | SYMBOL_NOT_EXPORTED = 3001 |
| 4001 ~ 4006 | 编译器配置(angularCompilerOptions)校验 |
CONFIG_FLAT_MODULE_NO_INDEX = 4001 |
| 5001 ~ 5002 | 宿主表达式与模板解析错误 | TEMPLATE_PARSE_ERROR = 5002 |
| -6100 / 6001 ~ 6009 | NgModule 语义分析 | NGMODULE_INVALID_DECLARATION = 6001 |
| -8001 ~ -8003 / 8004 ~ 8029 | 模板语义、Schema 校验、@defer/@let 等模板特性 |
MISSING_PIPE = 8004 |
| 8101 ~ 8118 | 模板类型检查与模板诊断 | INVALID_BANANA_IN_BOX = 8101 |
| 8900 ~ 8901 | 内联类型检查块 / 类型构造器生成 | INLINE_TCB_REQUIRED = 8900 |
| 9001 | Injectable 元数据冲突 | INJECTABLE_DUPLICATE_PROV = 9001 |
| 10001 ~ 10002 | 建议类(suggestion)诊断,非 Error 类别 | SUGGEST_STRICT_TEMPLATES = 10001 |
| 11001 / 11003 / 11100 ~ 11102 | 局部编译模式(local compilation)与具名 @defer 依赖 |
LOCAL_COMPILATION_UNRESOLVED_CONST = 11001 |
源码还保留了一条编号纪律:6999 曾分配给 NGMODULE_VE_DEPENDENCY_ON_IVY_LIB(View Engine 时代依赖 Ivy 库的旧错误),为避免混淆,该号码被明确注释为"不再复用"(见 error_code.ts)。这说明错误码一旦发布便被视作稳定标识,不会二次分配。
2.1 负数成员的用途
注意表中若干成员为负值(如 -2009、-3003、-8001)。在 util.ts 中 errorCodeWithGuideFromDiagnosticCode 仅对负值错误码做指南(guide)查找,返回 -absoluteErrorCode 命中的成员;随后 addDiagnosticDetails 会向诊断消息追加 Find more at {ERROR_DETAILS_PAGE_BASE_URL}/NG1001 形式的官方错误指南链接(基础地址由 error_details_base_url.ts 依据版本号计算,预发布版指向 next 前缀域名)。测试用例对此给出了实证(format_compiler_error_spec.ts)。需要强调的是,不论枚举值是正是负,最终对外展示的错误码一律取绝对值(下一节详述),符号只是编译流水线内部使用的元信息。
三、从枚举值到 NGxxxx:-99 标记编码机制
这是理解 Angular 编译器错误码最关键的一节。Angular 的诊断会复用 TypeScript 的 ts.Diagnostic 结构,而 TS 的格式化器会给每个诊断号硬编码 TS 前缀。为了让 Angular 自有的诊断显示成 NG 前缀,error_code.ts 采用了巧妙的"标记数"方案:
ERROR_CODE_MARKER = 99,即所有 Angular 诊断的数值都以99打头;ngErrorCode(code)计算出-(99 * 10^digits + |code|)作为真正的ts.Diagnostic.code。例如枚举成员DECORATOR_ARG_NOT_LITERAL = -1001,取绝对值后编码为-991001;DECORATOR_ARITY_WRONG = 1002编码为-991002(测试见 format_compiler_error_spec.ts);- 在诊断文本格式化阶段,
replaceTsWithNgInErrors用正则/(\u001b\[\d+m ?)TS-99(\d+: ?\u001b\[\d+m)/g把TS-99替换为NG,于是TS-991002显示为NG1002(util.ts)。
export const ERROR_CODE_MARKER = 99;
export function ngErrorCode(code: ErrorCode): number {
const absoluteCode = Math.abs(code);
return -(ERROR_CODE_MARKER * 10 ** decimalDigits(absoluteCode) + absoluteCode);
}
export function formatCompilerErrorCode(code: number): string {
return `NG${Math.abs(code)}`;
}
因此,你在构建日志或 IDE 里看到的 NG1001、NG8111 等,对应的 ts.Diagnostic.code 实际是 -991001、-998111,而枚举原文分别是 -1001 / 1002 与 8111。三者的换算关系可概括为:
- 枚举值:Angular 内部语义号(可能带符号);
- TS 诊断码:
-99拼接四位/五位补零绝对值(ngErrorCode产物); - 展示号:
NG+ 绝对值(formatCompilerErrorCode产物)。
这样设计的好处是让所有 Angular 错误码天然规避 TS 自带诊断的码段,并能在同一套 TS 管道中无损往返(反向解析由 absoluteErrorCodeFromDiagnosticCode 完成,先取绝对值、剥离 99 标记,见 util.ts)。
四、错误族详解(全量 120 个成员)
下面按错误族逐个给出成员、数值与含义。含义文字取自枚举源码注释与 API 报告,可直接作为"报错排查手册"使用。
4.1 装饰器与 initializer API(-1001 ~ 1100)
| 成员 | 值 | 触发场景 |
|---|---|---|
DECORATOR_ARG_NOT_LITERAL |
-1001 | 装饰器的参数不是字面量(无法静态求值) |
DECORATOR_ARITY_WRONG |
1002 | 装饰器调用参数个数错误 |
DECORATOR_NOT_CALLED |
1003 | 装饰器没有被当作函数调用 |
DECORATOR_UNEXPECTED |
1005 | 出现了不被允许使用的装饰器 |
DECORATOR_COLLISION |
1006 | 类或类字段上存在互不兼容的装饰器组合 |
VALUE_HAS_WRONG_TYPE |
1010 | 装饰器元数据值的类型不符 |
VALUE_NOT_LITERAL |
1011 | 需要字面量处提供了非字面量 |
DUPLICATE_DECORATED_PROPERTIES |
1012 | 同一属性被重复装饰 |
INITIALIZER_API_WITH_DISALLOWED_DECORATOR |
1050 | initializer API(如 input())同时叠加了不允许的装饰器,如字段同时使用 @Input 与 input() |
INITIALIZER_API_DECORATOR_METADATA_COLLISION |
1051 | initializer API 与类装饰器元数据重复声明(如 signal input 又出现在 @Directive({inputs})) |
INITIALIZER_API_NO_REQUIRED_FUNCTION |
1052 | 某 initializer API 不支持 .required 却被调用 |
INITIALIZER_API_DISALLOWED_MEMBER_VISIBILITY |
1053 | initializer API 用于不允许的访问修饰符成员(如 private 字段) |
DUPLICATE_BINDING_NAME |
1054 | 同一组件/指令的 inputs、outputs、models 间存在重复绑定名 |
INCORRECTLY_DECLARED_ON_STATIC_MEMBER |
1100 | Angular 特性(inputs、outputs、queries 等)被错误地声明在静态成员上 |
4.2 组件、指令、宿主指令与样式资源(-2003 ~ 2028)
| 成员 | 值 | 触发场景 |
|---|---|---|
COMPONENT_MISSING_TEMPLATE |
2001 | @Component 缺少 template/templateUrl |
PIPE_MISSING_NAME |
2002 | @Pipe 缺少 name |
PARAM_MISSING_TOKEN |
-2003 | DI 构造参数缺少注入 token |
DIRECTIVE_MISSING_SELECTOR |
2004 | @Directive/@Component 缺少 selector |
UNDECORATED_PROVIDER |
2005 | 未加装饰器的类被作为 provider 传入模块或指令 |
DIRECTIVE_INHERITS_UNDECORATED_CTOR |
2006 | 指令继承自"无 Angular 装饰器基类"的构造函数 |
UNDECORATED_CLASS_USING_ANGULAR_FEATURES |
2007 | 发现使用了 Angular 特性却没有装饰器的类 |
COMPONENT_RESOURCE_NOT_FOUND |
2008 | 组件无法解析外部资源(模板或样式文件) |
COMPONENT_INVALID_SHADOW_DOM_SELECTOR |
-2009 | 组件使用 ShadowDom 封装但 selector 不合 shadow DOM 标签要求 |
COMPONENT_NOT_STANDALONE |
2010 | 组件带 imports 却未标记 standalone: true |
COMPONENT_IMPORT_NOT_STANDALONE |
2011 | imports 中的指令/管道不是 standalone |
COMPONENT_UNKNOWN_IMPORT |
2012 | imports 中的类型既非指令/管道也非 NgModule |
HOST_DIRECTIVE_INVALID |
2013 | 编译器无法解析宿主指令元数据 |
HOST_DIRECTIVE_NOT_STANDALONE |
2014 | 宿主指令不是 standalone |
HOST_DIRECTIVE_COMPONENT |
2015 | 宿主指令被声明为组件(应是指令) |
INJECTABLE_INHERITS_INVALID_CONSTRUCTOR |
2016 | 带装饰器的类继承的基类构造函数与 Angular DI 不兼容 |
HOST_DIRECTIVE_UNDEFINED_BINDING |
2017 | 宿主指令为不存在的绑定设置别名 |
HOST_DIRECTIVE_CONFLICTING_ALIAS |
2018 | 宿主指令别名与既有绑定的公开名冲突 |
HOST_DIRECTIVE_MISSING_REQUIRED_BINDING |
2019 | 宿主指令定义未暴露其要求的绑定 |
CONFLICTING_INPUT_TRANSFORM |
2020 | 同一输入既配置了 transform 又有 ngAcceptInputType_ 成员 |
COMPONENT_INVALID_STYLE_URLS |
2021 | 组件同时使用 styleUrls 与 styleUrl |
COMPONENT_UNKNOWN_DEFERRED_IMPORT |
2022 | deferredImports 中的类型不是组件/指令/管道 |
NON_STANDALONE_NOT_ALLOWED |
2023 | 开启 strictStandalone 时声明了 standalone: false 组件 |
MISSING_NAMED_TEMPLATE_DEPENDENCY |
2024 | 具名模板依赖未定义在组件源文件中 |
INCORRECT_NAMED_TEMPLATE_DEPENDENCY_TYPE |
2025 | 具名模板依赖类型错误(如把指令类当作组件) |
UNSUPPORTED_SELECTORLESS_COMPONENT_FIELD |
2026 | selectorless 场景下使用了不支持的 @Component 字段 |
COMPONENT_ANIMATIONS_CONFLICT |
2027 | 组件既使用 animations 属性又在模板中使用 animate.enter/leave |
SERVICE_CONSTRUCTOR_DI |
2028 | @Service 类使用了构造函数依赖注入 |
4.3 符号与导入生成(-3003 ~ 3004)
| 成员 | 值 | 触发场景 |
|---|---|---|
SYMBOL_NOT_EXPORTED |
3001 | 引用的符号未从目标文件导出 |
IMPORT_CYCLE_DETECTED |
-3003 | 指令/管道之间会形成无法处理的循环导入(如 partial 编译模式) |
IMPORT_GENERATION_FAILURE |
3004 | 编译器无法为某个引用生成 import 语句 |
4.4 编译器配置(4001 ~ 4006)
| 成员 | 值 | 触发场景 |
|---|---|---|
CONFIG_FLAT_MODULE_NO_INDEX |
4001 | 扁平模块(flat module)缺少入口 index 文件 |
CONFIG_STRICT_TEMPLATES_IMPLIES_FULL_TEMPLATE_TYPECHECK |
4002 | 配置组合要求全量模板类型检查但未满足条件 |
CONFIG_EXTENDED_DIAGNOSTICS_IMPLIES_STRICT_TEMPLATES |
4003 | 配置了 extendedDiagnostics 却未开启 strictTemplates |
CONFIG_EXTENDED_DIAGNOSTICS_UNKNOWN_CATEGORY_LABEL |
4004 | extendedDiagnostics 中出现未知类别标签 |
CONFIG_EXTENDED_DIAGNOSTICS_UNKNOWN_CHECK |
4005 | extendedDiagnostics 中出现未知的检查项名称 |
CONFIG_EMIT_DECLARATION_ONLY_UNSUPPORTED |
4006 | 不支持 emitDeclarationOnly 模式的配置组合 |
4.5 解析错误(5001 ~ 5002)
| 成员 | 值 | 触发场景 |
|---|---|---|
HOST_BINDING_PARSE_ERROR |
5001 | 宿主表达式解析失败(如宿主监听器/绑定里出现管道) |
TEMPLATE_PARSE_ERROR |
5002 | 编译器无法解析组件模板 |
4.6 NgModule 分析(-6100 / 6001 ~ 6009)
| 成员 | 值 | 触发场景 |
|---|---|---|
NGMODULE_INVALID_DECLARATION |
6001 | declarations 中存在非法引用 |
NGMODULE_INVALID_IMPORT |
6002 | imports 中出现非法类型 |
NGMODULE_INVALID_EXPORT |
6003 | exports 中出现非法类型 |
NGMODULE_INVALID_REEXPORT |
6004 | exports 中的类型既不在 declarations 也未导入 |
NGMODULE_MODULE_WITH_PROVIDERS_MISSING_GENERIC |
6005 | 传入 NgModule 的 ModuleWithProviders 缺少泛型参数 |
NGMODULE_REEXPORT_NAME_COLLISION |
6006 | NgModule 导出多个同名指令/管道且需生成私有 re-export |
NGMODULE_DECLARATION_NOT_UNIQUE |
6007 | 指令/管道同时属于两个及以上 NgModule 的 declarations |
NGMODULE_DECLARATION_IS_STANDALONE |
6008 | standalone 指令/管道被放进 NgModule 的 declarations |
NGMODULE_BOOTSTRAP_IS_STANDALONE |
6009 | standalone 组件出现在 NgModule 的 bootstrap 列表 |
WARN_NGMODULE_ID_UNNECESSARY |
-6100 | 声明 id: module.id(编译器显式禁用的反模式) |
4.7 Schema 校验与模板语义(-8001 ~ -8003,8004 ~ 8029)
| 成员 | 值 | 触发场景 |
|---|---|---|
SCHEMA_INVALID_ELEMENT |
-8001 | 元素名未通过 DOM Schema 校验 |
SCHEMA_INVALID_ATTRIBUTE |
-8002 | 属性名未通过 DOM Schema 校验 |
MISSING_REFERENCE_TARGET |
-8003 | #ref="target" 找不到匹配指令 |
MISSING_PIPE |
8004 | 模板使用的管道在编译作用域中不存在 |
WRITE_TO_READ_ONLY_VARIABLE |
8005 | 对只读的模板变量赋值(如 (click)="something = ...") |
DUPLICATE_VARIABLE_DECLARATION |
8006 | 模板变量重复声明(如 *ngFor 中 let i 与 let i = index 冲突) |
SPLIT_TWO_WAY_BINDING |
8007 | 双向绑定的输入与输出目标不一致 |
MISSING_REQUIRED_INPUTS |
8008 | 指令的必填输入没有被绑定 |
ILLEGAL_FOR_LOOP_TRACK_ACCESS |
8009 | @for 的 track 表达式访问了不可用的变量 |
INACCESSIBLE_DEFERRED_TRIGGER_ELEMENT |
8010 | @defer 触发器元素不存在或位于不同视图 |
CONTROL_FLOW_PREVENTING_CONTENT_PROJECTION |
8011 | 投影插槽被多根节点的控制流包裹,导致后代无法投影 |
DEFERRED_PIPE_USED_EAGERLY |
8012 | deferredImports 的管道在 @defer 块外被使用 |
DEFERRED_DIRECTIVE_USED_EAGERLY |
8013 | deferredImports 的指令/组件在 @defer 块外被使用 |
DEFERRED_DEPENDENCY_IMPORTED_EAGERLY |
8014 | deferredImports 的依赖又被放进了普通 imports |
ILLEGAL_LET_WRITE |
8015 | 表达式尝试向 @let 声明赋值 |
LET_USED_BEFORE_DEFINITION |
8016 | 在 @let 定义前读取该变量 |
CONFLICTING_LET_DECLARATION |
8017 | @let 声明与同作用域其他符号冲突 |
UNCLAIMED_DIRECTIVE_BINDING |
8018 | selectorless 指令语法中的绑定未命中该指令任何 input/output |
DEFER_IMPLICIT_TRIGGER_MISSING_PLACEHOLDER |
8019 | 隐式触发器的 @defer 缺少 @placeholder |
DEFER_IMPLICIT_TRIGGER_INVALID_PLACEHOLDER |
8020 | 隐式触发器的 @placeholder 配置非法(如多根节点) |
DEFER_TRIGGER_MISCONFIGURATION |
8021 | @defer 定义了不可达/冗余的触发器组合 |
FORM_FIELD_UNSUPPORTED_BINDING |
8022 | FormField 指令上存在不支持的绑定 |
MULTIPLE_MATCHING_COMPONENTS |
-8023 | 编译作用域中多个组件匹配模板中同一元素 |
CONFLICTING_HOST_DIRECTIVE_BINDING |
-8024 | 宿主指令 input/output 以同名重复暴露 |
FOREIGN_COMPONENT_UNSUPPORTED_BINDING |
8025 | 外来组件(foreign component)节点上有不支持的 Angular 绑定 |
INVALID_CONTENT_PLACEMENT |
8026 | @content 块不是外来组件的直接子节点 |
FOREIGN_COMPONENT_CONTENT_UNNECESSARY_FOR_CHILDREN |
8027 | @content 块命名为 children(应隐式传递,命名多余) |
CONFLICTING_CONTENT_DECLARATION |
8028 | 同一外来组件声明多个同名 @content 块 |
CONFLICTING_CONTENT_AND_PROPERTY |
8029 | @content 块名与父外来组件上的输入绑定冲突 |
4.8 模板类型检查与信号语义(8101 ~ 8118)
| 成员 | 值 | 触发场景 |
|---|---|---|
INVALID_BANANA_IN_BOX |
8101 | 双向绑定语法写反(<div ([foo])="bar" />,括号包在方括号外层) |
NULLISH_COALESCING_NOT_NULLABLE |
8102 | ?? 左侧类型不含 null/undefined |
MISSING_CONTROL_FLOW_DIRECTIVE |
8103 | 使用 *ngIf 等控制流指令但未导入 CommonModule |
TEXT_ATTRIBUTE_NOT_BINDING |
8104 | 文本属性(如 class.blue="true")不会按绑定处理 |
MISSING_NGFOROF_LET |
8105 | 使用 NgForOf 却忘了写 let(如 *ngFor="item of items") |
SUFFIX_NOT_SUPPORTED |
8106 | 绑定后缀不受支持(如 [attr.width.px]) |
OPTIONAL_CHAIN_NOT_NULLABLE |
8107 | ?. 左侧类型不含 null/undefined |
SKIP_HYDRATION_NOT_STATIC |
8108 | ngSkipHydration 被当作绑定使用(应为静态属性) |
INTERPOLATED_SIGNAL_NOT_INVOKED |
8109 | 模板插值中的信号函数未加括号调用 |
UNSUPPORTED_INITIALIZER_API_USAGE |
8110 | initializer API 被用在非初始化上下文中(如普通函数内返回 input()) |
UNINVOKED_FUNCTION_IN_EVENT_BINDING |
8111 | 事件绑定写了 (click)="myFunc" 却未调用 |
UNUSED_LET_DECLARATION |
8112 | 模板中 @let 声明从未被使用 |
UNUSED_STANDALONE_IMPORTS |
8113 | @Component.imports 里的符号在模板中未使用 |
UNPARENTHESIZED_NULLISH_COALESCING |
8114 | 空值合并与 &&/` |
UNINVOKED_TRACK_FUNCTION |
8115 | @for 的 track 函数未被调用(应 track fn(item)) |
MISSING_STRUCTURAL_DIRECTIVE |
8116 | 模板使用了未导入的结构型指令 |
UNINVOKED_FUNCTION_IN_TEXT_INTERPOLATION |
8117 | 文本插值中函数未加括号({{ firstName }}) |
FORBIDDEN_REQUIRED_INITIALIZER_INVOCATION |
8118 | input.required() 等在禁止上下文(属性初始化器、构造函数)中被调用 |
4.9 其余专用码段(8900 ~ 11102)
| 成员 | 值 | 触发场景 |
|---|---|---|
INLINE_TCB_REQUIRED |
8900 | 类型检查需生成内联 type check block,但当前环境不支持 |
INLINE_TYPE_CTOR_REQUIRED |
8901 | 类型检查需为指令/组件生成内联类型构造器,但环境不支持 |
INJECTABLE_DUPLICATE_PROV |
9001 | Injectable 已存在 ɵprov 属性 |
SUGGEST_STRICT_TEMPLATES |
10001 | 建议开启 strictTemplates 以利用 Angular Language Service 全部能力 |
SUGGEST_SUBOPTIMAL_TYPE_INFERENCE |
10002 | 结构型指令可提供高级类型收窄,但当前类型检查配置无法参与推断 |
LOCAL_COMPILATION_UNRESOLVED_CONST |
11001 | 局部编译中需静态解析来自编译单元外部的 const(如作为装饰器参数) |
LOCAL_COMPILATION_UNSUPPORTED_EXPRESSION |
11003 | 局部编译暂不支持的表达式/语法 |
DEFER_BLOCK_MISSING_NAME_PARAMETER |
11100 | @defer 缺少 name 参数但 deferredImports 是对象 |
DEFER_BLOCK_UNKNOWN_NAME_PARAMETER |
11101 | @defer 的 name 参数不匹配 deferredImports 中的任何条目 |
DEFER_BLOCK_INVALID_NAME_PARAMETER |
11102 | @defer 指定了 name 参数但 deferredImports 不是对象 |
特别说明:源码注释明确指出 10XXX 码段专门保留给非 ts.DiagnosticCategory.Error 类别的诊断(error_code.ts),这类诊断由编译器在 Language Service 等工具的要求下生成,因此在常见构建报错中不出现。
五、错误码如何被生产与消费:诊断抛出的底层链路
仅知道枚举含义还不够。ErrorCode 真正发挥作用,是在 error.ts 提供的工具函数中,把"枚举码 + TS AST 节点 + 消息"组装成 ts.Diagnostic。
致命诊断 FatalDiagnosticError:当 ngtsc 在分析阶段遇到无法继续的错误时,会直接抛出该异常,内部保存错误码、ts.Node、消息与可选的关联信息(error.ts)。其 toDiagnostic() 调用 makeDiagnostic 完成转换。makeDiagnostic 的核心逻辑是:
export function makeDiagnostic(
code: ErrorCode,
node: ts.Node,
messageText: string | ts.DiagnosticMessageChain,
relatedInformation?: ts.DiagnosticRelatedInformation[],
category: ts.DiagnosticCategory = ts.DiagnosticCategory.Error,
): ts.DiagnosticWithLocation {
node = ts.getOriginalNode(node);
return {
category,
code: ngErrorCode(code),
file: ts.getOriginalNode(node).getSourceFile(),
start: node.getStart(undefined, false),
length: node.getWidth(),
messageText,
relatedInformation,
};
}
关键点:最终写入 ts.Diagnostic.code 的并非枚举原值,而是经过 ngErrorCode 编码后的 -99 标记数值(第 3 节已推导)。同时 makeDiagnostic 会回溯到原始节点(ts.getOriginalNode),保证经过中间代码变换后,报错位置仍指向开发者手写的源码。
配套工具还包括 makeDiagnosticChain / makeRelatedInformation(构造多级消息链与关联位置信息)、addDiagnosticChain(把补充说明追加进消息链)、isFatalDiagnosticError(运行时类型守卫)与 isLocalCompilationDiagnostics(识别局部编译相关错误,供 g3 等 1P 环境附加额外信息),全部从 index.ts 导出。
六、错误码的消费方:格式化、指南链接与外部工具
ErrorCode 生态的另外三个消费场景:
- 展示格式化:
formatCompilerErrorCode(code)返回NG${Math.abs(code)}。测试验证DECORATOR_ARG_NOT_LITERAL与DECORATOR_ARITY_WRONG分别格式化为NG1001、NG1002(format_compiler_error_spec.ts),与开发者日常在终端看到的错误号完全一致。 - 错误指南跳转:
addDiagnosticDetails向消息文本追加Find more at <base>/NG1001形式提示,测试断言消息拼接结果(同一 spec 的第三个用例)。指南基础地址根据@angular/compiler的VERSION计算:正式版指向v<major>.angular.dev/errors,预发布(-next/-rc)指向next.angular.dev/errors(error_details_base_url.ts)。 - 负值错误码的"是否有指南"判定:
errorCodeWithGuideFromDiagnosticCode先剥离99标记还原枚举码,再检查其是否命中负值成员(util.ts),从而决定是否值得为该诊断展示 error guide 链接。这也解释了为何 4.1~4.9 表格中部分成员刻意保留负数形态——它们是"拥有官方错误指南"的家族。
七、扩展阅读与排错建议
- 想查看
ErrorCode的完整 JSDoc 与模板示例,直接读枚举实现 error_code.ts,比 API 报告更详细; - 想确认某个诊断号与工具链的绑定关系是否被破坏,比对 API 报告 error_code.api.md 与源码是否一致(goldens 目录由 CI 校验);
- 想了解错误码格式化与指南链接的行为契约,阅读测试 format_compiler_error_spec.ts;
- 当遇到形如
NG8005的错误时,建议先反查上表定位错误族(8005 属模板语义区),再对照@for/@let/@defer/信号等对应特性写法修正模板,必要时开启strictTemplates与extendedDiagnostics让编译器在早期暴露更多 81xx 类问题。
总体而言,ErrorCode 是理解 Angular 编译器"如何说话"的钥匙:掌握它的分区规则与 -99 编码机制,既能在排错时快速定位错误语义域,也能在开发依赖 compiler-cli 的工具链时正确使用 ngErrorCode、formatCompilerErrorCode 与诊断指南接口。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0629
MiniCPM5-2BMiniCPM5-2B 是一款面向端侧、本地部署和资源受限场景的 2B 稠密 Transformer,能够达到同尺寸开源模型 SOTA 水平。Markdown00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
HivisionIDPhotos⚡️HivisionIDPhotos: a lightweight and efficient AI ID photos tools. 一个轻量级的AI证件照制作算法。Python07
DragonOSDragonOS is an operating system developed from scratch using Rust, with Linux compatibility. It is designed for **Serverless** scenarios. 使用Rust从0自研内核,具有Linux兼容性的操作系统,面向云计算Serverless场景而设计。Rust00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00