Files
c9s-skills/skills/javascript/SKILL.md
T
aj-geddes e219cfc94e feat: initial release of BMAD Skills for Claude Code
BMAD Skills for Claude Code - v1.0.0

This repository provides a comprehensive implementation of the BMAD Method
for Claude Code using native Claude Code features.

Features:
- 7 comprehensive skills (BMAD, security, Python, JS/TS, DevOps, testing, Git)
- 5 slash commands (/bmad-init, /bmad-prd, /bmad-arch, /bmad-story, /bmad-assess)
- Memory integration for context preservation
- Auto-detection and intelligent suggestions
- Todo tracking for stories
- Hooks for project-level automation
- One-command installation

Credits:
- BMAD Method™ by BMAD Code Organization
- Implementation for Claude Code by contributors

All BMAD methodology credit belongs to the BMAD Code Organization.
See: https://github.com/bmad-code-org/BMAD-METHOD
2025-10-25 03:53:21 -05:00

25 KiB

JavaScript/TypeScript Development Excellence Skill

Overview

This skill provides comprehensive guidance for modern JavaScript (ES6+) and TypeScript development, covering best practices, patterns, and professional development standards.

Modern JavaScript (ES6+)

Variable Declarations

// ❌ Avoid var - function-scoped and hoisted
var x = 10;

// ✅ Use const for values that won't be reassigned
const API_URL = 'https://api.example.com';
const user = { name: 'Alice' };  // Object itself is const, properties can change

// ✅ Use let for values that will be reassigned
let counter = 0;
counter += 1;

// Block scoping
{
    const blockScoped = 'only available here';
    let alsoBlockScoped = 'me too';
}
// console.log(blockScoped);  // ReferenceError

Arrow Functions

// Traditional function
function add(a, b) {
    return a + b;
}

// Arrow function
const add = (a, b) => a + b;

// Multiple lines
const processUser = (user) => {
    const validated = validate(user);
    return transform(validated);
};

// No parameters
const greet = () => console.log('Hello!');

// Single parameter (parentheses optional)
const square = x => x * x;

// Arrow functions don't bind their own 'this'
class Timer {
    constructor() {
        this.seconds = 0;
    }
    
    start() {
        // ✅ Arrow function preserves 'this'
        setInterval(() => {
            this.seconds++;
            console.log(this.seconds);
        }, 1000);
        
        // ❌ Regular function would lose 'this'
        // setInterval(function() {
        //     this.seconds++;  // 'this' is undefined
        // }, 1000);
    }
}

Template Literals

// String interpolation
const name = 'Alice';
const greeting = `Hello, ${name}!`;

// Multi-line strings
const html = `
    <div class="user">
        <h2>${user.name}</h2>
        <p>${user.email}</p>
    </div>
`;

// Expression evaluation
const price = 29.99;
const message = `Total: $${(price * 1.1).toFixed(2)}`;

// Tagged template literals
function sql(strings, ...values) {
    // Custom processing
    return {
        text: strings.reduce((acc, str, i) => 
            acc + str + (values[i] ? '$' + (i + 1) : ''), ''
        ),
        values
    };
}

const userId = 42;
const query = sql`SELECT * FROM users WHERE id = ${userId}`;
// { text: 'SELECT * FROM users WHERE id = $1', values: [42] }

Destructuring

// Array destructuring
const [first, second, ...rest] = [1, 2, 3, 4, 5];
// first = 1, second = 2, rest = [3, 4, 5]

// Skip elements
const [, , third] = [1, 2, 3];

// Default values
const [a = 10, b = 20] = [5];
// a = 5, b = 20

// Object destructuring
const user = { name: 'Alice', age: 30, email: 'alice@example.com' };
const { name, age } = user;

// Rename variables
const { name: userName, age: userAge } = user;

// Nested destructuring
const { address: { city, country } } = user;

// Default values
const { role = 'user' } = user;

// Function parameters
function greet({ name, age }) {
    console.log(`${name} is ${age} years old`);
}

greet({ name: 'Bob', age: 25 });

// Rest properties
const { name, ...otherProps } = user;

Spread Operator

