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