TypeSpec 1.5.0 版本深度解析:OpenAPI operationId 生成策略、时长编码增强与 LSP/API 能力升级
【免费下载链接】typespec项目地址: https://gitcode.com/GitHub_Trending/ty/typespec
TypeSpec 1.5.0(发布于 2025-10-08)是一次以「发射器灵活性与开发者工具链」为核心的版本升级。本指南以官方发布说明为骨架,结合当前仓库源码与测试用例,系统拆解本次更新中@typespec/compiler、@typespec/openapi3、typespec-vscode三大模块的新特性与问题修复,重点讲解 OpenAPI operationId 的三种生成策略、DurationKnownEncoding新增毫秒编码、@secret装饰器目标类型的扩展,以及编译器公共 API 与 LSP 入口点配置的增强。读完本文,你将掌握这些新能力的确切用法、配置项取值范围与底层实现原理,可直接应用于实际 TypeSpec 项目中。
版本概览
本次发布共涉及三大包:
| 包 | 类型 | 亮点 |
|---|---|---|
@typespec/compiler | Features / Bug Fixes | 测试 API、时长编码、LSP 入口点、公共 API 暴露、@secret扩展、codefix 增强 |
@typespec/openapi3 | Features / Bug Fixes | 新增operation-id-strategy选项、importer 一系列导入修复 |
typespec-vscode | Features | LSP 入口点配置、编译任务与 emitter 加载行为调整 |
此外@typespec/json-schema与@typespec/rest各修复了一个崩溃问题。
一、@typespec/openapi3:全新的operation-id-strategy选项
这是 1.5.0 中面向 OpenAPI 文档使用者最直接的可见变更。在未显式使用@operationId时,OpenAPI 发射器如何为操作生成operationId,此前是固定行为;现在你可以通过tspconfig.yaml中的options["@typespec/openapi3"]显式控制。
三种策略的定义
在 packages/openapi3/src/lib.ts 中,策略类型被定义为:
export type OperationIdStrategy = "parent-container" | "fqn" | "explicit-only";对应的行为如下:
| 策略 | 说明 | 默认分隔符 | 示例(namespace Baz { namespace Bar { op foo(): string } }位于@service命名空间下) |
|---|---|---|---|
parent-container | 取操作最近的父容器(interface 或 namespace)名称与操作名拼接。这是默认行为,也是 1.5.0 之前的历史行为 | _ | Bar_foo |
fqn | 从 service 根到操作的完整限定名逐级拼接 | . | Baz.Bar.foo |
explicit-only | 完全不自动生成,只输出显式通过@operationId指定的 ID | — | 未设置@operationId时该操作为undefined |
三种策略的默认分隔符实现在 packages/openapi3/src/openapi.ts 中,通过resolveOperationIdDefaultStrategySeparator按策略返回_、.或空字符串。
在 tspconfig.yaml 中使用
operation-id-strategy既可接受简单字符串,也可接受带自定义separator的对象形式(完整 JSON Schema 校验见 packages/openapi3/src/lib.ts):
emit: - "@typespec/openapi3" options: "@typespec/openapi3": operation-id-strategy: "fqn" # 字符串形式:Baz.Bar.foo或自定义分隔符:
options: "@typespec/openapi3": operation-id-strategy: kind: "fqn" # "parent-container" | "fqn" | "explicit-only" separator: "/" # 自定义拼接分隔符,如 Baz/Bar/foo对象形式各字段说明:
| 字段 | 类型 | 默认值 | 说明 |
|---|---|---|---|
kind | "parent-container" \| "fqn" \| "explicit-only" | "parent-container" | 决定@operationId缺失时的生成方式 |
separator | string | 随kind变化(_/./"") | 用于拼接操作名各段的字符 |
在 packages/openapi3/src/openapi.ts 中,resolveOperationIdStrategy负责归一化:未配置时返回默认的{ kind: "parent-container", separator: "_" };传入字符串时自动补全对应默认分隔符;传入对象时若省略separator同样按kind取默认值。类型定义同时支持字符串与对象两种写法(见 lib.ts)。
底层实现:OperationIdResolver
生成逻辑收敛在独立的OperationIdResolver类中(packages/openapi3/src/operation-id-resolver/operation-id-resolver.ts),其解析流程为:
- 若操作上显式存在
@operationId(通过getOperationId读取),直接返回该值,策略不生效; - 否则按策略执行
#resolveInternal:parent-container:取路径最后两段拼接(operationPath.slice(-2).join(separator));fqn:取完整路径拼接(operationPath.join(separator));explicit-only:返回undefined;
- 路径由
#getOperationPath构造:从操作名出发,沿interface/namespace逐级向上,直到遇到全局命名空间或@service标记的命名空间为止(这部分决定了为什么直接定义在 service 命名空间下的操作不会带上命名空间前缀)。
去重机制
不同 namespace 下可能出现同名操作,导致生成的 operationId 冲突。1.5.0 同步修复了这一问题(原文档 Bug Fixes 中 "Deduplicate operation ids that would resolve to the same one")。OperationIdResolver内部维护#used集合,一旦发现已使用过的名称,通过#findNextAvailableName追加_2、_3后缀(见 operation-id-resolver.ts)。
测试用例验证
operation-id-resolver.test.ts(packages/openapi3/src/operation-id-resolver/operation-id-resolver.test.ts)为三种策略各编写了完整用例,可直接作为行为规格参考:
- parent-container:根级操作生成
foo;service 命名空间下直接定义的操作仍是foo(不拼接 service 名);interface 内为Bar_foo;嵌套 namespaceBaz > Bar中只取最近一层Bar_foo;去重用例中Onenamespace 的test与Twointerface 下的test分别解析为One_test与One_test_2; - fqn:嵌套 namespace 会得到完整路径
Baz.Bar.foo; - explicit-only:显式
@OpenAPI.operationId("explicit_foo")时返回explicit_foo,未设置时返回undefined。
二、@typespec/compiler新特性详解
2.1 DurationKnownEncoding 新增 milliseconds
DurationKnownEncoding枚举(packages/compiler/lib/std/decorators.tsp)原本只支持ISO8601与seconds两种编码。1.5.0 新增milliseconds成员,定义在 TypeSpec 语言标准库层面:
enum DurationKnownEncoding { ISO8601: "ISO8601", // ISO8601 时长字符串 seconds: "seconds", // 以秒为单位的整数或浮点数 milliseconds: "milliseconds", // 以毫秒为单位的整数或浮点数(1.5.0 新增) }对应的 TS 类型别名位于 packages/compiler/src/lib/decorators.ts,并在 packages/compiler/src/index.ts 中作为公共导出。它的典型用途是与@encode装饰器配合,例如将duration编码为毫秒数值:@encode(DurationKnownEncoding.milliseconds) duration elapsed: duration;。新增毫秒编码后,时长类型在 JSON 序列化场景下可以更精确地表达亚秒级数据,无需再手动换算为秒。
2.2@secret装饰器目标类型扩展
此前@secret只允许作用于Scalar与ModelProperty。1.5.0 将目标扩展为Model、Union、Enum,即任何数据类型都可以被标记为 secret,用于整体性的数据敏感性标记。该变更的意义在于:此前若要标记一个由多个字段组成的模型为敏感数据,只能逐字段添加装饰器;现在可以直接在模型级别声明,例如:
@secret model Credentials { username: string; password: string; }发射器(如各类客户端代码生成器)可以据此识别并整体规避对这些类型的日志输出或文档暴露。
2.3 Testing API 增强
本次为测试基础设施带来三处改进:
- Tester 暴露 marker 位置(
Expose marker position in Tester):在使用t.code\...`模板测试时,/marker/` 标记的位置现在可以被测试代码读取,便于精确断言诊断信息在源码中的位置; - Linter 规则测试器传递
parseDocs(Bug Fixes 中 "Linter rule tester not passing parseDocs option to new tester instance"):修复了 linter 规则测试时文档解析选项未被传递给新 tester 实例的问题; - Tester 支持子导出(
Support sub exports without having them being defined as separate libraries):测试框架允许直接使用库的子路径导出(如@typespec/openapi/xxx),无需将其注册为独立库; - 同时修复了一个可能导致测试超时的问题。
2.4 编译器公共 API 扩展
1.5.0 集中暴露了一批此前为内部实现的 API,方便库作者与工具链开发者使用:
| API | 位置 | 说明 |
|---|---|---|
applyCodeFix/applyCodeFixes/resolveCodeFix/applyCodeFixEdits | packages/compiler/src/index.ts | 编程式地应用 codefix。applyCodeFixEdits负责按编辑片段重写文件内容,applyCodeFixes批量处理多个 codefix |
createSuppressCodeFix | packages/compiler/src/core/compiler-code-fixes/suppress.codefix.ts | 生成「添加 suppression 注释」的 codefix;程序内部在生成诊断 codefix 时即调用它(见 program.ts) |
getNodeForTarget | packages/compiler/src/ast/index.ts | 从诊断 target 定位对应的 AST 节点,从@typespec/compiler/ast导出 |
其中「codefix 跨文件支持」也是一项重要增强:codefix 可以作用于与诊断不同的文件,并在需要时自动创建新文件;suppression codefix 在查找可用父节点时会向上寻找第一个有效父节点(Bug Fixes 中 "Add suppression codefix looks up for the first valid parent")。此外,@secret等 API 现在支持传入 suppression 消息,允许在抑制诊断时附加说明文字。
2.5 测试框架与发射器 Schema 支持 Union
- Emitter 配置 Schema 支持 Union:发射器在声明配置项类型时可以使用 union 类型,使得 JSON Schema 校验可以表达更丰富的取值组合;
- Hover 显示模板参数默认值:LSP hover 签名中会展示模板参数的默认值,提升语言服务体验。
2.6 LSP 入口点配置
[LSP] Allow configuring which file names to use as entrypoints(PR 编号 7929)让语言服务器不再硬编码入口点文件名。底层配置项entrypoint定义于编译器的配置 Schema 中(packages/compiler/src/config/config-schema.ts),并在 config-loader.ts 中被加载校验;对应 Bug Fix 是「entrypoint 配置默认值为 null 时不再出错」。这意味着你可以自定义项目入口.tsp文件名(默认是main.tsp),LSP 会按配置识别入口点,这在大型 monorepo 或非标准目录结构中尤为实用。
2.7 其他编译器修复
- 修正 TypeSpec「unused-using」警告文案中的语法错误(删除多余的 "be" 一词);
- 修复 LSP 在动态加载包内某个库后出现的连接失败问题;
- 支持导入自身模块(如位于
@typespec/openapi包内时以@typespec/openapi/some/path导入自身),遵循 ESM 规范。
三、typespec-vscode 扩展更新
1.5.0 同步将 LSP 入口点配置带入了 VS Code 扩展,并调整了两项启动行为(PR 8346):
- 限制启动时创建的 vscode 任务:扩展启动时不再大量预创建编译任务,降低启动开销;
- 默认不在 LSP 编译中包含 emitters:此前 LSP 编译会默认带上
tspconfig.yaml中声明的全部 emitters,现在默认不包含任何 emitter。如需恢复显式控制,可在 VS Code 设置中配置:
{ "typespec.lsp.emit": ["@typespec/openapi3"] }将typespec.lsp.emit设置为["<config:defaults>"]可以恢复为「使用 tspconfig.yaml 中定义的全部 emitters」的旧行为。这一调整让编辑器内的实时类型检查更轻量,把 emitter 的执行留给显式的编译命令。
四、@typespec/openapi3的导入器(importer)修复
本次修复集中在「从已有 OpenAPI 描述导入回 TypeSpec」的路径上,这些修复直接关系到 OpenAPI → TypeSpec 的逆向转换质量:
| 修复点 | 说明 |
|---|---|
additionalProperties: true {}导入 | 现在正确转换为Record<unknown>,而不是生成不完整的模型 |
单一any/oneOf解包 | 当anyOf/oneOf只有一个成员时解包该成员,得到语义更有意义的类型 |
| 枚举默认值加前缀 | 导入时为枚举的默认值加上enum前缀,避免与普通字面量混淆 |
| 不再为每个成员类型重复输出默认值 | 避免在导入的描述中为每个成员类型重复发射默认值 |
anyOf/oneOf+type: null | 正确导入并保留装饰器、文档注释 |
| multipart 请求体 | 仅在实际存在 multipart 请求体时才导入,避免生成多余结构 |
| null 默认值崩溃 | 修复「null 值默认值导致导入崩溃」的回归问题 |
属性名为set的崩溃 | 修复在属性名为set时的崩溃(@typespec/json-schema同步修复) |
五、@typespec/rest修复:@parentResource递归引用
@typespec/rest修复了一个崩溃场景:当资源通过@parentResource递归地引用自身时不再崩溃。例如资源层级中 parent 与 child 类型互相循环引用(如model A { @parentResource parent?: A; }),此前会导致发射器栈溢出,1.5.0 已处理。
总结与升级建议
TypeSpec 1.5.0 的关键升级路径建议:
- OpenAPI 使用者:升级后注意
operation-id-strategy默认值仍为parent-container,即历史行为不变、不会破坏现有文档;如需更稳定的跨语言 SDK 命名,可切换为fqn并在文档生成后对比前后差异; - 库 / 工具链作者:
applyCodeFix、createSuppressCodeFix、getNodeForTarget等 API 已公开,可以围绕编译器诊断构建自定义修复工具; - 编辑器体验:VS Code 用户可通过
typespec.lsp.emit精细控制 LSP 编译范围,获得更快的编辑反馈; - 时长数据建模:新
milliseconds编码让亚秒级 duration 的 JSON 表达更直接,配合@encode即可落地。
相关源码与测试均可在仓库内查阅:openapi3 发射器选项定义、OperationIdResolver 实现与其测试、DurationKnownEncoding 标准库声明、codefix 公共 API。
【免费下载链接】typespec项目地址: https://gitcode.com/GitHub_Trending/ty/typespec
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考