Loading...
Loading...
Enforces the project's core TypeScript standards including explicit typing, import organization, class member ordering, and code safety rules. ALWAYS apply when creating, modifying, or reviewing any TypeScript (.ts/.tsx) file.
npx skill4agent add jgeurts/eslint-config-decent enforcing-typescript-standards.ts.tsx.ts.tsxpublicprivateprotectedimport typeimport type { Foo } from './foo.js'anyanyasRecord<string, unknown>anyNumber(value)parseInt(value, 10)parseFloat(value)PickOmitPartialastypeofinstanceof// 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
}??||0''?._ijkexyfunction foo()const foo = function()constconstlet{ foo }{ foo: foo }`Hello ${name}`'Hello ' + name===reducefor...ofPascalCaseMyValue_0''nullundefinedErrorconsole.log()eval()Function()awaitPromise.all@ts-ignore@ts-expect-error// increment countercounter++// returns a string: stringarr.length > 0str !== ''obj !== null && obj !== undefinedanyas||??PickOmitPartialnpm run buildnpx tsc --noEmitnpm 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;
}
}// 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;
}