// Array spreading
const arr1 = [1, 2, 3];
const arr2 = [4, 5, 6];
const combined = [...arr1, ...arr2];

// Copy array
const original = [1, 2, 3];
const copy = [...original];

// Object spreading
const defaults = { theme: 'dark', language: 'en' };
const userPrefs = { language: 'fr' };
const config = { ...defaults, ...userPrefs };
// { theme: 'dark', language: 'fr' }

// Clone object (shallow)
const obj = { a: 1, b: 2 };
const clone = { ...obj };

// Function arguments
function sum(a, b, c) {
    return a + b + c;
}

const numbers = [1, 2, 3];
sum(...numbers);  // 6

Enhanced Object Literals

const name = 'Alice';
const age = 30;

// Shorthand property names
const user = {
    name,     // Instead of name: name
    age,      // Instead of age: age
    
    // Method shorthand
    greet() {
        console.log(`Hello, I'm ${this.name}`);
    },
    
    // Computed property names
    [`role_${Date.now()}`]: 'admin',
    
    // Dynamic keys
    [Symbol.iterator]: function*() {
        yield this.name;
        yield this.age;
    }
};

Classes

class User {
    // Private fields (ES2022)
    #password;
    
    // Public fields
    role = 'user';
    
    // Static fields
    static instances = 0;
    
    constructor(name, email, password) {
        this.name = name;
        this.email = email;
        this.#password = password;
        User.instances++;
    }
    
    // Instance method
    greet() {
        console.log(`Hello, ${this.name}`);
    }
    
    // Getter
    get displayName() {
        return this.name.toUpperCase();
    }
    
    // Setter
    set displayName(value) {
        this.name = value;
    }
    
    // Private method
    #validatePassword(password) {
        return password.length >= 8;
    }
    
    // Static method
    static create(data) {
        return new User(data.name, data.email, data.password);
    }
}

// Inheritance
class Admin extends User {
    constructor(name, email, password) {
        super(name, email, password);
        this.role = 'admin';
    }
    
    // Override method
    greet() {
        console.log(`Hello, Admin ${this.name}`);
    }
}

Async/Await

// Promise-based
function fetchUser(id) {
    return fetch(`/api/users/${id}`)
        .then(response => response.json())
        .then(user => {
            console.log(user);
            return user;
        })
        .catch(error => {
            console.error('Error:', error);
            throw error;
        });
}

// Async/await
async function fetchUser(id) {
    try {
        const response = await fetch(`/api/users/${id}`);
        const user = await response.json();
        console.log(user);
        return user;
    } catch (error) {
        console.error('Error:', error);
        throw error;
    }
}

// Parallel execution
async function fetchMultipleUsers(ids) {
    // All requests happen in parallel
    const promises = ids.map(id => fetch(`/api/users/${id}`));
    const responses = await Promise.all(promises);
    const users = await Promise.all(responses.map(r => r.json()));
    return users;
}

// Sequential execution
async function processUsersSequentially(ids) {
    const users = [];
    for (const id of ids) {
        const user = await fetchUser(id);  // Waits for each
        users.push(user);
    }
    return users;
}

// Error handling with Promise.allSettled
async function fetchWithResults(urls) {
    const results = await Promise.allSettled(
        urls.map(url => fetch(url))
    );
    
    const successful = results.filter(r => r.status === 'fulfilled');
    const failed = results.filter(r => r.status === 'rejected');
    
    return { successful, failed };
}

Modules

// Export
// user.js
export class User {
    constructor(name) {
        this.name = name;
    }
}

export function createUser(name) {
    return new User(name);
}

export const DEFAULT_ROLE = 'user';

// Default export
export default class UserManager {
    // ...
}

// Import
// app.js
import UserManager, { User, createUser, DEFAULT_ROLE } from './user.js';

// Import all
import * as userModule from './user.js';
userModule.createUser('Alice');

// Dynamic import
async function loadModule() {
    const module = await import('./user.js');
    const user = module.createUser('Bob');
}

// Re-export
// index.js
export { User, createUser } from './user.js';
export { Admin } from './admin.js';

TypeScript

Type Annotations

