TypeScript
Provide best-practice coding conventions and generate standards-compliant TypeScript code.
MIT-0 · Free to use, modify, and redistribute. No attribution required.
⭐ 2 · 75 · 0 current installs · 0 all-time installs
byenoyao@Wscats
MIT-0
Security Scan
OpenClaw
Benign
high confidencePurpose & Capability
The name and description (TypeScript style guide / code generation and review) match the contents of SKILL.md and README. The skill is instruction-only and does not request unrelated binaries, credentials, or config paths — everything requested is proportionate to a documentation/code-generation helper.
Instruction Scope
SKILL.md is a comprehensive style guide and runtime instruction set for generating/reviewing TypeScript code. It does not instruct the agent to read arbitrary files, access environment variables, call external endpoints, or exfiltrate data. Note: the activation rule ('activates whenever the user says or implies TypeScript') is broad but consistent with the advertised behavior (assist on TypeScript topics).
Install Mechanism
There is no install spec and no code files to write or execute on disk. As an instruction-only skill this has the lowest install risk.
Credentials
The skill requires no environment variables, credentials, or config paths. No secret or unrelated service access is requested.
Persistence & Privilege
The skill does not request always:true and uses normal user-invocable/autonomous invocation defaults. It does not attempt to modify other skills or system-wide settings.
Assessment
This skill is an instruction-only TypeScript style guide and appears internally consistent. It does not request credentials or install software, so the security risk is low. Before installing or enabling it, consider: (1) whether you want the skill to auto-activate any time TypeScript is mentioned (it may respond frequently in multi-topic conversations); (2) reviewing generated code and suggested configs for suitability to your project (style guides are opinionated); and (3) disabling or limiting autonomous invocation if you prefer manual control over when the skill runs. No regex scanner findings were produced because the skill contains no executable code.Like a lobster shell, security has layers — review code before you run it.
Current versionv1.0.6
Download ziplatest
License
MIT-0
Free to use, modify, and redistribute. No attribution required.
SKILL.md
TypeScript Style Guide Skill
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.
Table of Contents
- General Principles
- Naming Conventions
- Types & Interfaces
- Enums
- Variables & Constants
- Functions
- Classes
- Modules & Imports
- Generics
- Error Handling
- Async / Await & Promises
- Comments & Documentation
- Formatting & Style
- Null & Undefined Handling
- Type Assertions & Guards
- React & JSX (when applicable)
- Testing Conventions
- Tooling & Configuration
1. General Principles
- Strict mode always: Enable
"strict": trueintsconfig.json. This turns onnoImplicitAny,strictNullChecks,strictFunctionTypes, and more. - Prefer readability over cleverness: Code is read far more often than written. Favour explicit, self-documenting code.
- Minimise
any: Treat every use ofanyas tech debt. Preferunknownwhen the type is truly not known, or use generics. - Immutability by default: Use
constoverlet; preferreadonlyproperties andReadonlyArray<T>/Readonly<T>utility types. - Single responsibility: Each file, class, and function should have one clear purpose.
- Keep files small: A file should ideally stay under 400 lines. If it grows larger, consider splitting it.
2. Naming Conventions
| Construct | Convention | Example |
|---|---|---|
| Variable / Function | camelCase | getUserName, isActive |
| Boolean variable | camelCase with prefix | isLoading, hasAccess, canEdit |
| Constant (module) | UPPER_SNAKE_CASE | MAX_RETRY_COUNT, API_BASE_URL |
| Constant (local) | camelCase | const defaultTimeout = 3000 |
| Class | PascalCase | UserService, HttpClient |
| Interface | PascalCase | UserProfile, ApiResponse |
| Type alias | PascalCase | UserId, Theme |
| Enum | PascalCase | Direction, HttpStatus |
| Enum member | PascalCase | Direction.Up, HttpStatus.Ok |
| Generic parameter | Single uppercase letter or descriptive PascalCase | T, TKey, TValue |
| File name | kebab-case.ts | user-service.ts, api-client.ts |
| Test file name | kebab-case.test.ts or kebab-case.spec.ts | user-service.test.ts |
| React component file | PascalCase.tsx | UserProfile.tsx |
| Private field | camelCase (no _ prefix) | private count: number |
Additional Naming Rules
- Do NOT prefix interfaces with
I(e.g.,→IUserUser). - Do NOT suffix types/interfaces with
TypeorInterface. - Use descriptive names. Avoid single-letter variables except for conventional usages like loop
indices (
i,j) or generic type parameters (T,K,V). - Acronyms of 2 characters stay uppercase (
IO,ID); 3+ characters use PascalCase (Http,Xml,Api).
3. Types & Interfaces
Prefer interface for Object Shapes
// ✅ 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;
When to Use type vs interface
Use interface when … | Use type when … |
|---|---|
| Defining the shape of an object or class | Creating union or intersection types |
| You want declaration merging | Using mapped / conditional types |
| Extending other interfaces | Aliasing primitive or tuple types |
Rules
- Always annotate function return types explicitly for public APIs.
- Let TypeScript infer types for local variables when the type is obvious.
- Prefer
typeoverinterfacefor function signatures: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.
4. Enums
Prefer String Enums
// ✅ Good — string enum for readability and debuggability
enum Direction {
Up = 'UP',
Down = 'DOWN',
Left = 'LEFT',
Right = 'RIGHT',
}
When to Use const Enum or Union Types
// ✅ 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';
Rules
- Do NOT use numeric enums without explicit values (auto-increment is fragile).
- Prefer string literal union types over enums when the set is small and no namespace features are needed.
- Never mix string and numeric members in the same enum.
5. Variables & Constants
// ✅ 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;
Rules
- Always use
constunless the variable needs reassignment; then uselet. - Never use
var. - Declare variables as close to their first usage as possible.
- One variable declaration per statement.
- Prefer destructuring for extracting properties:
// ✅ Good const { name, age } = user; // ❌ Bad const name = user.name; const age = user.age; - Use
as constfor immutable literal objects / arrays:const ROUTES = { home: '/', about: '/about', contact: '/contact', } as const;
6. Functions
Function Declarations
// ✅ 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 {
// ...
}
Rules
- 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:
// ✅ 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
Functiontype. Use specific function signatures. - Keep functions small — ideally under 30 lines of logic.
- Functions should do one thing.
- Prefer early returns to reduce nesting:
// ✅ Good function getDiscount(user: User): number { if (!user.isPremium) return 0; if (user.yearsActive < 2) return 5; return 10; }
7. Classes
// ✅ 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);
}
}
Rules
- Prefer composition over inheritance.
- Use
readonlyfor properties that should not change after construction. - Member ordering:
- Static fields
- Instance fields
- Constructor
- Static methods
- Public methods
- Protected methods
- Private methods
- Use explicit access modifiers (
public,protected,private) on every member. - Do NOT prefix private members with
_. TypeScript'sprivatekeyword is sufficient. - Use
#(ES private fields) when true runtime privacy is required. - Prefer parameter properties for simple constructor-only assignments:
class Logger { constructor(private readonly prefix: string) {} } - Avoid classes with only static methods — use plain functions in a module instead.
- Implement interfaces explicitly:
class UserRepositoryImpl implements UserRepository { ... }
8. Modules & Imports
// ✅ 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';
Rules
- 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 typefor type-only imports to help bundlers tree-shake:import type { User } from './user'; - Group and order imports:
- Node built-ins (
node:fs,node:path) - External packages (
react,lodash) - Internal aliases (
@/utils,@/components) - Relative parent (
../) - Relative sibling (
./)
- Node built-ins (
- 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.
9. Generics
// ✅ 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];
}
Rules
- Use a single uppercase letter (
T,U,K,V) for simple generics. - Use descriptive PascalCase prefixed with
T(TKey,TValue,TResult) when there are multiple type parameters or the purpose is not obvious. - Always add constraints when possible (
<T extends SomeType>). - Avoid unnecessary generics — if a type parameter is used only once, it's likely not needed.
- Prefer generic utility types (
Array<T>,Promise<T>,Map<K, V>) over their shorthand where readability benefits.
10. Error Handling
// ✅ 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,
);
}
}
Rules
- Never swallow errors silently (empty
catchblocks). - Use custom error classes that extend
Errorfor domain-specific errors. - Type the
catchvariable asunknown(default in TS 4.4+) and narrow before use. - Prefer Result / Either patterns for expected failures in libraries:
type Result<T, E = Error> = { ok: true; value: T } | { ok: false; error: E }; - Always clean up resources in
finallyblocks or use disposable patterns. - Log errors with sufficient context (operation name, input summary, stack trace).
11. Async / Await & Promises
// ✅ 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 };
}
Rules
- Prefer
async/awaitover.then()chains for readability. - Always annotate return types as
Promise<T>. - Use
Promise.all()for independent concurrent operations. - Use
Promise.allSettled()when failures should not abort sibling operations. - Never use
new Promise()when an async function suffices (avoid the explicit-construction antipattern). - Avoid
async voidfunctions — they cannot beawait-ed and swallow errors. Exception: event handlers where the framework requiresvoid. - Prefer
for...ofwithawaitfor sequential async iteration, notforEach.
12. Comments & Documentation
/**
* 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);
}
Rules
- Use TSDoc (
/** */) for all public APIs, exported functions, classes, interfaces, and types. - Include
@param,@returns,@throws, and@exampletags where applicable. - Do NOT comment obvious code. The code should be self-documenting.
- Use
// TODO:with a ticket number for planned improvements. - Use
// FIXME:for known issues that need resolution. - Use
// HACK:for workarounds that should be revisited. - Never leave commented-out code in the main branch.
- Place file-level comments at the top if the module's purpose is not obvious from its name.
- Keep comments up-to-date with code changes.
13. Formatting & Style
General
- Indentation: 2 spaces (no tabs).
- Semicolons: Required at the end of every statement.
- Quotes: Single quotes (
') for strings; backticks (`) for template literals. - Trailing commas: Always use in multi-line constructs (arrays, objects, parameters, generics).
- Max line length: 100 characters (soft limit), 120 characters (hard limit).
- Braces: Required for all control structures, even single-line bodies.
- Blank lines: One blank line between top-level declarations; no multiple consecutive blank lines.
Specific Patterns
// ✅ 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 };
Tooling
- Use Prettier for auto-formatting with the following baseline config:
{ "semi": true, "singleQuote": true, "trailingComma": "all", "printWidth": 100, "tabWidth": 2, "arrowParens": "always" } - Use ESLint with
@typescript-eslint/eslint-pluginand@typescript-eslint/parserfor linting.
14. Null & Undefined Handling
// ✅ 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;
}
Rules
- Enable
strictNullChecks(included instrictmode). - Prefer
undefinedovernullas the "absence of value" indicator, unless interacting with external APIs that usenull. - Use optional chaining (
?.) and nullish coalescing (??) instead of manual checks. - Do NOT use non-null assertion (
!) unless you can prove the value is always defined (add a comment explaining why). - Prefer the
satisfiesoperator (TS 5.0+) to validate types without widening.
15. Type Assertions & Guards
// ✅ 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;
}
}
Rules
- Prefer type guards (
iskeyword) over type assertions (as). - Use
as constfor literal narrowing, notas SpecificType. - Use discriminated unions with a
kind/typefield for state machines and result types. - Avoid
as unknown as Tdouble assertions — they are almost always a design smell. - When assertions are necessary, add a comment justifying them.
- Use
satisfiesfor type validation without assertion:const palette = { red: [255, 0, 0], green: '#00ff00', } satisfies Record<string, string | number[]>;
16. React & JSX (when applicable)
// ✅ 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>
);
}
Rules
- Use function declarations or named function expressions for components (not anonymous).
- Define props as an
interface(e.g.,UserCardProps), not inline. - Mark all props as
readonly. - Do NOT use
React.FC— it is discouraged since React 18. - Use
React.ReactElementorReact.ReactNodeas return type annotation. - Colocate styles, tests, and types with the component when practical.
- Prefer controlled components over uncontrolled.
- Memoize expensive computations with
useMemo, stable callbacks withuseCallback. - Extract custom hooks when logic is reused across components.
- Component file structure:
- Imports
- Types / Interfaces
- Constants
- Component function
- Helper functions (private to the module)
17. Testing Conventions
// ✅ 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();
});
});
});
Rules
- Test files are colocated or in a
__tests__directory. - Name test files
*.test.tsor*.spec.ts. - Follow the Arrange → Act → Assert pattern.
- One assertion concept per test (multiple
expectcalls 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:const mockFetch = jest.fn<Promise<User>, [string]>();
18. Tooling & Configuration
Recommended tsconfig.json (Baseline)
{
"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
}
}
Recommended ESLint Rules
| Rule | Setting |
|---|---|
@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) |
Usage
Natural Language Activation
Simply mention TypeScript in your conversation — the skill activates automatically.
Example prompts:
| Prompt | Skill 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. |
Inline Rule References
You can ask about any specific section:
- "Explain TypeScript naming conventions" → References §2
- "How should I handle null in TypeScript?" → References §14
- "Show me TypeScript generic best practices" → References §9
Files
2 totalSelect a file
Select a file to preview.
Comments
Loading comments…
