mcpField
B
ban-type-assertions

ban-type-assertions

github.com/factory-ai开发117144 次安装3 天前更新
代码仓库

关于 ban-type-assertions

用于在 TypeScript 项目中禁止类型断言的开发技能,通过配置 ESLint 规则并用编译器可验证的方式替换 `as` 转换。它指导开发者优先在外部数据边界使用 Zod 校验,并检查相关类型、测试和包。

主要功能

  1. 01启用禁止类型断言的 ESLint 规则
  2. 02按数据边界使用 Zod 解析
  3. 03通过控制流缩窄联合类型
  4. 04检查并整合共享 Schema
  5. 05运行 lint、类型检查和测试

适用场景

  1. 01清理项目中的 `as` 类型转换
  2. 02校验来自 JSON、磁盘或网络的数据
  3. 03修复类型断言规则触发的测试问题

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/factory-ai/factory-plugins --skill ban-type-assertions

安装后在 Claude Code 中输入/ban-type-assertions确认已加载。

详细介绍

禁止类型断言

在一个包中启用 @typescript-eslint/consistent-type-assertions,并设置 assertionStyle: 'never',然后用编译器可验证的模式替换所有 as X 类型转换。

核心理念

选择严格正确的做法,而不是更简单的做法。

每个 as 断言都是开发者在告诉编译器“相信我”的地方。目标是让编译器改为验证。如果你用同样未经验证的类型守卫替换 as Foo,那并没有带来任何改进——你只是把断言挪了个位置。

快速参考

  • 规则:@typescript-eslint/consistent-type-assertions
  • 配置:{ assertionStyle: 'never' }
  • 位置:packages/<name>/.eslintrc.js

工作流

1. 启用规则

将以下内容添加到包的 .eslintrc.js 中:

rules: {
  '@typescript-eslint/consistent-type-assertions': ['error', { assertionStyle: 'never' }],
}
2. 列出违规项
cd packages/<name> && npm run lint 2>&1 | grep "consistent-type-assertions"

修复前,先按文件和模式对违规项分组。

3. 修复前先调研

编写任何替代代码之前:

  1. 检查是否已有 zod schema——在 @factory/common 和整个代码库中,搜索与类型名称一同出现的 Schema。
  2. 检查是否存在尚未导出的 schema——如果存在,导出它们,而不是新建 schema。
  3. 检查各包之间是否有重复的类型/接口——如果有,将其合并到 @factory/common。
  4. 了解数据流——这是解析边界(外部数据)、类型收窄位置(联合类型),还是库类型缺陷?
4. 按模式层级修复违规项
第 1 层:Zod 解析(用于外部数据边界)

用于所有从 JSON、磁盘、网络、IPC 等进入系统的数据。这提供的是运行时验证,而不只是类型注解。

// BAD
const data = JSON.parse(raw) as MyType;

// GOOD
const data = MySchema.parse(JSON.parse(raw));

当需要妥善处理错误时(例如返回包含请求 ID 等上下文的错误响应),使用 safeParse:

// BAD: throws before you can extract the request id
const request = RequestSchema.parse(JSON.parse(raw));

// GOOD: safeParse lets you return a proper error
const parsed = RequestSchema.safeParse(JSON.parse(raw));
if (!parsed.success) {
  return errorResponse(rawObj?.id ?? null, INVALID_PARAMS, parsed.error.message);
}
const request = parsed.data;
第 2 层:控制流收窄(用于联合类型)

使用 switch、in、instanceof 或可辨识联合:

// BAD
(error as NodeJS.ErrnoException).code

// GOOD
if (error instanceof Error && 'code' in error) {
  const code = error.code;
}
// BAD
if (METHODS.has(method as Method)) { ... }

// GOOD: switch narrows exhaustively
switch (method) {
  case 'foo':
  case 'bar':
    return handle(method); // narrowed
}
第 3 层:使用附带理由的 eslint-disable(最后手段)

仅用于确实无法避免的情况(库类型缺陷、无法推断的泛型参数)。务必说明原因:

// eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- ws library types require generic parameter
ws.on('message', handler);
反模式:伪装成类型断言的类型守卫
// NOT an improvement -- checks shape but not content
function isDaemonRequest(x: unknown): x is DaemonRequest {
  return typeof x === 'object' && x !== null && 'method' in x;
}

zod schema 会验证值。像这样的类型守卫只是换了一种更繁琐的形式来进行未经验证的断言。只有在收窄逻辑确实足够时才使用类型守卫。

5. 使用严格 schema,而不是宽松 schema

如果存在 schema(例如 SessionSettingsSchema),应严格使用它,而不是使用 z.record(z.unknown())。这样可以确保向前兼容——如果迁移中删除了字段,读取时就会清理过时数据。

// BAD: accepts anything
const settings = z.record(z.unknown()).parse(raw);

// GOOD: validates against the real shape
const settings = SessionSettingsSchema.parse(raw);
6. 将共享 schema 提升到 @factory/common

如果发现各包之间存在重复的接口、类型或 schema,请将它们合并:

  1. 在 @factory/common/<domain>/<subdomain>/schema.ts 中创建 schema
  2. 将所有枚举放在同级的 enums.ts 中(factory/enum-file-organization 要求)
  3. 通过子路径导出(例如 @factory/common/session/summary),不要通过汇总入口 index.ts 导出
  4. 删除所有本地重复定义
  5. 更新所有使用方,从 common 子路径导入
  6. 在仓库根目录运行 npm run knip,检查未使用的汇总入口再导出
7. 修复测试 mock,使其符合 schema

用 .parse() 替换 as X 后,依赖该断言的测试 mock 会验证失败。请修复 mock,不要在测试中禁用规则。

创建辅助函数,集中管理有效的测试样例:

function mockSessionSummary(
  overrides?: Partial<SessionSummaryEvent>,
): SessionSummaryEvent {
  return {
    type: 'session_start',
    id: 'test-id',
    title: 'Test Session',
    owner: 'test-owner',
    ...overrides,
  };
}
8. 在错误处理内部的边界处解析

确保解析发生在能生成恰当错误响应的位置,而不是导致异常未处理:

// BAD: parse outside try/catch -- if it throws, you lose context
const request = RequestSchema.parse(data);
try { handle(request); } catch { ... }

// GOOD: safeParse before try, handle error with context
const parsed = RequestSchema.safeParse(data);
if (!parsed.success) {
  return errorResponse(rawData?.id ?? null, INVALID_PARAMS, parsed.error.message);
}
try { handle(parsed.data); } catch { ... }

验证

对所有受影响的包运行以下命令(对 @factory/common 的更改可能会导致下游 lint 失败):

# Lint (all affected packages)
cd packages/<name> && npm run lint

# Typecheck
npm run typecheck

# Tests
npm run test

# Unused exports (repo root)
npm run knip

注意事项

  • factory/enum-file-organization 要求 TypeScript 枚举必须放在名为 enums.ts 的文件中
  • no-barrel-files 禁止通过 barrel 文件重新导出类型——使用者必须直接从子路径导入
  • 将类型提升到 common 时,如果新子路径尚无 package.json 导出项,请添加一项
  • 如果测试文件在 mock 设置中使用断言语法,可能需要在 .eslintrc.js 中为该规则添加测试覆盖配置;但应优先修复 mock,而不是禁用该规则

评论

支持 Markdown