// Basic types
let name: string = 'Alice';
let age: number = 30;
let isActive: boolean = true;
let nothing: null = null;
let notDefined: undefined = undefined;

// Arrays
let numbers: number[] = [1, 2, 3];
let strings: Array<string> = ['a', 'b', 'c'];

// Tuples
let tuple: [string, number] = ['Alice', 30];

// Objects
let user: { name: string; age: number } = {
    name: 'Alice',
    age: 30
};

// Functions
function add(a: number, b: number): number {
    return a + b;
}

const multiply = (a: number, b: number): number => a * b;

// Optional parameters
function greet(name: string, title?: string): string {
    return title ? `Hello, ${title} ${name}` : `Hello, ${name}`;
}

// Default parameters
function createUser(name: string, role: string = 'user') {
    return { name, role };
}

// Rest parameters
function sum(...numbers: number[]): number {
    return numbers.reduce((acc, n) => acc + n, 0);
}

// Void return type
function logMessage(message: string): void {
    console.log(message);
}

// Never return type (functions that never return)
function throwError(message: string): never {
    throw new Error(message);
}

Interfaces and Types

// Interface
interface User {
    id: number;
    name: string;
    email: string;
    age?: number;  // Optional property
    readonly createdAt: Date;  // Read-only
}

// Extending interfaces
interface Admin extends User {
    permissions: string[];
}

// Type alias
type UserID = number | string;

type User = {
    id: UserID;
    name: string;
    email: string;
};

// Union types
type Status = 'pending' | 'approved' | 'rejected';
type Result = User | Error;

// Intersection types
type Timestamped = {
    createdAt: Date;
    updatedAt: Date;
};

type TimestampedUser = User & Timestamped;

// Function types
type Validator = (value: string) => boolean;
type AsyncFetcher<T> = (id: number) => Promise<T>;

// Generic types
type Response<T> = {
    data: T;
    error?: string;
};

interface Repository<T> {
    findById(id: number): Promise<T | null>;
    save(entity: T): Promise<T>;
    delete(id: number): Promise<void>;
}

class UserRepository implements Repository<User> {
    async findById(id: number): Promise<User | null> {
        // Implementation
        return null;
    }
    
    async save(user: User): Promise<User> {
        // Implementation
        return user;
    }
    
    async delete(id: number): Promise<void> {
        // Implementation
    }
}

Advanced Types

// Utility types

// Partial - Make all properties optional
type PartialUser = Partial<User>;

// Required - Make all properties required
type RequiredUser = Required<User>;

// Readonly - Make all properties readonly
type ReadonlyUser = Readonly<User>;

// Pick - Select specific properties
type UserPreview = Pick<User, 'id' | 'name'>;

// Omit - Exclude specific properties
type UserWithoutEmail = Omit<User, 'email'>;

// Record - Create object type with specific keys and value types
type UserRoles = Record<string, string[]>;
// { [key: string]: string[] }

// Mapped types
type Nullable<T> = {
    [P in keyof T]: T[P] | null;
};

type NullableUser = Nullable<User>;

// Conditional types
type IsArray<T> = T extends any[] ? true : false;

type Check1 = IsArray<number[]>;  // true
type Check2 = IsArray<string>;    // false

// Template literal types
type EmailLocale = 'en' | 'fr' | 'de';
type EmailId = `${EmailLocale}_${string}`;

const emailId: EmailId = 'en_welcome';  // Valid
// const invalid: EmailId = 'es_welcome';  // Error

// Type guards
function isUser(obj: any): obj is User {
    return 'name' in obj && 'email' in obj;
}

function processData(data: User | Admin) {
    if (isUser(data)) {
        // TypeScript knows data is User here
        console.log(data.name);
    }
}

// Discriminated unions
type Shape =
    | { kind: 'circle'; radius: number }
    | { kind: 'square'; size: number }
    | { kind: 'rectangle'; width: number; height: number };

function getArea(shape: Shape): number {
    switch (shape.kind) {
        case 'circle':
            return Math.PI * shape.radius ** 2;
        case 'square':
            return shape.size ** 2;
        case 'rectangle':
            return shape.width * shape.height;
    }
}

