mcpField
E
enforcing-typescript-standards

enforcing-typescript-standards

github.com/jgeurts开发1213 次安装1 天前更新
代码仓库

关于 enforcing-typescript-standards

Enforcing TypeScript Standards 是面向处理 TypeScript 代码的 Agent 技能,提供类型、导入、类成员、安全、测试和错误处理规则,并指导检查与改进代码变更。

主要功能

  1. 01采用显式类型并复用类型
  2. 02整理导入和类成员顺序
  3. 03遵循更安全的 TypeScript 模式
  4. 04改进错误处理和测试
  5. 05指导代码审查与重构
  6. 06验证构建和 lint

适用场景

  1. 01创建或修改 .ts 和 .tsx 文件
  2. 02检查 TypeScript 变更是否合规
  3. 03按项目标准重构代码
  4. 04检查类型编译和 lint 结果

安装

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

$npx skills add https://github.com/jgeurts/oxlint-config-decent --skill enforcing-typescript-standards

安装后在 Claude Code 中输入/enforcing-typescript-standards确认已加载。

详细介绍

强制执行 TypeScript 标准

强制执行项目的核心 TypeScript 标准,包括显式类型、导入组织、类成员排序和代码安全规则。

触发条件

当用户说出或暗示以下任一内容时,启用此技能:

  • “编写”“创建”“实现”“添加”“构建”(新 TypeScript 代码)
  • “修复”“更新”“更改”“修改”“重构”(现有 TypeScript 代码)
  • “审查”“检查”“改进”“清理”(代码质量)
  • 任何涉及 .ts 或 .tsx 文件的请求

具体触发条件:

  • 创建新的 .ts 或 .tsx 文件
  • 修改现有 TypeScript 代码
  • 审查 TypeScript 代码是否符合规范

核心标准

类型安全
  • 显式返回类型:在实际可行时优先使用显式返回类型;如果类型推断显而易见且不会增加清晰度,则可省略
  • 显式成员可见性:类成员必须标注 public、private 或 protected
  • 仅类型导入:类型使用 import type:import type { Foo } from './foo.js'
  • 类型组成项排序:联合类型/交叉类型必须按字母顺序排列
  • 只抛出 Error 对象:切勿抛出字符串或其他原始值
  • 避免使用 any 和类型断言:优先使用适当的类型,而不是 any 或 as 类型转换;仅在确有必要时使用
  • 显式声明 JSON 字段的类型:JSON 数据使用 Record<string, unknown> 或特定接口,绝不使用 any
  • 使用 Number() 进行转换:优先使用 Number(value),而不是 parseInt(value, 10) 或 parseFloat(value)
  • 复用现有类型:定义新接口之前,先搜索可直接复用、扩展或通过 Pick、Omit、Partial 等工具类型派生的现有类型
类型断言的替代方案

使用 as 之前,按以下顺序尝试这些方法:

  1. 在源头使用恰当的类型
  2. 类型守卫(typeof、instanceof)
  3. 通过控制流进行类型收窄
  4. 自定义类型谓词函数
  5. 可辨识联合
// Bad
const user = data as User;

// Good
function isUser(data: unknown): data is User {
  return typeof data === 'object' && data !== null && 'id' in data;
}
if (isUser(data)) {
  // data is now typed as User
}
导入组织
  • 导入顺序:内置模块 → 外部模块 → 内部模块 → 父级模块 → 同级模块 → 索引(各组内按字母顺序排列)
  • 不重复导入:合并来自同一模块的导入
  • 导入后换行:导入代码块后必须留一个空行
类成员排序
  1. 签名(调用/构造)
  2. 字段:private → public → protected
  3. 构造函数:public → protected → private
  4. 方法:public → protected → private
代码风格
  • 简单胜过巧妙:直观易读的代码优于巧妙的一行代码
  • 提前返回:使用守卫子句减少嵌套;对边界情况提前返回
  • 空值合并:默认值优先使用 ?? 而非 ||(避免对 0 或 '' 产生误判)
  • 可选链:使用 ?. 安全访问属性
  • 匹配现有模式:遵循代码库中已有的约定
  • 有意义的标识符:名称必须具有描述性(例外:_、i、j、k、e、x、y)
  • 函数声明:使用 function foo(),而不是 const foo = function()
  • 优先使用 const:除非需要重新赋值,否则使用 const
  • 禁止使用 var:始终使用 const 或 let
  • 对象简写:使用 { foo },而不是 { foo: foo }
  • 模板字面量:使用 `Hello ${name}`,而不是 'Hello ' + name
  • 严格相等:除 null 比较外,使用 ===
  • 每个文件一个类:每个文件最多定义一个类
  • 避免使用 reduce:为清晰起见,优先使用 for...of 循环或其他数组方法
  • 函数优于类:优先使用导出的函数,而非带静态方法的类(除非需要状态)
  • 禁止嵌套函数:在模块级别定义辅助函数,而非放在其他函数内部
  • 不可变性:创建新对象/数组,而不是修改现有对象/数组
