关于 enforcing-typescript-standards
Enforcing TypeScript Standards 是面向处理 TypeScript 代码的 Agent 技能,提供类型、导入、类成员、安全、测试和错误处理规则,并指导检查与改进代码变更。
主要功能
- 01采用显式类型并复用类型
- 02整理导入和类成员顺序
- 03遵循更安全的 TypeScript 模式
- 04改进错误处理和测试
- 05指导代码审查与重构
- 06验证构建和 lint
适用场景
- 01创建或修改 .ts 和 .tsx 文件
- 02检查 TypeScript 变更是否合规
- 03按项目标准重构代码
- 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 之前,按以下顺序尝试这些方法:
- 在源头使用恰当的类型
- 类型守卫(
typeof、instanceof) - 通过控制流进行类型收窄
- 自定义类型谓词函数
- 可辨识联合
// 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
}
导入组织
- 导入顺序:内置模块 → 外部模块 → 内部模块 → 父级模块 → 同级模块 → 索引(各组内按字母顺序排列)
- 不重复导入:合并来自同一模块的导入
- 导入后换行:导入代码块后必须留一个空行
类成员排序
- 签名(调用/构造)
- 字段:private → public → protected
- 构造函数:public → protected → private
- 方法: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派生类型
验证工作流
- 分析:根据这些 TypeScript 标准检查代码更改
- 生成/重构:编写或修改代码,使其符合以上所有规则
- 简化:检查是否有简化空间——优先选择清晰、直接的代码,而非巧妙的方案
- 检查命名:确认变量和函数名在更改后仍符合上下文
- 构建:确认类型检查通过且没有错误(例如
pnpm run build或pnpm exec tsc --noEmit) - 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