Generics

// Generic function
function identity<T>(value: T): T {
    return value;
}

const num = identity<number>(42);
const str = identity('hello');  // Type inferred

// Generic with constraints
interface HasLength {
    length: number;
}

function getLength<T extends HasLength>(item: T): number {
    return item.length;
}

getLength('hello');      // OK
getLength([1, 2, 3]);    // OK
// getLength(42);        // Error: number doesn't have length

// Generic class
class DataStore<T> {
    private data: T[] = [];
    
    add(item: T): void {
        this.data.push(item);
    }
    
    get(index: number): T | undefined {
        return this.data[index];
    }
    
    filter(predicate: (item: T) => boolean): T[] {
        return this.data.filter(predicate);
    }
}

const userStore = new DataStore<User>();
userStore.add({ id: 1, name: 'Alice', email: 'alice@example.com' });

// Multiple type parameters
function merge<T, U>(obj1: T, obj2: U): T & U {
    return { ...obj1, ...obj2 };
}

const result = merge({ name: 'Alice' }, { age: 30 });
// result: { name: string; age: number }

Decorators (Experimental)

// Enable in tsconfig.json:
// "experimentalDecorators": true

// Class decorator
function logged<T extends { new(...args: any[]): {} }>(constructor: T) {
    return class extends constructor {
        constructor(...args: any[]) {
            super(...args);
            console.log(`Created instance of ${constructor.name}`);
        }
    };
}

@logged
class User {
    constructor(public name: string) {}
}

// Method decorator
function measureTime(
    target: any,
    propertyKey: string,
    descriptor: PropertyDescriptor
) {
    const original = descriptor.value;
    
    descriptor.value = async function(...args: any[]) {
        const start = Date.now();
        const result = await original.apply(this, args);
        const elapsed = Date.now() - start;
        console.log(`${propertyKey} took ${elapsed}ms`);
        return result;
    };
    
    return descriptor;
}

class DataService {
    @measureTime
    async fetchData() {
        // Slow operation
        return data;
    }
}

// Property decorator
function readonly(target: any, propertyKey: string) {
    Object.defineProperty(target, propertyKey, {
        writable: false
    });
}

class Config {
    @readonly
    apiUrl = 'https://api.example.com';
}

React with TypeScript

Component Types

import React, { FC, ReactNode, useState, useEffect } from 'react';

// Props interface
interface UserCardProps {
    user: User;
    onEdit?: (user: User) => void;
    className?: string;
}

// Functional component
const UserCard: FC<UserCardProps> = ({ user, onEdit, className }) => {
    const handleClick = () => {
        onEdit?.(user);
    };
    
    return (
        <div className={className} onClick={handleClick}>
            <h3>{user.name}</h3>
            <p>{user.email}</p>
        </div>
    );
};

// Component with children
interface ContainerProps {
    children: ReactNode;
    title: string;
}

const Container: FC<ContainerProps> = ({ children, title }) => {
    return (
        <div>
            <h2>{title}</h2>
            {children}
        </div>
    );
};

// Hooks with TypeScript
const UserList: FC = () => {
    const [users, setUsers] = useState<User[]>([]);
    const [loading, setLoading] = useState<boolean>(false);
    const [error, setError] = useState<Error | null>(null);
    
    useEffect(() => {
        const fetchUsers = async () => {
            setLoading(true);
            try {
                const response = await fetch('/api/users');
                const data = await response.json();
                setUsers(data);
            } catch (err) {
                setError(err as Error);
            } finally {
                setLoading(false);
            }
        };
        
        fetchUsers();
    }, []);
    
    if (loading) return <div>Loading...</div>;
    if (error) return <div>Error: {error.message}</div>;
    
    return (
        <div>
            {users.map(user => (
                <UserCard key={user.id} user={user} />
            ))}
        </div>
    );
};

// Custom hook
function useLocalStorage<T>(key: string, initialValue: T) {
    const [value, setValue] = useState<T>(() => {
        const stored = localStorage.getItem(key);
        return stored ? JSON.parse(stored) : initialValue;
    });
    
    useEffect(() => {
        localStorage.setItem(key, JSON.stringify(value));
    }, [key, value]);
    
    return [value, setValue] as const;
}