命名约定
  • 枚举成员:使用 PascalCase(例如 MyValue)
  • 禁止尾随下划线:标识符不能以 _ 结尾
注释
  • 避免冗余注释:代码已清楚表达的内容,不要再添加注释
  • 避免重复注释:不要重复函数名、类型或附近注释中的信息
  • 只写有意义的注释:只解释 为什么,不要解释 做什么——代码会说明它做什么
布尔表达式
  • 优先使用真值判断:使用隐式真值/假值判断,而不是显式比较
  • 例外:当区分语义上有效的 0/'' 与 null/undefined 很重要时,使用显式判断
测试
  • 尽量少用模拟:避免什么都模拟;有可用的真实实现和数据生成器时,应优先使用
  • 测试真实行为:测试模拟对象价值不大——应测试实际代码路径
  • 不要偷懒:编写全面的测试,覆盖边界情况,而不只是正常流程
错误处理
  • 使用具体错误类型:有意义时,优先使用具体错误类型,而非通用的 Error
  • 避免静默失败:不要使用空的 catch 块吞掉错误
  • 处理拒绝:始终处理 Promise 拒绝
  • 让错误继续传播:不要为了重新抛出或记录错误而捕获错误——让错误传递给错误处理程序

负面知识

避免以下反模式:

  • 在生产代码中使用 console.log() 语句
  • 使用 eval() 或 Function() 构造函数
  • 嵌套三元运算符
  • 在循环中使用 await,而 Promise.all 更简单时(顺序很重要或并行处理会增加复杂度时,可以按顺序等待)
  • 空接口
  • 变量遮蔽
  • 在循环内定义函数
  • 不带说明地使用 @ts-ignore(使用带有 10 个以上字符说明的 @ts-expect-error)
  • 重复描述代码的注释:在 counter++ 上方写 // increment counter
  • 重复类型信息的注释:返回类型为 : string 时写 // returns a string
  • 注释掉的代码(删除它;使用版本控制)
  • 冗长的布尔比较:arr.length > 0、str !== ''、obj !== null && obj !== undefined
  • 通过注释禁用 lint 规则(应修复代码)
  • 过度使用 any 类型或 as 类型断言
  • 在测试中过度使用模拟,而不使用真实实现或数据生成器
  • 静默吞掉错误的空 catch 块
  • 在适合使用 ?? 时用 || 设置默认值
  • 可以通过提前返回简化时,避免深层嵌套
  • 仅为重新抛出或记录错误而捕获错误
  • 在其他函数内部定义嵌套函数
  • 修改对象/数组,而不是创建新对象/数组
  • TOCTOU:在操作文件/资源前先检查其是否存在(应直接尝试操作并处理错误)
  • 仅包含静态方法的类(应使用普通函数)
  • 不要重复定义已有接口;应复用接口,或通过 Pick/Omit/Partial 派生类型

验证工作流

  1. 分析:根据这些 TypeScript 标准检查代码更改
  2. 生成/重构:编写或修改代码,使其符合以上所有规则
  3. 简化:检查是否有简化空间——优先选择清晰、直接的代码,而非巧妙的方案
  4. 检查命名:确认变量和函数名在更改后仍符合上下文
  5. 构建:确认类型检查通过且没有错误(例如 pnpm run build 或 pnpm exec tsc --noEmit)
  6. Lint:完成任务前运行 pnpm run lint,确认符合规范

示例

注释示例
// Standard
// Retry with exponential backoff to handle transient network failures
async function fetchWithRetry(url: string, attempts = 3): Promise<Response> {
  for (let i = 0; i < attempts; i++) {
    try {
      return await fetch(url);
    } catch {
      await sleep(2 ** i * 100);
    }
  }
  throw new Error(`Failed after ${attempts} attempts`);
}

// Non-Standard
/**
 * Fetches data from a URL with retry logic
 * @param url - The URL to fetch from
 * @param attempts - Number of attempts (default 3)
 * @returns A Promise that resolves to a Response
 */
