enforcing-typescript-standards by jgeurts/eslint-config-decent
npx skills add https://github.com/jgeurts/eslint-config-decent --skill enforcing-typescript-standards强制执行项目的核心 TypeScript 标准,包括显式类型标注、导入组织、类成员排序和代码安全规则。
当用户说出或暗示以下任何内容时,激活此技能:
.ts 或 .tsx 文件的请求具体触发条件:
.ts 或 .tsx 文件public、private 或 广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
protectedimport type:import type { Foo } from './foo.js'any 和类型断言:优先使用正确的类型而非 any 或 as 强制转换;仅在真正必要时使用它们Record<string, unknown> 或特定接口,切勿使用 anyNumber(value) 而非 parseInt(value, 10) 或 parseFloat(value)Pick、Omit、Partial 或其他实用类型派生的现有类型在使用 as 之前,按顺序尝试以下方法:
typeof、instanceof)// 错误示例
const user = data as User;
// 正确示例
function isUser(data: unknown): data is User {
return typeof data === 'object' && data !== null && 'id' in data;
}
if (isUser(data)) {
// data 现在被类型化为 User
}
?? 而非 ||(避免对 0 或 '' 的误判)?. 进行安全的属性访问_、i、j、k、e、x、y)function foo() 而非 const foo = function()constconst 或 let{ foo } 而非 { foo: foo }Hello ${name} 而非 'Hello ' + name===reduce:为清晰起见,优先使用 for...of 循环或其他数组方法PascalCase(例如,MyValue)_ 结尾0/''(有效值)与 null/undefined 在语义上很重要时,使用显式检查Error避免这些反模式:
console.log() 语句eval() 或 Function() 构造函数Promise.all 会更简单时,在循环内使用 await(当顺序重要或并行性增加复杂性时,顺序等待是可以的)@ts-ignore(使用带有 10 个以上字符描述的 @ts-expect-error)counter++ 上方的 // 递增计数器: string 时的 // 返回一个字符串arr.length > 0、str !== ''、obj !== null && obj !== undefinedany 类型或 as 类型断言?? 更合适时,使用 || 作为默认值Pick/Omit/Partial 重用或派生npm run build 或 npx tsc --noEmit)npm run lint 以确认符合性// 标准
// 使用指数退避重试以处理瞬态网络故障
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`);
}
// 非标准
/**
* 使用重试逻辑从 URL 获取数据
* @param url - 要从中获取的 URL
* @param attempts - 尝试次数(默认 3)
* @returns 解析为 Response 的 Promise
*/
async function fetchWithRetry(url: string, attempts = 3): Promise<Response> {
// 循环尝试
for (let i = 0; i < attempts; i++) {
try {
// 尝试获取 URL
return await fetch(url);
} catch {
// 重试前等待
await sleep(2 ** i * 100);
}
}
// 如果所有尝试都失败,抛出错误
throw new Error(`Failed after ${attempts} attempts`);
}
// 标准
if (myArray.length) {
}
if (myString) {
}
if (myObject) {
}
if (!value) {
}
// 非标准
if (myArray.length !== 0) {
}
if (myArray.length > 0) {
}
if (myString !== '') {
}
if (myObject !== null && myObject !== undefined) {
}
if (value === null || value === undefined) {
}
// 标准
function processUser(user: User | null): Result {
if (!user) {
return { error: '未提供用户' };
}
if (!user.isActive) {
return { error: '用户未激活' };
}
return { data: transform(user) };
}
// 非标准
function processUser(user: User | null): Result {
if (user) {
if (user.isActive) {
return { data: transform(user) };
} else {
return { error: '用户未激活' };
}
} else {
return { error: '未提供用户' };
}
}
// 标准
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)}`;
}
// 非标准
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)}`;
}
}
// 标准
function transformItem(item: Item): TransformedItem {
return { id: item.id, name: item.name.toUpperCase() };
}
async function processItems(items: Item[]): Promise<TransformedItem[]> {
return items.map(transformItem);
}
// 非标准
async function processItems(items: Item[]): Promise<TransformedItem[]> {
function transformItem(item: Item): TransformedItem {
return { id: item.id, name: item.name.toUpperCase() };
}
return items.map(transformItem);
}
// 标准
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));
}
// 非标准
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;
}
// 标准
async function getUser(id: string): Promise<User> {
return userService.findById(id);
}
// 非标准
async function getUser(id: string): Promise<User> {
try {
return await userService.findById(id);
} catch (error) {
console.error(error);
throw error;
}
}
// 标准
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;
}
}
// 非标准
async function readConfig(path: string): Promise<Config> {
if (await fileExists(path)) {
const content = await readFile(path, 'utf-8');
return JSON.parse(content);
}
return defaultConfig;
}
// 给定一个现有类型
interface User {
id: string;
email: string;
name: string;
passwordHash: string;
createdAt: Date;
updatedAt: Date;
}
// 标准 - 从现有类型派生
type PublicUser = Omit<User, 'passwordHash'>;
type UserSummary = Pick<User, 'id' | 'name'>;
type UserUpdate = Partial<Pick<User, 'email' | 'name'>>;
// 非标准 - 复制已存在的字段
interface PublicUser {
id: string;
email: string;
name: string;
createdAt: Date;
updatedAt: Date;
}
interface UserSummary {
id: string;
name: string;
}
interface UserUpdate {
email?: string;
name?: string;
}
每周安装数
117
仓库
GitHub 星标数
1
首次出现时间
2026 年 1 月 22 日
安全审计
安装于
claude-code99
github-copilot97
opencode95
codex93
gemini-cli92
cursor83
Enforces the project's core TypeScript standards including explicit typing, import organization, class member ordering, and code safety rules.
Activate this skill when the user says or implies any of these:
.ts or .tsx filesSpecific triggers:
.ts or .tsx filepublic, private, or protectedimport type for types: import type { Foo } from './foo.js'any and type assertions: Prefer proper typing over any or as casts; use them only when truly necessaryRecord<string, unknown> or specific interfaces for JSON data, never anyNumber(value) over parseInt(value, 10) or parseFloat(value)Pick, Omit, Partial, or other utility typesBefore using as, try these approaches in order:
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
}
?? over || for defaults (avoids false positives on 0 or '')?. for safe property access_, i, j, k, , , )PascalCase (e.g., MyValue)_0/'' (valid values) from null/undefined is semantically importantError when meaningfulAvoid these anti-patterns:
console.log() statements in production codeeval() or Function() constructorawait inside loops when Promise.all would be simpler (sequential awaits are fine when order matters or parallelism adds complexity)@ts-ignore without explanation (use @ts-expect-error with 10+ char description)// increment counter above counter++npm run build or npx tsc --noEmit)npm run lint to confirm compliance before completing the task// 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;
}
}
// 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;
}
Weekly Installs
117
Repository
GitHub Stars
1
First Seen
Jan 22, 2026
Security Audits
Gen Agent Trust HubPassSocketPassSnykPass
Installed on
claude-code99
github-copilot97
opencode95
codex93
gemini-cli92
cursor83
Node.js 环境配置指南:多环境管理、类型安全与最佳实践
10,500 周安装
exyfunction foo() not const foo = function()const unless reassignment is neededconst or let{ foo } not { foo: foo }Hello ${name} not 'Hello ' + name=== except for null comparisonsreduce: Prefer for...of loops or other array methods for clarity// returns a string when return type is : stringarr.length > 0, str !== '', obj !== null && obj !== undefinedany type or as type assertions|| for defaults when ?? is more appropriatePick/Omit/Partial