// Usage
const [theme, setTheme] = useLocalStorage<'light' | 'dark'>('theme', 'light');

Best Practices

Error Handling

// Custom error classes
class ValidationError extends Error {
    constructor(
        message: string,
        public field: string
    ) {
        super(message);
        this.name = 'ValidationError';
    }
}

class NotFoundError extends Error {
    constructor(
        message: string,
        public resource: string,
        public id: number
    ) {
        super(message);
        this.name = 'NotFoundError';
    }
}

// Error handling in async functions
async function fetchUser(id: number): Promise<User> {
    try {
        const response = await fetch(`/api/users/${id}`);
        
        if (!response.ok) {
            if (response.status === 404) {
                throw new NotFoundError('User not found', 'user', id);
            }
            throw new Error(`HTTP error: ${response.status}`);
        }
        
        const user = await response.json();
        
        if (!validateUser(user)) {
            throw new ValidationError('Invalid user data', 'user');
        }
        
        return user;
        
    } catch (error) {
        if (error instanceof ValidationError) {
            console.error(`Validation error in ${error.field}:`, error.message);
        } else if (error instanceof NotFoundError) {
            console.error(`${error.resource} ${error.id} not found`);
        } else {
            console.error('Unexpected error:', error);
        }
        throw error;
    }
}

// Result type pattern
type Result<T, E = Error> =
    | { ok: true; value: T }
    | { ok: false; error: E };

async function safeFunction(): Promise<Result<User>> {
    try {
        const user = await fetchUser(1);
        return { ok: true, value: user };
    } catch (error) {
        return { ok: false, error: error as Error };
    }
}

// Usage
const result = await safeFunction();
if (result.ok) {
    console.log(result.value.name);
} else {
    console.error(result.error.message);
}

Null Safety

// Optional chaining
const user: User | undefined = getUser();
const email = user?.email;  // undefined if user is undefined
const city = user?.address?.city;  // Safe navigation

// Nullish coalescing
const name = user?.name ?? 'Guest';  // Use 'Guest' if name is null/undefined
const port = config.port ?? 3000;    // Different from || (handles 0, '', false)

// Non-null assertion (use sparingly)
const element = document.getElementById('root')!;  // Asserts non-null

// Type narrowing
function processUser(user: User | null) {
    if (user === null) {
        return;
    }
    // TypeScript knows user is User here
    console.log(user.name);
}

// Discriminated unions for null handling
type Optional<T> =
    | { hasValue: true; value: T }
    | { hasValue: false };

function getOptional<T>(value: T | null): Optional<T> {
    if (value === null) {
        return { hasValue: false };
    }
    return { hasValue: true, value };
}

Immutability

// Spread for copies
const original = { name: 'Alice', age: 30 };
const updated = { ...original, age: 31 };  // New object

// Array operations (immutable)
const numbers = [1, 2, 3];

// Add item
const withFour = [...numbers, 4];

// Remove item
const without Two = numbers.filter(n => n !== 2);

// Update item
const doubled = numbers.map(n => n * 2);

// Update object in array
const users: User[] = [...];
const updated = users.map(user =>
    user.id === targetId
        ? { ...user, name: newName }
        : user
);

// Immer for complex updates
import produce from 'immer';

const newState = produce(state, draft => {
    draft.users[0].name = 'Bob';
    draft.users.push(newUser);
});

Code Organization

// Feature-based structure
src/
├── features/
   ├── auth/
      ├── components/
         ├── LoginForm.tsx
         └── RegisterForm.tsx
      ├── hooks/
         └── useAuth.ts
      ├── services/
         └── authService.ts
      ├── types/
         └── auth.types.ts
      └── index.ts
   ├── users/
      ├── components/
      ├── hooks/
      ├── services/
      └── types/
   └── ...
├── shared/
   ├── components/
   ├── hooks/
   ├── types/
   └── utils/
├── App.tsx
└── index.tsx

// Barrel exports (index.ts)
export { LoginForm, RegisterForm } from './components';
export { useAuth } from './hooks';
export { authService } from './services';
export type { AuthState, Credentials } from './types';

