Activation: This skill activates whenever the user says or implies TypeScript. It responds with standards-compliant TypeScript code and can explain any rule on demand.
"strict": true in tsconfig.json. This turns on noImplicitAny,
strictNullChecks, strictFunctionTypes, and more.any: Treat every use of any as tech debt. Prefer unknown when the type is
truly not known, or use generics.const over let; prefer readonly properties and
ReadonlyArray<T> / Readonly<T> utility types.ConstructConventionExampleVariable / FunctioncamelCase``getUserName, isActiveBoolean variablecamelCase with prefixisLoading, hasAccess, canEditConstant (module)UPPER_SNAKE_CASE``MAX_RETRY_COUNT, API_BASE_URLConstant (local)camelCase``const defaultTimeout = 3000ClassPascalCase``UserService, HttpClientInterfacePascalCase``UserProfile, ApiResponseType aliasPascalCase``UserId, ThemeEnumPascalCase``Direction, HttpStatusEnum memberPascalCase``Direction.Up, HttpStatus.OkGeneric parameterSingle uppercase letter or descriptive PascalCase``T, TKey, TValueFile namekebab-case.ts``user-service.ts, api-client.tsTest file namekebab-case.test.ts or kebab-case.spec.ts``user-service.test.tsReact component filePascalCase.tsx``UserProfile.tsxPrivate fieldcamelCase (no _ prefix)private count: number
I (e.g., IUser → User).Type or Interface.i, j) or generic type parameters ( T, K, V).IO, ID); 3+ characters use PascalCase
( Http, Xml, Api).interface for Object Shapes#typescript
// ✅ Good — use interface for object shapes
interface User {
readonly id: string;
name: string;
email: string;
}
// ✅ Good — use type for unions, intersections, mapped types
type Status = 'active' | 'inactive' | 'suspended';
type Result<T> = Success<T> | Failure;
type vs interface#Use interface when …Use type when …Defining the shape of an object or classCreating union or intersection typesYou want declaration mergingUsing mapped / conditional typesExtending other interfacesAliasing primitive or tuple types
Always annotate function return types explicitly for public APIs.
Let TypeScript infer types for local variables when the type is obvious.
Prefer type over interface for function signatures:
typescript
type Comparator<T> = (a: T, b: T) => number;
Avoid empty interfaces. If extending, include at least a comment explaining intent.
Use Record<K, V> instead of { [key: string]: V }.
Use utility types ( Partial<T>, Pick<T, K>, Omit<T, K>, Required<T>) to derive types
rather than duplicating.
typescript
// ✅ Good — string enum for readability and debuggability
enum Direction {
Up = 'UP',
Down = 'DOWN',
Left = 'LEFT',
Right = 'RIGHT',
}
const Enum or Union Types#typescript
// ✅ Good — const enum for zero-runtime-cost enums (no reverse mapping needed)
const enum HttpMethod {
Get = 'GET',
Post = 'POST',
Put = 'PUT',
Delete = 'DELETE',
}
// ✅ Also good — string literal union (simpler, tree-shakeable)
type HttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE';
typescript
// ✅ Good
const maxRetries = 3;
const baseUrl = 'https://api.example.com';
let currentAttempt = 0;
// ❌ Bad — using var
var legacyValue = 'old';
// ❌ Bad — using let when value never changes
let neverReassigned = 42;
Always use const unless the variable needs reassignment; then use let.
Never use var.
Declare variables as close to their first usage as possible.
One variable declaration per statement.
Prefer destructuring for extracting properties: typescript
// ✅ Good
const { name, age } = user;
// ❌ Bad
const name = user.name;
const age = user.age;
Use as const for immutable literal objects / arrays:
typescript
const ROUTES = {
home: '/',
about: '/about',
contact: '/contact',
} as const;
typescript
// ✅ Good — explicit return type for public functions
function calculateTotal(items: CartItem[]): number {
return items.reduce((sum, item) => sum + item.price * item.quantity, 0);
}
// ✅ Good — arrow function for callbacks / short lambdas
const double = (n: number): number => n * 2;
// ✅ Good — use default parameters instead of optional + fallback
function createUser(name: string, role: Role = Role.Viewer): User {
// ...
}
Annotate return types for all exported / public functions.
Prefer arrow functions for callbacks and inline functions.
Prefer function declarations for top-level named functions (they are hoisted and more readable in stack traces).
Maximum parameters: 3. If more are needed, group them into an options object: typescript
// ✅ Good
interface CreateUserOptions {
name: string;
email: string;
role?: Role;
department?: string;
}
function createUser(options: CreateUserOptions): User { ... }
// ❌ Bad — too many positional parameters
function createUser(name: string, email: string, role: Role, dept: string): User { ... }
Do not use arguments; use rest parameters ( ...args) instead.
Avoid Function type. Use specific function signatures.
Keep functions small — ideally under 30 lines of logic.
Functions should do one thing.
Prefer early returns to reduce nesting: typescript
// ✅ Good
function getDiscount(user: User): number {
if (!user.isPremium) return 0;
if (user.yearsActive < 2) return 5;
return 10;
}
typescript
// ✅ Good
class UserService {
private readonly repository: UserRepository;
constructor(repository: UserRepository) {
this.repository = repository;
}
async findById(id: string): Promise<User | undefined> {
return this.repository.get(id);
}
}
Prefer composition over inheritance.
Use readonly for properties that should not change after construction.
Member ordering:
Use explicit access modifiers ( public, protected, private) on every member.
Do NOT prefix private members with _. TypeScript's private keyword is sufficient.
Use # (ES private fields) when true runtime privacy is required.
Prefer parameter properties for simple constructor-only assignments: typescript
class Logger {
constructor(private readonly prefix: string) {}
}
Avoid classes with only static methods — use plain functions in a module instead.
Implement interfaces explicitly: typescript
class UserRepositoryImpl implements UserRepository { ... }
typescript
// ✅ Good — named exports
export function parseConfig(raw: string): Config { ... }
export interface Config { ... }
// ✅ Good — re-export barrel file (index.ts)
export { parseConfig } from './parse-config';
export type { Config } from './parse-config';
Prefer named exports over default exports (better refactoring, auto-import support).
Use default exports only for React components if the project convention requires it.
Use import type / export type for type-only imports to help bundlers tree-shake:
typescript
import type { User } from './user';
Group and order imports:
node:fs, node:path)react, lodash)@/utils, @/components)../)./)Separate each group with a blank line.
Do NOT use wildcard imports ( import * as) unless re-exporting a module namespace.
Keep barrel files ( index.ts) thin — do not add logic.
typescript
// ✅ Good — descriptive when intent is ambiguous
function merge<TTarget, TSource>(target: TTarget, source: TSource): TTarget & TSource {
return { ...target, ...source };
}
// ✅ Good — single-letter when intent is obvious
function identity<T>(value: T): T {
return value;
}
// ✅ Good — constrained generics
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
T, U, K, V) for simple generics.T ( TKey, TValue, TResult) when there are
multiple type parameters or the purpose is not obvious.<T extends SomeType>).Array<T>, Promise<T>, Map<K, V>) over their shorthand where
readability benefits.typescript
// ✅ Good — custom error class
class AppError extends Error {
constructor(
message: string,
public readonly code: string,
public readonly statusCode: number = 500,
) {
super(message);
this.name = 'AppError';
}
}
// ✅ Good — typed error handling
function parseJson<T>(raw: string): T {
try {
return JSON.parse(raw) as T;
} catch (error) {
throw new AppError(
`Failed to parse JSON: ${error instanceof Error ? error.message : String(error)}`,
'PARSE_ERROR',
400,
);
}
}
Never swallow errors silently (empty catch blocks).
Use custom error classes that extend Error for domain-specific errors.
Type the catch variable as unknown (default in TS 4.4+) and narrow before use.
Prefer Result / Either patterns for expected failures in libraries: typescript
type Result<T, E = Error> = { ok: true; value: T } | { ok: false; error: E };
Always clean up resources in finally blocks or use disposable patterns.
Log errors with sufficient context (operation name, input summary, stack trace).
typescript
// ✅ Good — async/await
async function fetchUser(id: string): Promise<User> {
const response = await httpClient.get<User>(`/users/${id}`);
return response.data;
}
// ✅ Good — parallel execution
async function fetchDashboard(userId: string): Promise<Dashboard> {
const [user, posts, notifications] = await Promise.all([
fetchUser(userId),
fetchPosts(userId),
fetchNotifications(userId),
]);
return { user, posts, notifications };
}
async/await over .then() chains for readability.Promise<T>.Promise.all() for independent concurrent operations.Promise.allSettled() when failures should not abort sibling operations.new Promise() when an async function suffices (avoid the explicit-construction
antipattern).async void functions — they cannot be await-ed and swallow errors.
Exception: event handlers where the framework requires void.for...of with await for sequential async iteration, not forEach.typescript
/**
* Calculate the compound interest for a principal amount.
*
* @param principal - The initial amount of money.
* @param rate - The annual interest rate (decimal, e.g. 0.05 for 5%).
* @param times - Number of times interest is compounded per year.
* @param years - Number of years the money is invested.
* @returns The total amount after compound interest.
*
* @example
* ```typescript
* const total = compoundInterest(1000, 0.05, 12, 10);
* // total ≈ 1647.01
* ```
*/
function compoundInterest(
principal: number,
rate: number,
times: number,
years: number,
): number {
return principal * Math.pow(1 + rate / times, times * years);
}
/** */) for all public APIs, exported functions, classes, interfaces, and types.@param, @returns, @throws, and @example tags where applicable.// TODO: with a ticket number for planned improvements.// FIXME: for known issues that need resolution.// HACK: for workarounds that should be revisited.') for strings; backticks ( `) for template literals.typescript
// ✅ Good — braces always, even for single-line if
if (isValid) {
process();
}
// ❌ Bad
if (isValid) process();
// ✅ Good — trailing comma
const config = {
host: 'localhost',
port: 3000,
debug: true,
};
// ✅ Good — consistent object shorthand
const name = 'Alice';
const user = { name, age: 30 };
Use Prettier for auto-formatting with the following baseline config: json
{
"semi": true,
"singleQuote": true,
"trailingComma": "all",
"printWidth": 100,
"tabWidth": 2,
"arrowParens": "always"
}
Use ESLint with @typescript-eslint/eslint-plugin and
@typescript-eslint/parser for linting.
typescript
// ✅ Good — optional chaining
const city = user?.address?.city;
// ✅ Good — nullish coalescing
const displayName = user.nickname ?? user.name ?? 'Anonymous';
// ✅ Good — explicit null checks for critical paths
function getUser(id: string): User {
const user = repository.findById(id);
if (user === undefined) {
throw new AppError(`User not found: ${id}`, 'USER_NOT_FOUND', 404);
}
return user;
}
strictNullChecks (included in strict mode).undefined over null as the "absence of value" indicator, unless interacting with
external APIs that use null.?.) and nullish coalescing ( ??) instead of manual checks.!) unless you can prove the value is always defined (add a
comment explaining why).satisfies operator (TS 5.0+) to validate types without widening.typescript
// ✅ Good — type guard function
function isUser(value: unknown): value is User {
return (
typeof value === 'object' &&
value !== null &&
'id' in value &&
'name' in value
);
}
// ✅ Good — discriminated union
interface Success<T> {
kind: 'success';
data: T;
}
interface Failure {
kind: 'failure';
error: Error;
}
type Result<T> = Success<T> | Failure;
function handleResult<T>(result: Result<T>): T {
switch (result.kind) {
case 'success':
return result.data;
case 'failure':
throw result.error;
}
}
Prefer type guards ( is keyword) over type assertions ( as).
Use as const for literal narrowing, not as SpecificType.
Use discriminated unions with a kind / type field for state machines and result types.
Avoid as unknown as T double assertions — they are almost always a design smell.
When assertions are necessary, add a comment justifying them.
Use satisfies for type validation without assertion:
typescript
const palette = {
red: [255, 0, 0],
green: '#00ff00',
} satisfies Record<string, string | number[]>;
tsx
// ✅ Good — functional component with explicit props type
interface UserCardProps {
readonly user: User;
readonly onSelect?: (userId: string) => void;
}
export function UserCard({ user, onSelect }: UserCardProps): React.ReactElement {
const handleClick = useCallback(() => {
onSelect?.(user.id);
}, [onSelect, user.id]);
return (
<div className="user-card" onClick={handleClick} role="button" tabIndex={0}>
<h3>{user.name}</h3>
<p>{user.email}</p>
</div>
);
}
interface (e.g., UserCardProps), not inline.readonly.React.FC — it is discouraged since React 18.React.ReactElement or React.ReactNode as return type annotation.useMemo, stable callbacks with useCallback.typescript
// ✅ Good — descriptive test structure
describe('UserService', () => {
describe('findById', () => {
it('should return the user when the id exists', async () => {
const user = await service.findById('user-1');
expect(user).toEqual(expect.objectContaining({ id: 'user-1' }));
});
it('should return undefined when the id does not exist', async () => {
const user = await service.findById('non-existent');
expect(user).toBeUndefined();
});
});
});
Test files are colocated or in a __tests__ directory.
Name test files *.test.ts or *.spec.ts.
Follow the Arrange → Act → Assert pattern.
One assertion concept per test (multiple expect calls are okay if they test one logical
assertion).
Use descriptive test names that read like a sentence.
Mock external dependencies; do NOT mock the module under test.
Prefer dependency injection to make code testable.
Aim for high test coverage on business logic; don't chase 100% on boilerplate.
Use jest.fn() or equivalent for spies/mocks; type them properly:
typescript
const mockFetch = jest.fn<Promise<User>, [string]>();
tsconfig.json (Baseline)#jsonc
{
"compilerOptions": {
"target": "ES2022",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"lib": ["ES2022"],
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"isolatedModules": true,
"declaration": true,
"declarationMap": true,
"sourceMap": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
"noUncheckedIndexedAccess": true,
"exactOptionalPropertyTypes": true
}
}
RuleSetting@typescript-eslint/no-explicit-any``error``@typescript-eslint/explicit-function-return-type``warn (for exported functions)@typescript-eslint/no-unused-vars``error``@typescript-eslint/consistent-type-imports``error``@typescript-eslint/no-non-null-assertion``warn``@typescript-eslint/prefer-nullish-coalescing``error``@typescript-eslint/prefer-optional-chain``error``@typescript-eslint/strict-boolean-expressions``warn``@typescript-eslint/naming-convention``error (configured per construct)
Simply mention TypeScript in your conversation — the skill activates automatically.
Example prompts:
PromptSkill Response"Write a TypeScript function to merge two objects deeply"Generates a fully typed, standards-compliant deepMerge<T, U>() function following all rules above."Review my TypeScript code for style issues"Analyses the provided code against this guide and suggests improvements."Convert this JavaScript to TypeScript"Converts code, adds strict types, interfaces, and follows all naming / formatting conventions."What's the TypeScript best practice for error handling?"Explains the Result pattern, custom error classes, and typed catch blocks per §10."Create a TypeScript React component for a data table"Generates a well-typed functional component following §16 React rules with proper props interface."Set up a new TypeScript project"Provides tsconfig.json, ESLint config, Prettier config, and project structure following §18.
You can ask about any specific section: