- 开发工具
- 静态分析
- Lint
- 代码质量
【免费下载链接】typescript-eslint
:sparkles: Monorepo for all the tooling which enables ESLint to support TypeScript
@typescript-eslint/rule-schema-to-typescript-types是 typescript-eslint 仓库中的一个内部工具包,它读取规则在meta.schema中声明的 JSON Schema(v4),并输出与之等价的 TypeScript 类型字符串(形如type Options = [...])。这些生成结果既用于文档展示,也作为规则类型定义的权威来源。读完本文,你将理解该包的输入/输出约定、类型映射规则、$ref/$defs处理方式、错误边界与优化策略,并能独立复现其核心能力。
一、包定位:内部工具,服务于文档与类型生成
在 typescript-eslint 这个 monorepo 中,packages/rule-schema-to-typescript-types/README.md 明确声明这是一个Internal Package(内部包),面向 typescript-eslint 仓库自身的工具链,而非面向最终用户的独立产品。官方文档页 docs/packages/RuleSchemaToTypeScriptTypes.mdx 对它的描述是一句话:
Converts ESLint rule schemas to equivalent TypeScript type strings ✨
包的package.json(packages/rule-schema-to-typescript-types/package.json)中 version 为8.71.1,依赖了同仓库的@typescript-eslint/type-utils与@typescript-eslint/utils,另外依赖natural-compare(用于排序,保证输出确定性),Node 版本要求^18.18.0 || ^20.9.0 || >=21.1.0。
该包在整个仓库中的核心消费方是 eslint-plugin 的类型生成测试 packages/eslint-plugin/tests/schemas.test.ts:它遍历所有规则的ruleDef.meta.schema,调用schemaToTypes生成类型字符串,再用 Prettier 格式化后写入schema-snapshots/*.shot快照文件。也就是说,每一个规则的"可读类型签名"都来自这个包。对应的快照产物可见 packages/eslint-plugin/tests/schema-snapshots/(例如no-unused-vars.shot),其中# SCHEMA:段是原始 JSON Schema,# TYPES:段是生成的类型定义。
二、入口函数schemaToTypes:输入与输出约定
对外唯一入口是schemaToTypes,实现在 src/index.ts:
export function schemaToTypes( schema: JSONSchema4 | readonly JSONSchema4[], ): string关键行为:
- 输入:可以是单个 JSON Schema v4 对象,也可以是 schema 数组。
JSONSchema4类型定义在 packages/utils/src/json-schema.ts(对应 JSON Schema draft v4 的各个子类型:JSONSchema4ObjectSchema、JSONSchema4ArraySchema、JSONSchema4RefSchema等)。 - 数组输入 → 元组类型:数组被当作规则 options 的元组,输出
type Options = [T0, T1, ...]; - 对象输入 → 单一类型:输出
type Options = T; - 空数组输入:返回固定的
/** No options declared */+type Options = [];(对应测试 tests/index.test.ts 的第一条用例); $defs/definitions展开:仅支持顶层的$defs或definitions(源码注释we only support defs at the top level for simplicity),每个定义键会被转换为 PascalCase 类型名(toPascalCase,首字母大写),并同时注册#/$defs/{key}与#/items/{index}/$defs/{key}两个引用路径,随后以"引用类型在前、Options 类型在后、块间空行分隔"的顺序拼接输出。
官方文档页 docs/packages/RuleSchemaToTypeScriptTypes.mdx 给出的最小示例:
import { schemaToTypes } from '@typescript-eslint/rule-schema-to-typescript-types'; schemaToTypes({ description: 'My great option!', items: { type: 'string' }, type: 'array', }); // 输出: // type Options = [ // /** My great option! */ // string[] // ];三、类型生成规则:JSON Schema → TS 类型的映射
核心递归逻辑在 src/generateType.ts,处理顺序如下:
$ref优先:命中refMap则生成TypeReferenceAST(引用类型名);enum:把枚举值生成联合类型(见第四节);anyOf/oneOf:均生成联合类型。源码注释特别说明:anyOf在 JSON Schema 语义上其实是组合(T、U、V 的任意交集组合),但实践中大多被用来模拟oneOf,因此本工具直接按联合类型处理;type关键字:按类型分发——any→unknown(不生成any,避免污染类型安全);null→null;number/string→ 直接输出对应关键字;boolean→boolean;integer→number(TS 无integer类型);array→ 走generateArrayType(第五节);object→ 走generateObjectType(第六节);
- 未声明
type且不含$ref/enum/oneOf的 schema→ 抛出NotSupportedError("untyped schemas without one of [$ref, enum, oneOf]"); type为数组(多类型 schema)→ 同样抛出NotSupportedError。
显式不支持的关键字
src/generateType.ts 定义了一组"可能应该支持但当前不支持"的关键字,命中即抛NotSupportedError:
allOf, dependencies, extends, maxProperties, minProperties, multipleOf, not, patternProperties错误信息会带上完整 JSON 序列化的目标 schema,方便定位问题。
四、枚举与联合类型:generateUnionType
src/generateUnionType.ts 负责把枚举成员转为联合类型成员:
- 字符串:转成单引号字面量(内部单引号会被转义为
\'); - 数字 / 布尔值:原样转成字面量;
- 对象:递归走
generateType; - 不支持的成员:
null或数组出现在 enum 中会抛NotSupportedError。
联合成员在输出时会被排序以保证确定性(依赖natural-compare),排序逻辑见 src/printAST.ts 的compareElements:不同节点类型先按代码文本自然比较;同为 tuple 时先按元素数量升序(避免 natural-compare 把长元组排前面),再按代码比较。
五、数组与元组:generateArrayType的启发式策略
src/generateArrayType.ts 是策略最复杂的一环,核心规则:
items缺失:抛UnexpectedError({type: 'array'}理论上可映射为any[],但过于宽松,工具拒绝生成);- 单类型数组(
items不是数组):直接生成T[];即使声明了minItems/maxItems也不生成元组(源码注释:[T, ...T[]]形式虽然可行,但为了文档可读性放弃,参见 issue #11117); items为数组 → 按元组处理,并引入三个关键约束:MAX_ITEMS_TO_TUPLIZE = 20:元组元素超过 20 个就不再元组化,直接退化为数组类型以保证简洁;maxItems语义:只有当maxItems < 20时才考虑;若maxItems > items.length且存在additionalItems(对象形式),则生成展开元素(打印为...T[]);maxItems小于 items 个数、或大于 items 个数却没有additionalItems,都会抛UnexpectedError;minItems语义:当 items 个数多于minItems时,不采用可选元素运算符[T, T?, T?](因为它允许['a', undefined, 'c']这种中间空洞),而是生成元组的联合[T] | [T, T] | [T, T, T],保证每个位置类型更精确(源码注释给出了这一类型安全论证),且只有联合的最后一个元组带展开参数。
例如测试中的用例[{ items: [{type:'string'}], type: 'array' }]输出为:
type Options = [ | [] | [string]]六、对象类型:必填/可选属性与索引签名
src/generateObjectType.ts 处理type: 'object'的 schema:
- 属性必填性:根据
required数组判定;不在required中的属性输出为可选(?:)。required非数组时按空集合处理; - 属性名转义:通过
@typescript-eslint/type-utils的requiresQuoting判断属性名是否需要加引号,必要时输出'prop-name'形式; - 索引签名:
additionalProperties === true或未声明时,索引签名类型为unknown;additionalProperties为对象时递归生成其类型。输出形式为[k: string]: T; - 属性排序:在打印阶段(src/printAST.ts)用
naturalCompare对属性名排序,保证"无论声明顺序如何,输出一致",并强制把对象打印为多行。
七、注释生成:description → JSDoc
src/getCommentLines.ts 将 schema 的description作为注释行;src/printAST.ts 的printComment负责排版:
- 单行描述 →
/** description */; - 多行 → 逐行加
*前缀的多行 JSDoc;描述中的任意换行符(CRLF、CR、LF、行分隔符 U+2028、段落分隔符 U+2029)都会被ASTUtils.LINEBREAK_MATCHER拆分为独立注释行——测试 tests/index.test.ts 对五种换行符分别断言了多行注释输出。
八、AST 优化:联合展开与去重
src/optimizeAST.ts 在生成后对中间 AST 做一轮优化,主要是针对联合类型:
- 递归优化所有子节点;
unwrapUnions展平嵌套联合,并把外层联合的注释行前置到第一个元素上,避免注释丢失;- 按
JSON.stringify去重联合成员(注释注明这是"hacky way to deduplicate union members")。
九、打印输出:把 AST 渲染为类型字符串
src/printAST.ts 定义了完整的打印器:
printTypeAlias输出/** 注释 */\ntype 别名 = 类型;- 数组:
T[],若元素是联合则加括号(T | U)[](printAndMaybeParenthesize); - 对象:
{\nprop: T;\n[k: string]: T},属性间用分号连接并强制换行(注释说明这样能让 Prettier 稳定地按多行输出); - 元组:
[T0,T1,...T[]],元素逐个打印(含各自注释),展开元素打印为...T[]; - 联合:每个成员前加
/** 注释 */ |,成员排序后按\n连接,整行以空格开头(因此快照中出现| 'a'的样式)。
十、错误边界:两种明确的异常
src/errors.ts 定义了两种错误,均携带完整的目标 schema JSON 以便排查:
NotSupportedError:遇到当前不支持的特性(如allOf、patternProperties、无类型且无$ref/enum的 schema、多类型数组、单类型数组配additionalItems、enum 中的null/数组);UnexpectedError:遇到"不应发生"的内部不一致(如缺失items、maxItems与 items 数量矛盾、$ref找不到对应定义,错误信息会列出所有已注册的 ref 路径)。
十一、在仓库中的真实应用:schema-snapshots 工作流
eslint-plugin 的 schemas.test.ts 展示了完整的落地用法:
- 遍历
../src/rules/index.js导出的全部规则; - 将
ruleDef.meta.schema用 Prettier 格式化后写入快照的# SCHEMA:段(对 enum 数组与对象属性做排序以保证跨平台稳定); - 调用
schemaToTypes(ruleDef.meta.schema),同样用 Prettier 格式化后写入# TYPES:段; - 用
toMatchFileSnapshot与 schema-snapshots/ 下的.shot文件比对,从而在 CI 中保证"schema 变更必须同步更新类型快照"。
以no-unused-vars.shot(packages/eslint-plugin/tests/schema-snapshots/no-unused-vars.shot)为例,其生成的类型(节选):
type Options = [ | 'local' | { /** Whether to check all, some, or no arguments. */ args?: | 'all' | 'none' | 'after-used'; argsIgnorePattern?: string; /** Whether to check catch block arguments. */ caughtErrors?: | 'none' | 'all'; caughtErrorsIgnorePattern?: string; // ... 其余属性 } | 'all', ];可以看到:oneOf成员被展开为联合、enum 成员被排序、description被转成内联 JSDoc、非必填属性带?。这些快照同时是规则文档展示的素材来源,因此该包保证了"文档里看到的类型与规则实际校验的 schema 永远一致"。
十二、小结:何时使用与边界提醒
- 使用场景:当你需要把 ESLint 规则的 options schema 转换为可读、可文档化的 TypeScript 类型时,
schemaToTypes是开箱即用的工具;它也是 typescript-eslint 内部保证规则 schema 与类型文档一致性的基础设施。 - 适用前提:输入必须是 JSON Schema draft v4(
JSONSchema4),且避免使用本工具明确不支持的allOf、dependencies、patternProperties等关键字。 - 已知取舍:
anyOf按联合而非严格组合语义生成;maxItems > 20时不再元组化;单类型数组忽略minItems/maxItems;$defs/definitions仅支持顶层声明——这些都是为了文档可读性与实现简洁性做出的有意设计,使用前应了解这些边界。
- 开发工具
- 静态分析
- Lint
- 代码质量
【免费下载链接】typescript-eslint
:sparkles: Monorepo for all the tooling which enables ESLint to support TypeScript
相关推荐
ESET-KeyGen与GitHub Actions集成:自动化生成ESET密钥的高效方法
ESET KeyGen与GitHub Actions集成:自动化生成ESET密钥的高效方法 ESET KeyGen是一款功能强大的ESET杀毒软件试用密钥与账号
CLIArkType代码生成:如何从JSON Schema自动生成TypeScript类型定义
ArkType代码生成:如何从JSON Schema自动生成TypeScript类型定义 ArkType是一个强大的TypeScript运行时验证库,它能够从J
后端OpenAPI TypeScript自定义类型映射终极指南:从JSON Schema到TypeScript类型
OpenAPI TypeScript自定义类型映射终极指南:从JSON Schema到TypeScript类型 openapi typescript是一个强大的
开发工具代码生成后端
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考