Testing

Jest with TypeScript

// user.test.ts
import { User } from './user';

describe('User', () => {
    let user: User;
    
    beforeEach(() => {
        user = new User('Alice', 'alice@example.com');
    });
    
    it('should create user with name and email', () => {
        expect(user.name).toBe('Alice');
        expect(user.email).toBe('alice@example.com');
    });
    
    it('should validate email', () => {
        expect(() => new User('Bob', 'invalid')).toThrow();
    });
});

// Mocking
import { fetchUser } from './api';

jest.mock('./api');
const mockedFetchUser = fetchUser as jest.MockedFunction<typeof fetchUser>;

test('should handle API response', async () => {
    mockedFetchUser.mockResolvedValue({
        id: 1,
        name: 'Alice',
        email: 'alice@example.com'
    });
    
    const user = await fetchUser(1);
    expect(user.name).toBe('Alice');
    expect(mockedFetchUser).toHaveBeenCalledWith(1);
});

React Testing Library

import { render, screen, fireEvent, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { UserCard } from './UserCard';

describe('UserCard', () => {
    const mockUser: User = {
        id: 1,
        name: 'Alice',
        email: 'alice@example.com'
    };
    
    it('should render user information', () => {
        render(<UserCard user={mockUser} />);
        
        expect(screen.getByText('Alice')).toBeInTheDocument();
        expect(screen.getByText('alice@example.com')).toBeInTheDocument();
    });
    
    it('should call onEdit when clicked', async () => {
        const onEdit = jest.fn();
        render(<UserCard user={mockUser} onEdit={onEdit} />);
        
        await userEvent.click(screen.getByRole('button', { name: /edit/i }));
        
        expect(onEdit).toHaveBeenCalledWith(mockUser);
    });
    
    it('should handle loading state', async () => {
        render(<UserList />);
        
        expect(screen.getByText(/loading/i)).toBeInTheDocument();
        
        await waitFor(() => {
            expect(screen.queryByText(/loading/i)).not.toBeInTheDocument();
        });
    });
});

Configuration

tsconfig.json

{
  "compilerOptions": {
    "target": "ES2020",
    "lib": ["ES2020", "DOM", "DOM.Iterable"],
    "module": "ESNext",
    "moduleResolution": "node",
    "resolveJsonModule": true,
    "allowJs": true,
    "checkJs": false,
    "jsx": "react-jsx",
    "declaration": true,
    "declarationMap": true,
    "sourceMap": true,
    "outDir": "./dist",
    "rootDir": "./src",
    "removeComments": true,
    "noEmit": true,
    "isolatedModules": true,
    "allowSyntheticDefaultImports": true,
    "esModuleInterop": true,
    "forceConsistentCasingInFileNames": true,
    "strict": true,
    "noUnusedLocals": true,
    "noUnusedParameters": true,
    "noImplicitReturns": true,
    "noFallthroughCasesInSwitch": true,
    "skipLibCheck": true
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules", "dist", "**/*.test.ts"]
}

ESLint Configuration

// .eslintrc.js
module.exports = {
  parser: '@typescript-eslint/parser',
  extends: [
    'eslint:recommended',
    'plugin:@typescript-eslint/recommended',
    'plugin:react/recommended',
    'plugin:react-hooks/recommended',
    'prettier'
  ],
  plugins: ['@typescript-eslint', 'react', 'react-hooks'],
  rules: {
    '@typescript-eslint/explicit-function-return-type': 'off',
    '@typescript-eslint/no-explicit-any': 'warn',
    '@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }],
    'react/react-in-jsx-scope': 'off',
    'react/prop-types': 'off'
  },
  settings: {
    react: {
      version: 'detect'
    }
  }
};

Conclusion

Modern JavaScript/TypeScript development requires:

  • ES6+ features for clean, expressive code
  • TypeScript for type safety and better tooling
  • Async/await for readable asynchronous code
  • Proper error handling and null safety
  • Immutable data patterns
  • Comprehensive testing
  • Strong typing and interfaces

Always prioritize code clarity, type safety, and maintainability.