async function fetchWithRetry(url: string, attempts = 3): Promise<Response> {
  // Loop through attempts
  for (let i = 0; i < attempts; i++) {
    try {
      // Try to fetch the URL
      return await fetch(url);
    } catch {
      // Wait before retrying
      await sleep(2 ** i * 100);
    }
  }
  // Throw error if all attempts fail
  throw new Error(`Failed after ${attempts} attempts`);
}
布尔表达式示例
// Standard
if (myArray.length) {
}
if (myString) {
}
if (myObject) {
}
if (!value) {
}

// Non-Standard
if (myArray.length !== 0) {
}
if (myArray.length > 0) {
}
if (myString !== '') {
}
if (myObject !== null && myObject !== undefined) {
}
if (value === null || value === undefined) {
}
提前返回示例
// Standard
function processUser(user: User | null): Result {
  if (!user) {
    return { error: 'No user provided' };
  }
  if (!user.isActive) {
    return { error: 'User is inactive' };
  }
  return { data: transform(user) };
}

// Non-Standard
function processUser(user: User | null): Result {
  if (user) {
    if (user.isActive) {
      return { data: transform(user) };
    } else {
      return { error: 'User is inactive' };
    }
  } else {
    return { error: 'No user provided' };
  }
}
使用函数而非类的示例
// Standard
export function calculateTotal(items: Item[]): number {
  return items.reduce((sum, item) => sum + item.price, 0);
}

export function formatCurrency(amount: number): string {
  return `$${amount.toFixed(2)}`;
}

// Non-Standard
export class Calculator {
  static calculateTotal(items: Item[]): number {
    return items.reduce((sum, item) => sum + item.price, 0);
  }

  static formatCurrency(amount: number): string {
    return `$${amount.toFixed(2)}`;
  }
}
不使用嵌套函数的示例
// Standard
function transformItem(item: Item): TransformedItem {
  return { id: item.id, name: item.name.toUpperCase() };
}

async function processItems(items: Item[]): Promise<TransformedItem[]> {
  return items.map(transformItem);
}

// Non-Standard
async function processItems(items: Item[]): Promise<TransformedItem[]> {
  function transformItem(item: Item): TransformedItem {
    return { id: item.id, name: item.name.toUpperCase() };
  }
  return items.map(transformItem);
}
不可变性示例
// Standard
function addItem(items: Item[], newItem: Item): Item[] {
  return [...items, newItem];
}

function removeItem(items: Item[], id: string): Item[] {
  return items.filter((item) => item.id !== id);
}

function updateItem(items: Item[], id: string, updates: Partial<Item>): Item[] {
  return items.map((item) => (item.id === id ? { ...item, ...updates } : item));
}

// Non-Standard
function addItem(items: Item[], newItem: Item): Item[] {
  items.push(newItem);
  return items;
}

function removeItem(items: Item[], id: string): Item[] {
  const index = items.findIndex((item) => item.id === id);
  items.splice(index, 1);
  return items;
}
错误传播示例
// Standard
async function getUser(id: string): Promise<User> {
  return userService.findById(id);
}

// Non-Standard
async function getUser(id: string): Promise<User> {
  try {
    return await userService.findById(id);
  } catch (error) {
    console.error(error);
    throw error;
  }
}
TOCTOU 示例
// Standard
async function readConfig(path: string): Promise<Config> {
  try {
    const content = await readFile(path, 'utf-8');
    return JSON.parse(content);
  } catch (error) {
    if (isNotFoundError(error)) {
      return defaultConfig;
    }
    throw error;
  }
}

// Non-Standard
async function readConfig(path: string): Promise<Config> {
  if (await fileExists(path)) {
    const content = await readFile(path, 'utf-8');
    return JSON.parse(content);
  }
  return defaultConfig;
}
类型复用示例
// Given an existing type
interface User {
  id: string;
  email: string;
  name: string;
  passwordHash: string;
  createdAt: Date;
  updatedAt: Date;
}

// Standard - derive from existing type
type PublicUser = Omit<User, 'passwordHash'>;
type UserSummary = Pick<User, 'id' | 'name'>;
type UserUpdate = Partial<Pick<User, 'email' | 'name'>>;

// Non-Standard - duplicating fields that already exist
interface PublicUser {
  id: string;
  email: string;
  name: string;
  createdAt: Date;
  updatedAt: Date;
}

interface UserSummary {
  id: string;
  name: string;
}

interface UserUpdate {
  email?: string;
  name?: string;
}

评论

支持 Markdown