Migration and Adoption Strategies

1. JavaScript to TypeScript Migration Patterns

Strategy Approach Effort Best For
Big Bang Convert entire codebase at once High - requires significant time Small projects, greenfield migration
Incremental Convert files gradually, allowJs: true Moderate - spread over time Large codebases, active development
Module-by-Module Convert one module/feature at a time Low per iteration - controlled pace Modular architectures, microservices
Leaf-to-Root Start with files with no dependencies Low initial effort - increases Bottom-up migration, utility libraries
Root-to-Leaf Start with entry points, move down High initial effort - decreases Top-down migration, see benefits early
Test Files First Convert tests before implementation Moderate - tests guide migration Well-tested codebases

Example: Incremental migration setup

// Step 1: Initialize TypeScript in existing project
npm install --save-dev typescript @types/node
npx tsc --init

// Step 2: Configure tsconfig.json for gradual migration
{
  "compilerOptions": {
    // Allow JavaScript files
    "allowJs": true,
    "checkJs": false,  // Don't type-check JS files (yet)
    
    // Output settings
    "outDir": "./dist",
    "rootDir": "./src",
    
    // Start lenient, tighten over time
    "strict": false,
    "noImplicitAny": false,
    "strictNullChecks": false,
    
    // Module settings
    "module": "ESNext",
    "target": "ES2020",
    "moduleResolution": "node",
    "esModuleInterop": true,
    
    // Source maps for debugging
    "sourceMap": true
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules", "dist"]
}

// Step 3: Rename files incrementally
// Start with utility files (no dependencies)
mv src/utils/date.js src/utils/date.ts
mv src/utils/string.js src/utils/string.ts

// Step 4: Fix type errors in converted files
// Add explicit types, fix any usage

// Step 5: Gradually enable strict settings
{
  "compilerOptions": {
    "noImplicitAny": true,  // Enable gradually
    // "strictNullChecks": true,  // Enable after most files converted
    // "strict": true  // Final goal
  }
}

// Step 6: Track progress
// Count .ts vs .js files
find src -name "*.ts" | wc -l
find src -name "*.js" | wc -l

// Migration checklist
// ✅ tsconfig.json configured
// ✅ Dependencies have type definitions (@types/*)
// ⏳ 25% of files converted (utils, helpers)
// ⏳ 50% of files converted (components, services)
// ⏳ 75% of files converted (pages, routes)
// ⏳ 100% of files converted
// ⏳ strict: true enabled
// ⏳ All any types replaced

Example: Converting a JavaScript file

// Before: user.js
export function getUser(id) {
  return fetch(`/api/users/${id}`)
    .then(res => res.json())
    .then(data => {
      return {
        id: data.id,
        name: data.name,
        email: data.email,
        isActive: data.is_active
      };
    });
}

export function formatUserName(user) {
  return `${user.name} (${user.email})`;
}

export const USER_ROLES = {
  ADMIN: 'admin',
  USER: 'user',
  GUEST: 'guest'
};

// After: user.ts - Step 1 (rename, minimal changes)
export function getUser(id: string): Promise<any> {
  return fetch(`/api/users/${id}`)
    .then(res => res.json())
    .then(data => {
      return {
        id: data.id,
        name: data.name,
        email: data.email,
        isActive: data.is_active
      };
    });
}

export function formatUserName(user: any): string {
  return `${user.name} (${user.email})`;
}

export const USER_ROLES = {
  ADMIN: 'admin',
  USER: 'user',
  GUEST: 'guest'
} as const;

// After: user.ts - Step 2 (add proper types)
interface User {
  id: string;
  name: string;
  email: string;
  isActive: boolean;
}

interface ApiUser {
  id: string;
  name: string;
  email: string;
  is_active: boolean;
}

export async function getUser(id: string): Promise<User> {
  const response = await fetch(`/api/users/${id}`);
  const data: ApiUser = await response.json();
  
  return {
    id: data.id,
    name: data.name,
    email: data.email,
    isActive: data.is_active
  };
}

export function formatUserName(user: User): string {
  return `${user.name} (${user.email})`;
}

export const USER_ROLES = {
  ADMIN: 'admin',
  USER: 'user',
  GUEST: 'guest'
} as const;

export type UserRole = typeof USER_ROLES[keyof typeof USER_ROLES];

// After: user.ts - Step 3 (add error handling, validation)
import { z } from 'zod';

const ApiUserSchema = z.object({
  id: z.string(),
  name: z.string(),
  email: z.string().email(),
  is_active: z.boolean()
});

export async function getUser(id: string): Promise<User> {
  if (!id) {
    throw new Error('User ID is required');
  }

  const response = await fetch(`/api/users/${id}`);
  
  if (!response.ok) {
    throw new Error(`Failed to fetch user: ${response.status}`);
  }
  
  const data = await response.json();
  const validated = ApiUserSchema.parse(data);  // Runtime validation
  
  return {
    id: validated.id,
    name: validated.name,
    email: validated.email,
    isActive: validated.is_active
  };
}

2. Gradual Type Adoption with @ts-check

Feature Description Usage Benefit
@ts-check Enable TypeScript checking in JavaScript files Add // @ts-check at top of file Type checking without conversion
@ts-nocheck Disable TypeScript checking Add // @ts-nocheck at top Temporarily disable for problematic files
@ts-ignore Ignore error on next line Add // @ts-ignore before line Bypass specific errors
@ts-expect-error Expect error on next line Add // @ts-expect-error Document known issues
JSDoc Types Type annotations in comments /** @type {string} */ Add types without .ts conversion
checkJs Enable type checking for all JS files "checkJs": true in tsconfig Project-wide JS type checking

Example: Using @ts-check with JSDoc

// user.js - JavaScript file with type checking
// @ts-check

/**
 * @typedef {Object} User
 * @property {string} id
 * @property {string} name
 * @property {string} email
 * @property {boolean} isActive
 * @property {'admin' | 'user' | 'guest'} role
 */

/**
 * Fetch a user by ID
 * @param {string} id - User ID
 * @returns {Promise<User>} User data
 */
async function getUser(id) {
  const response = await fetch(`/api/users/${id}`);
  /** @type {User} */
  const user = await response.json();
  return user;
}

/**
 * Format user's display name
 * @param {User} user - User object
 * @returns {string} Formatted name
 */
function formatUserName(user) {
  return `${user.name} (${user.email})`;
}

/**
 * @type {User[]}
 */
const users = [];

// TypeScript will now check these!
const user = await getUser('123');
console.log(formatUserName(user));

// This will show an error!
// @ts-expect-error - role is wrong type
const badUser = { id: '1', name: 'Bad', email: 'bad@example.com', isActive: true, role: 'invalid' };

// Import types from .d.ts or .ts files
/**
 * @param {import('./types').Config} config
 */
function initialize(config) {
  // config is typed based on imported type
}

// Generic types in JSDoc
/**
 * @template T
 * @param {T[]} array
 * @param {(item: T) => boolean} predicate
 * @returns {T | undefined}
 */
function find(array, predicate) {
  return array.find(predicate);
}

// This is type-safe!
const numbers = [1, 2, 3];
const result = find(numbers, n => n > 2);  // result: number | undefined

// Union types
/**
 * @param {string | number} value
 * @returns {string}
 */
function stringify(value) {
  return String(value);
}

// Intersection types (use multiple @typedef)
/**
 * @typedef {Object} HasId
 * @property {string} id
 */

/**
 * @typedef {Object} HasName
 * @property {string} name
 */

/**
 * @typedef {HasId & HasName} Entity
 */

/**
 * @param {Entity} entity
 */
function processEntity(entity) {
  console.log(entity.id, entity.name);  // Both properties available
}

Example: Enabling checkJs project-wide

// tsconfig.json - Type check all JavaScript
{
  "compilerOptions": {
    "allowJs": true,
    "checkJs": true,  // Type check all .js files
    "noEmit": true,   // Don't generate output (just check)
    
    // Be lenient initially
    "strict": false,
    "noImplicitAny": false
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules"]
}

// Opt out specific files if needed
// problematic-file.js
// @ts-nocheck
// This file won't be type checked

// Gradually improve coverage
// Run type check
npx tsc --noEmit

// Fix errors one file at a time
// When a file is fully typed, rename to .ts

// package.json scripts
{
  "scripts": {
    "type-check": "tsc --noEmit",
    "type-check:watch": "tsc --noEmit --watch"
  }
}

3. Legacy Code Integration Strategies

Strategy Implementation Use Case Trade-off
Type Stubs Create .d.ts files for legacy modules External JS libraries without types Manual maintenance required
any Escape Hatch Use any for unknown legacy types Quick migration, complex legacy code Loses type safety
Wrapper Pattern Wrap legacy code in typed interfaces Isolate legacy code, provide typed API Additional abstraction layer
Adapter Pattern Convert legacy data structures to typed ones Bridge old and new code Runtime conversion overhead
Facade Pattern Type-safe facade over legacy system Complex legacy subsystems Duplicate logic possible
Gradual Rewrite Rewrite modules with proper types Long-term modernization High effort, longer timeline

Example: Creating type stubs for legacy code

// Legacy JavaScript library (can't modify)
// legacy-lib.js
module.exports = {
  createConnection: function(config) {
    // Implementation
    return {
      query: function(sql, params) {
        // Query implementation
      },
      close: function() {
        // Close connection
      }
    };
  },
  utils: {
    escape: function(str) {
      return str.replace(/'/g, "''");
    }
  }
};

// Create type definitions
// legacy-lib.d.ts
export interface Connection {
  query<T = any>(sql: string, params?: any[]): Promise<T[]>;
  close(): Promise<void>;
}

export interface Config {
  host: string;
  port: number;
  username: string;
  password: string;
  database: string;
}

export function createConnection(config: Config): Connection;

export namespace utils {
  export function escape(str: string): string;
}

// Now can use with types!
import * as legacyLib from './legacy-lib';

const connection = legacyLib.createConnection({
  host: 'localhost',
  port: 5432,
  username: 'user',
  password: 'pass',
  database: 'mydb'
});

const users = await connection.query<User>('SELECT * FROM users');

// Wrapper pattern for better API
// db-wrapper.ts
import * as legacyLib from './legacy-lib';

export class Database {
  private connection: legacyLib.Connection;

  constructor(config: legacyLib.Config) {
    this.connection = legacyLib.createConnection(config);
  }

  async query<T>(sql: string, params: any[] = []): Promise<T[]> {
    // Add logging, error handling, retry logic
    console.log('Executing query:', sql);
    try {
      return await this.connection.query<T>(sql, params);
    } catch (error) {
      console.error('Query failed:', error);
      throw error;
    }
  }

  async findOne<T>(table: string, id: string): Promise<T | null> {
    const results = await this.query<T>(
      `SELECT * FROM ${table} WHERE id = $1`,
      [id]
    );
    return results[0] ?? null;
  }

  async close(): Promise<void> {
    await this.connection.close();
  }
}

// Usage with type safety
const db = new Database({ /* config */ });
const user = await db.findOne<User>('users', '123');  // Typed!

Example: Adapter pattern for legacy data

// Legacy API returns different structure
interface LegacyUserResponse {
  user_id: string;
  user_name: string;
  user_email: string;
  is_active: number;  // 0 or 1
  created_date: string;  // Date string
  user_meta: string;  // JSON string
}

// Modern typed structure
interface User {
  id: string;
  name: string;
  email: string;
  isActive: boolean;
  createdAt: Date;
  metadata: Record<string, unknown>;
}

// Adapter to convert legacy to modern
class UserAdapter {
  static fromLegacy(legacy: LegacyUserResponse): User {
    return {
      id: legacy.user_id,
      name: legacy.user_name,
      email: legacy.user_email,
      isActive: legacy.is_active === 1,
      createdAt: new Date(legacy.created_date),
      metadata: legacy.user_meta ? JSON.parse(legacy.user_meta) : {}
    };
  }

  static toLegacy(user: User): LegacyUserResponse {
    return {
      user_id: user.id,
      user_name: user.name,
      user_email: user.email,
      is_active: user.isActive ? 1 : 0,
      created_date: user.createdAt.toISOString(),
      user_meta: JSON.stringify(user.metadata)
    };
  }
}

// Service layer uses modern types
class UserService {
  async getUser(id: string): Promise<User> {
    // Legacy API call
    const response = await fetch(`/api/legacy/users/${id}`);
    const legacyData: LegacyUserResponse = await response.json();
    
    // Convert to modern structure
    return UserAdapter.fromLegacy(legacyData);
  }

  async updateUser(user: User): Promise<User> {
    // Convert to legacy format
    const legacyData = UserAdapter.toLegacy(user);
    
    const response = await fetch(`/api/legacy/users/${user.id}`, {
      method: 'PUT',
      body: JSON.stringify(legacyData)
    });
    
    const updated: LegacyUserResponse = await response.json();
    return UserAdapter.fromLegacy(updated);
  }
}

// Application code only sees modern types
const service = new UserService();
const user = await service.getUser('123');  // User type
console.log(user.isActive);  // boolean, not number!

4. Third-party Library Type Definitions

Source Installation Quality Notes
DefinitelyTyped npm i -D @types/library-name Community-maintained, varies Most popular libraries covered
Built-in Types Included with library - check package.json Official, usually excellent Look for "types" field in package.json
Custom .d.ts Create in project: types/library-name/index.d.ts Manual effort required For libraries without types
Declaration Maps Use library's .d.ts.map for source navigation Best debugging experience Jump to actual source code
Type Stubs Minimal types to suppress errors Low - just enough to compile Quick fix, improve later
Module Augmentation Extend existing type definitions Good for plugins/extensions Merge with library types

Example: Working with type definitions

// 1. Install types from DefinitelyTyped
npm install --save-dev @types/lodash
npm install --save-dev @types/express
npm install --save-dev @types/node

// 2. Check if library has built-in types
// Look in package.json
{
  "name": "some-library",
  "types": "./dist/index.d.ts",  // Built-in types!
  "typings": "./dist/index.d.ts"  // Alternative field name
}

// 3. Create custom types for untyped library
// types/untyped-lib/index.d.ts
declare module 'untyped-lib' {
  export function doSomething(param: string): void;
  export class SomeClass {
    constructor(config: any);
    method(): Promise<any>;
  }
}

// 4. Module augmentation to extend library types
// types/express-extended.d.ts
import { User } from './models';

declare global {
  namespace Express {
    interface Request {
      user?: User;  // Add custom property
      sessionId?: string;
    }
  }
}

// Now can use extended types
import { Request, Response } from 'express';

app.get('/profile', (req: Request, res: Response) => {
  if (req.user) {  // TypeScript knows about req.user!
    res.json(req.user);
  }
});

// 5. Ambient module declarations for non-JS assets
// types/assets.d.ts
declare module '*.png' {
  const value: string;
  export default value;
}

declare module '*.svg' {
  const content: React.FunctionComponent<React.SVGProps<SVGSVGElement>>;
  export default content;
}

declare module '*.css' {
  const classes: { [key: string]: string };
  export default classes;
}

// Now can import assets with types
import logo from './logo.png';  // string
import Icon from './icon.svg';  // React component
import styles from './styles.css';  // object

// 6. Configure TypeScript to find custom types
// tsconfig.json
{
  "compilerOptions": {
    "typeRoots": [
      "./node_modules/@types",
      "./types"  // Custom types directory
    ],
    "types": [
      "node",
      "jest",
      "express"
    ]
  }
}

// 7. Handling libraries with wrong/outdated types
// Use @ts-expect-error with comment
import { someFunction } from 'badly-typed-lib';

// @ts-expect-error - Library types are incorrect, see issue #123
const result = someFunction('param');

5. Team Onboarding and Training Approaches

Activity Duration Focus Outcome
Fundamentals Workshop Half day - 1 day Basic types, interfaces, generics Team can write typed code
Code Review Sessions Ongoing - 30min weekly Review TypeScript PRs, discuss patterns Consistent code quality
Pair Programming Ongoing - 2-4hrs weekly Senior with junior, solve real problems Hands-on learning
Style Guide Creation 1 week Document team conventions Consistency across codebase
Migration Pairing Ongoing - during migration Convert files together, teach patterns Faster migration, knowledge sharing
Brown Bag Sessions Monthly - 30-45min Advanced topics, new features, tips Continuous improvement

Example: Onboarding checklist

// TypeScript Onboarding Checklist

// Week 1: Setup & Basics
☐ Install VS Code with TypeScript extensions
  - ESLint, Prettier, Error Lens
☐ Clone team repository with TypeScript config
☐ Run `npm install` and `npm run type-check`
☐ Complete basic TypeScript tutorial
  - Types, interfaces, functions
☐ Read team style guide
☐ Set up dev environment (tsconfig, linting)

// Week 2: Reading & Understanding
☐ Review 5 existing TypeScript files in codebase
☐ Understand common patterns used by team
☐ Shadow senior developer during code review
☐ Ask questions about unfamiliar patterns
☐ Read documentation on utility types

// Week 3: Small Contributions
☐ Convert 1-2 simple JavaScript files to TypeScript
☐ Submit PR with proper types
☐ Respond to code review feedback
☐ Fix type errors in existing code
☐ Add JSDoc types to JavaScript files

// Week 4: Independent Work
☐ Implement new feature with TypeScript
☐ Write tests with proper types
☐ Review others' TypeScript PRs
☐ Refactor code to use better types
☐ Share learnings with team

// Ongoing Learning
☐ Attend weekly TypeScript office hours
☐ Contribute to team style guide
☐ Learn advanced patterns (generics, utility types)
☐ Stay updated with TypeScript releases
☐ Help onboard new team members

Example: Team training resources

// Team TypeScript Learning Path

// 1. Official Documentation
// https://www.typescriptlang.org/docs/handbook/intro.html
// Comprehensive, authoritative source

// 2. Team Wiki / Confluence
// Document team-specific patterns and conventions
// Examples:
// - How we handle API types
// - Common utility types we use
// - Error handling patterns
// - Testing conventions

// 3. Internal Code Examples
// Create examples repository with common patterns

// /examples/basic-types.ts
interface User {
  id: string;
  email: string;
  name: string;
}

// /examples/generics.ts
function createResponse<T>(data: T): ApiResponse<T> {
  return { success: true, data };
}

// /examples/utility-types.ts
type UpdateUserInput = Partial<Omit<User, 'id'>>;

// 4. Code Review Guidelines
// - Always provide types for function parameters and returns
// - Prefer interfaces over type aliases for object shapes
// - Use unknown over any when type is truly unknown
// - Document complex types with JSDoc
// - Use utility types instead of manual type manipulation

// 5. Weekly Office Hours
// Every Friday 2-3pm: TypeScript Q&A
// - Bring questions about types you're stuck on
// - Discuss challenging patterns
// - Review recent TypeScript changes to codebase

// 6. Slack Channel: #typescript-help
// - Ask questions
// - Share tips and articles
// - Discuss TypeScript updates

// 7. Recommended Courses
// - TypeScript Deep Dive (free book)
// - Execute Program: TypeScript
// - Frontend Masters: TypeScript courses
// - Total TypeScript by Matt Pocock

// 8. Practice Exercises
// Type Challenges: https://github.com/type-challenges/type-challenges
// Start with easy, progress to hard

// 9. Lunch & Learn Sessions
// Monthly presentations:
// - "Advanced Generic Patterns"
// - "Type-safe API Clients"
// - "Migrating Legacy Code"
// - "TypeScript 5.0 New Features"

// 10. Mentorship Program
// Pair junior developers with TypeScript experts
// Regular 1-on-1 sessions to review code and answer questions

6. Code Review Guidelines for TypeScript

Category Check For Good Practice Anti-pattern
Type Safety No any, proper types for params/returns Explicit types, unknown over any Excessive any usage, missing types
Type Inference Let TypeScript infer when obvious Infer simple types, annotate complex ones Over-annotation, redundant types
Utility Types Use built-in utilities appropriately Partial<T>, Pick<T, K>, Omit<T, K> Manual type construction when utility exists
Interfaces vs Types Consistent usage per team convention Interface for objects, type for unions/aliases Inconsistent mixing without reason
Null Safety Handle null/undefined properly Optional chaining, nullish coalescing Non-null assertions (!), unchecked access
Type Assertions Minimize use of as keyword Type guards, validation before assertion Unsafe as casts, as any
Documentation JSDoc for public APIs Describe params, returns, examples Missing docs for complex types

Example: Code review checklist

// TypeScript Code Review Checklist

// ✅ Type Safety
// ✓ Good - Explicit types
function getUser(id: string): Promise<User | null> {
  // ...
}

// ✗ Bad - Using any
function getUser(id: any): Promise<any> {
  // ...
}

// ✅ Type Inference
// ✓ Good - Let TypeScript infer
const count = users.length;  // number inferred
const names = users.map(u => u.name);  // string[] inferred

// ✗ Bad - Over-annotation
const count: number = users.length;
const names: string[] = users.map((u: User): string => u.name);

// ✅ Utility Types
// ✓ Good - Use built-in utilities
type UpdateUserInput = Partial<Omit<User, 'id' | 'createdAt'>>;

// ✗ Bad - Manual type construction
type UpdateUserInput = {
  name?: string;
  email?: string;
  age?: number;
  // ... manually list all optional fields
};

// ✅ Null Safety
// ✓ Good - Safe access
const email = user?.profile?.email ?? 'no-email@example.com';

// ✗ Bad - Non-null assertion
const email = user!.profile!.email;  // Can throw at runtime

// ✅ Type Guards vs Assertions
// ✓ Good - Type guard
function isUser(value: unknown): value is User {
  return (
    typeof value === 'object' &&
    value !== null &&
    'id' in value &&
    'email' in value
  );
}

if (isUser(data)) {
  console.log(data.email);  // Safe
}

// ✗ Bad - Unsafe assertion
const user = data as User;  // No runtime check!
console.log(user.email);

// ✅ Discriminated Unions
// ✓ Good - Proper discriminated union
type Result<T> = 
  | { success: true; data: T }
  | { success: false; error: string };

function handle<T>(result: Result<T>) {
  if (result.success) {
    console.log(result.data);  // TypeScript knows data exists
  } else {
    console.error(result.error);  // TypeScript knows error exists
  }
}

// ✗ Bad - Unclear union
type Result<T> = {
  data?: T;
  error?: string;
};

// ✅ Function Overloads
// ✓ Good - Clear overloads
function format(value: string): string;
function format(value: number): string;
function format(value: Date): string;
function format(value: string | number | Date): string {
  // Implementation
  return String(value);
}

// ✗ Bad - Single signature with union
function format(value: string | number | Date): string {
  return String(value);
}

// ✅ Const Assertions
// ✓ Good - Narrow to literal types
const config = {
  apiUrl: 'https://api.example.com',
  timeout: 5000
} as const;
// type: { readonly apiUrl: "https://api.example.com"; readonly timeout: 5000 }

// ✗ Bad - Wider types
const config = {
  apiUrl: 'https://api.example.com',  // type: string
  timeout: 5000  // type: number
};

// ✅ Documentation
// ✓ Good - JSDoc for complex functions
/**
 * Fetch paginated users with optional filtering
 * @param page - Page number (1-indexed)
 * @param pageSize - Number of items per page
 * @param filters - Optional filters for user properties
 * @returns Promise resolving to paginated user data
 * @throws {ApiError} If the API request fails
 * @example
 * const result = await getUsers(1, 10, { role: 'admin' });
 */
async function getUsers(
  page: number,
  pageSize: number,
  filters?: UserFilters
): Promise<PaginatedResponse<User>> {
  // ...
}

// ✗ Bad - No documentation
async function getUsers(page: number, pageSize: number, filters?: UserFilters) {
  // ...
}

Example: PR review feedback examples

// Good Review Feedback

// 1. Constructive type improvement
// ❌ Current:
function processData(data: any) { }

// 💬 Feedback:
// "Can we add a proper type for data? If the structure is unknown,
// consider using 'unknown' and adding a type guard."

// ✅ Suggested:
interface DataInput {
  id: string;
  values: number[];
}
function processData(data: DataInput) { }

// 2. Encourage utility types
// ❌ Current:
type UpdateUser = {
  name?: string;
  email?: string;
  age?: number;
};

// 💬 Feedback:
// "We can use Partial<T> and Omit<T, K> here to derive from User type,
// which will automatically stay in sync if User changes."

// ✅ Suggested:
type UpdateUser = Partial<Omit<User, 'id' | 'createdAt'>>;

// 3. Safer null handling
// ❌ Current:
const userName = user.profile.name;

// 💬 Feedback:
// "Profile might be null/undefined. Consider using optional chaining
// and providing a fallback."

// ✅ Suggested:
const userName = user.profile?.name ?? 'Anonymous';

// 4. Better error types
// ❌ Current:
try {
  await fetchData();
} catch (error) {
  console.error(error.message);  // error is 'any'
}

// 💬 Feedback:
// "Can we add proper typing for the error? Consider creating a type guard."

// ✅ Suggested:
try {
  await fetchData();
} catch (error) {
  if (error instanceof Error) {
    console.error(error.message);
  } else {
    console.error('Unknown error:', error);
  }
}

// 5. Consistent naming
// ❌ Current:
interface IUser { }  // Hungarian notation
type userRole = 'admin' | 'user';  // lowercase

// 💬 Feedback:
// "Let's follow our style guide: PascalCase for types/interfaces,
// no 'I' prefix."

// ✅ Suggested:
interface User { }
type UserRole = 'admin' | 'user';

// Approval comments
// ✅ "Great use of discriminated unions here! Very type-safe."
// ✅ "Nice refactoring to use Pick<T, K>. Much cleaner."
// ✅ "Love the type guards - this makes the code much safer."
// ✅ "Excellent JSDoc documentation!"
Note: Migration and adoption best practices:
  • Migration - Start incremental with allowJs, convert file-by-file, gradually enable strict mode
  • @ts-check - Use JSDoc types in JavaScript for gradual adoption before .ts conversion
  • Legacy code - Create type stubs, use wrapper/adapter patterns, isolate untypes code
  • Type definitions - Install @types/* packages, create custom .d.ts, use module augmentation
  • Onboarding - Workshops, code reviews, pair programming, style guide, continuous learning
  • Code review - Check type safety, avoid any, use utilities, document complex types

Migration and Adoption Strategies Summary

  • JS to TS Migration - Incremental approach with allowJs: true, convert file-by-file, enable strict gradually
  • Gradual Adoption - Use @ts-check and JSDoc in JavaScript files before converting to TypeScript
  • Legacy Integration - Create .d.ts stubs, wrapper/adapter patterns, isolate legacy code with typed APIs
  • Type Definitions - Install @types/* from DefinitelyTyped, check for built-in types, create custom declarations
  • Team Onboarding - Workshops, pair programming, code reviews, style guide, continuous learning culture
  • Code Reviews - Enforce type safety, avoid any, use utility types, document complex patterns, provide constructive feedback