拓冰建站拓冰建站
首页 / 资讯中心 / 正文

TypeSpec 诊断 API 全解析:在自定义库中声明、报告与收集错误和警告

TypeSpec 诊断 API 全解析在自定义库中声明、报告与收集错误和警告【免费下载链接】typespec项目地址: https://gitcode.com/GitHub_Trending/ty/typespecTypeSpec 编译器通过一套统一的**诊断 APIDiagnostic API**来报告规范specification中的错误error与警告warning任何自定义库library、装饰器、校验钩子$onValidate和发射器emitter都应通过这套 API 向用户呈现问题。本文基于 extending-typespec/diagnostics.md 文档结合typespec/compiler的实际源码实现完整讲解诊断的声明、报告、收集机制、短名与别名解析规则以及真实库如typespec/http中的落地写法帮助你写出符合官方规范、用户体验良好的 TypeSpec 库。诊断 API 在 TypeSpec 中的定位TypeSpec 编译器将发现规范中的问题与把问题呈现给用户这两件事解耦无论是语法错误、语义错误、库内业务规则校验失败还是 emitter 输出过程中的异常状况最终都会归一化为统一的Diagnostic对象通过Program的reportDiagnostic通道对外输出并在 CLI、IDEVSCode 扩展、tsp-server等不同前端中得到一致的展示与抑制suppress能力。诊断 API 的核心入口是createTypeSpecLibrary它位于 packages/compiler/src/core/library.ts。当你的库通过该函数声明了diagnostics映射后编译期即获得了完整的类型检查as const 泛型约束并自动生成reportDiagnostic与createDiagnostic两个工具函数。最佳实践用诊断 API 而非 throw官方文档明确给出两条黄金准则❌避免用throw报告错误。在 TypeSpec 中抛出的任何异常都会被用户视为你库中的 bugcompiler 内部会用compilerAssert兜底拦截并提示compiler bug。✅使用诊断 API 报告预期内的错误与警告✅ 在装饰器decorator、$onValidate或$onEmit中直接调用reportDiagnostic❌不要在 accessor被其他库或 emitter 复用的函数中直接调用reportDiagnostic而应返回诊断元组交由调用方决定如何处理详见下文收集诊断。这一约定的背后逻辑是decorator /$onValidate/$onEmit是 TypeSpec 语义检查与发射流程的终点此时产生的问题必然需要呈现给用户而 accessor 可能被多次调用例如被多个 emitter 复用若每次都直接上报会出现重复诊断。诊断的硬性要求每个诊断必须满足以下约束对应 packages/compiler/src/core/types.ts 中的Diagnostic接口要求说明code必须提供。完整代码为库名/本地代码lib-name/local-code例如typespec/my-lib/no-arrayseverity必须提供取值为error或warning。error不能被抑制suppress只有warning和 lint 规则诊断才允许被#suppress指令屏蔽消息必须至少有一条。以default作为messageId的消息会被当作默认消息当调用方未指定messageId时自动选中消息参数可选。通过paramMessage模板在消息中插值动态信息在createDiagnosticCreatorpackages/compiler/src/core/diagnostic-creator.ts的实现中未声明的code或未定义的messageId会直接抛出带提示的异常列出所有已定义代码/消息从而在开发期尽早暴露库定义与调用不一致的问题最终生成的Diagnostic对象包含code由库名自动拼接、severity、message、target并可选携带url诊断文档链接与codefixes代码修复。声明你将上报的诊断所有诊断必须先在库定义中声明。以下为文档给出的完整示例同时展示了固定消息、参数化消息、多消息三种形态import { createTypeSpecLibrary, paramMessage } from typespec/compiler; // lib.js export const $lib createTypeSpecLibrary({ name: typespec/my-lib, diagnostics: { // 固定消息的基础诊断 no-array: { severity: error, messages: { default: Array is not allowed in my-lib models., }, }, // 参数化消息 duplicate-route: { severity: error, messages: { default: paramMessageRoute ${path} is being referenced in 2 different operations., }, }, // 同一 code 下的多条消息通过 messageId 区分 duplicate-name: { severity: warning, messages: { default: paramMessageDuplicate type name: ${value}., parameter: paramMessageDuplicate parameter key: ${value}., }, }, }, } as const); // 重新导出辅助函数便于直接调用 export const { reportDiagnostic, createDiagnostic } $lib;声明后上述三个诊断的完整名称为typespec/my-lib/no-arraytypespec/my-lib/duplicate-routetypespec/my-lib/duplicate-nameparamMessage的实现位于 packages/compiler/src/core/param-message.ts它是一个标签模板函数收集模板中的键名keys并在调用时用传入的字典dict替换占位符未提供的键会被跳过而非报错因此消息模板对参数的容忍度较高。注意as const至关重要——它让 TypeScript 能从字面量中推断出完整的 code 与 messageId 联合类型从而在调用reportDiagnostic时获得编译期校验。真实库中的声明写法typespec/http是官方库中非常典型的例子packages/http/src/lib.ts可以看到多消息、warning 与 error 混合声明的真实形态export const $lib createTypeSpecLibrary({ name: typespec/http, diagnostics: { http-verb-duplicate: { severity: error, messages: { default: paramMessageHTTP verb already applied to ${entityName}, }, }, // ... 省略中间诊断 double-slash: { severity: warning, messages: { default: paramMessageRoute will result in duplicate slashes as parameter ${paramName} use path expansion and is prefixed with a /, optionalUnset: paramMessageRoute will result in duplicate slashes when optional parameter ${paramName} is not set., optionalSet: paramMessageRoute will result in duplicate slashes when optional parameter ${paramName} is set., }, }, }, } as const);短名short name解析机制完整诊断名typespec/my-lib/no-array冗长且含scope/前缀不利于在#suppress指令或 lint 配置中书写。因此编译器支持去掉包作用域的短名typespec/my-lib/no-array→my-lib/no-arraytypespec/http/duplicate-name→http/duplicate-name短名解析逻辑集中在 packages/compiler/src/core/diagnostic-code.ts 的getPackageShortName规则如下包名模式短名显式声明了alias使用alias优先级最高typespec/namename如typespec/http→httpscope/typespec-namename如azure-tools/typespec-client-generator-core→client-generator-coretypespec-name无 scopename其他形式无短名只能使用完整名无论是完整名还是短名在抑制suppress或配置 lint 规则时都会被接受编译器内部通过createDiagnosticCodeResolverpackages/compiler/src/core/diagnostic-code.ts统一解析回规范化的完整形式。自定义别名alias当自动剥离作用域得到的短名不够直观、或与其他库冲突时库可以声明自定义aliasexport const $lib createTypeSpecLibrary({ name: azure-tools/typespec-client-generator-core, alias: tcgc, diagnostics: { /* ... */ }, } as const);此时诊断与 lint 规则即可写作tcgc/code例如#suppress tcgc/no-foo。alias的合法性由 diagnostic-code.ts 中的正则^[a-z0-9](?:-[a-z0-9])*$校验只能是 kebab-case——仅含小写字母、数字和单连字符不能有大写、下划线、空格也不能以连字符开头/结尾或出现连续连字符如tcgc、my-lib-2均合法。若alias非法createTypeSpecLibrary会通过compilerAssert直接抛错见 library.ts。短名歧义处理如果两个已加载库解析出相同的短名无论是自动剥离还是显式 alias该短名即被视为歧义ambiguous引用该短名时会收到警告提示并列出所有候选库的完整名对这几个冲突的库必须使用完整名typespec/xxx/...进行抑制或配置。createDiagnosticCodeResolver通过shortToNames映射收集同短名候选并在getAmbiguousShortName中返回候选列表用于生成警告diagnostic-code.ts。报告诊断reportDiagnostic 的三种用法在装饰器、$onValidate或$onEmit中直接调用从$lib解构出的reportDiagnosticimport { reportDiagnostic } from ./lib.js; // 1) 固定消息只需 code target reportDiagnostic(program, { code: no-array, target: diagnosticTarget, }); // 2) 参数化消息通过 format 注入模板占位符 reportDiagnostic(program, { code: duplicate-route, format: { path: /foo }, target: diagnosticTarget, }); // 3) 多消息用 messageId 选择具体消息 reportDiagnostic(program, { code: duplicate-name, messageId: parameter, format: { value: $select }, target: diagnosticTarget, });关键字段说明program当前编译的Program实例code库内声明的本地代码不含库名前缀库名前缀由 creator 自动拼接messageId默认default用于多消息诊断选择具体文案format供paramMessage模板插值使用的键值对target诊断定位目标可以是语法节点、TypeSpec 类型实体、Sym等见 types.ts 的DiagnosticTarget定义用于在 IDE 中高亮具体位置。reportDiagnostic的底层实现diagnostic-creator.ts只是createDiagnostic后再调用program.reportDiagnostic(diag)真正组装诊断拼接完整 code、解析消息、附加url/codefixes的逻辑在createDiagnostic中完成。在typespec/http中$onValidate流程里就有真实调用案例例如报告未找到服务警告packages/http/src/operations.tsif (namespace.operations.size 0 locationContext.type project) { reportDiagnostic(program, { code: no-service-found, format: { namespace: namespace.name }, target: namespace, }); }收集诊断accessor 中的标准模式当问题可能产生于一个可复用的 accessor例如库内部的getRoutes()、getParameters()等被多次调用的函数时不要直接上报到 program而是返回诊断元组[结果, 诊断列表]让调用方决定上报时机。这能避免 accessor 被多次调用时产生重复诊断。元组类型在源码中定义为DiagnosticResultT [T, readonly Diagnostic[]]types.ts。方式一借助 createDiagnosticCollectorimport { createDiagnosticCollector, createDiagnostic } from typespec/compiler; function getRoutes(): [Route[], readonly Diagnostic[]] { const diagnostics createDiagnosticCollector(); diagnostics.add( createDiagnostic(program, { code: no-array, target: diagnosticTarget, }), ); // pipe把 getParameters() 返回元组中的诊断并入当前收集器并取出其数据 const params diagnostics.pipe(getParameters()); const routes computeRoutes(params); return diagnostics.wrap(routes); }DiagnosticCollector提供四个方法实现见 diagnostics.ts方法作用add(diagnostic)向收集器追加一条诊断pipe(result)解包一个DiagnosticResult元组合并其中的诊断返回数据本身用于串联调用链wrap(value)将最终结果包成[value, diagnostics]元组返回join(result)合并另一个元组的诊断并返回合并后的元组typespec/http的 payload.ts 中就大量使用了diagnostics.pipe(getContentTypes(...))这种链式收集写法。方式二手动收集不引入 collector直接用数组手动管理import { createDiagnostic } from typespec/compiler; function getRoutes(): [Route[], readonly Diagnostic[]] { const diagnostics: Diagnostic[] []; diagnostics.push( createDiagnostic(program, { code: no-array, target: diagnosticTarget, }), ); return [routes, diagnostics]; }配套工具createDiagnostic与reportDiagnostic同源但只构造Diagnostic对象、不立即上报专供收集模式使用ignoreDiagnostics(result)diagnostics.ts在明确想忽略 accessor 附带诊断时直接取出元组中的数据部分。与 Linter 规则的联动诊断体系与 lint 规则共用同一套声明与短名机制。库可以用createLinterRule声明规则并在规则上下文LinterRuleContext中通过context.reportDiagnostic上报诊断linter.ts。以编译器内置规则unused-using为例unused-using.rule.tscreateLinterRule({ name: unused-using, severity: warning, description: Linter rules for unused using statement., messages: { default: paramMessageusing ${code} is declared but never used., }, create(context) { return { root: (program) { program.resolver.getUnusedUsings().forEach((target) { context.reportDiagnostic({ format: { code: getUsingName(target.name) }, target, codefixes: [removeUnusedCodeCodeFix(target)], }); }); }, }; }, });可以看到规则消息同样支持paramMessage参数化并可附带codefixes代码修复——这也是为什么规则和诊断都能用短名如tcgc/no-foo在#suppress或 lint 配置中被引用两者共享createDiagnosticCodeResolver的解析管线。小结TypeSpec 的诊断 API 以声明 → 报告 → 收集为主线配合短名/别名解析形成了完整的错误上报与抑制体系声明在createTypeSpecLibrary的diagnostics映射中声明 code、severity、messages默认消息用default动态消息用paramMessage报告在 decorator、$onValidate、$onEmit中通过reportDiagnostic(program, { code, format, messageId, target })上报error不可被抑制收集在 accessor 中返回[T, Diagnostic[]]元组借助createDiagnosticCollector的add/pipe/wrap/join汇总诊断交由调用方决定上报避免重复诊断引用诊断与 lint 规则同时支持完整名scope/pkg/code与短名pkg/code或自定义alias/code别名需为 kebab-case短名冲突时必须回退到完整名。遵循这套约定你的 TypeSpec 库就能与编译器、IDE、CLI 及所有官方工具链无缝协作为用户提供定位精准、可抑制、可修复的高质量错误信息。【免费下载链接】typespec项目地址: https://gitcode.com/GitHub_Trending/ty/typespec创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门