TypeScript Type Safety Enforcer
Enforce strict TypeScript type safety standards with zero tolerance for shortcuts.
Core Rule: NEVER Use "any" Type
This is an immediate fail. No exceptions.
// ❌ ABSOLUTELY FORBIDDEN
const data: any = getData();
function process(input: any): any { }
const items: any[] = [];
// ✅ ALWAYS DO THIS INSTEAD
const data: UserData = getData();
function process(input: UserInput): ProcessedResult { }
const items: string[] = [];
Type Safety Principles
1. Always Define Proper Types and Interfaces
Bad:
function createUser(data) {
return {
id: data.id,
name: data.name,
email: data.email
};
}
Good:
interface UserInput {
id: string;
name: string;
email: string;
}
interface User {
id: string;
name: string;
email: string;
createdAt: Date;
}
function createUser(data: UserInput): User {
return {
id: data.id,
name: data.name,
email: data.email,
createdAt: new Date()
};
}
2. Explicit Function Parameters and Return Types
Bad:
function calculate(a, b) {
return a + b;
}
async function fetchUser(id) {
const response = await api.get(`/users/${id}`);
return response.data;
}
Good:
function calculate(a: number, b: number): number {
return a + b;
}
async function fetchUser(id: string): Promise<User> {
const response = await api.get<User>(`/users/${id}`);
return response.data;
}
3. Use "unknown" Instead of "any"
When type is truly unknown, use unknown and narrow with type guards.
Bad:
function parseJSON(json: string): any {
return JSON.parse(json);
}
Good:
function parseJSON(json: string): unknown {
return JSON.parse(json);
}
// Then use type guard to narrow
function isUser(data: unknown): data is User {
return (
typeof data === 'object' &&
data !== null &&
'id' in data &&
'name' in data &&
'email' in data
);
}
const data = parseJSON(jsonString);
if (isUser(data)) {
// Now TypeScript knows data is User
console.log(data.name);
}
4. Prefer Specific Types Over Broad Types
Bad:
type Status = string;
function setStatus(status: string) { }
Good:
type Status = 'active' | 'inactive' | 'pending';
function setStatus(status: Status) { }
// Or with enums
enum Status {
Active = 'active',
Inactive = 'inactive',
Pending = 'pending'
}
function setStatus(status: Status) { }
5. Type Guards for Narrowing
Example:
// Type guard functions
function isString(value: unknown): value is string {
return typeof value === 'string';
}
function isUser(value: unknown): value is User {
return (
typeof value === 'object' &&
value !== null &&
'id' in value &&
typeof (value as any).id === 'string'
);
}
// Usage
function process(input: unknown) {
if (isString(input)) {
// TypeScript knows input is string here
console.log(input.toUpperCase());
} else if (isUser(input)) {
// TypeScript knows input is User here
console.log(input.name);
}
}
TypeScript Utility Types
Use built-in utility types instead of "any":
Partial
interface User {
id: string;
name: string;
email: string;
}
// Make all properties optional
function updateUser(id: string, updates: Partial<User>) {
// updates can have any combination of User properties
}
Required
interface UserInput {
name?: string;
email?: string;
}
// Make all properties required
function createUser(data: Required<UserInput>) {
// data.name and data.email are guaranteed to exist
}
Pick<T, K>
interface User {
id: string;
name: string;
email: string;
password: string;
createdAt: Date;
}
// Pick only specific properties
type PublicUser = Pick<User, 'id' | 'name' | 'email'>;
Omit<T, K>
// Exclude specific properties
type UserWithoutPassword = Omit<User, 'password'>;
Record<K, T>
// Create object type with specific keys
type UserRoles = Record<string, 'admin' | 'user' | 'guest'>;
const roles: UserRoles = {
'user1': 'admin',
'user2': 'user'
};
ReturnType
function getUser() {
return { id: '1', name: 'John' };
}
// Get return type of function
type UserType = ReturnType<typeof getUser>;
Strict Type Checking Configuration
Ensure tsconfig.json has strict mode enabled:
{
"compilerOptions": {
"strict": true,
"noImplicitAny": true,
"strictNullChecks": true,
"strictFunctionTypes": true,
"strictBindCallApply": true,
"strictPropertyInitialization": true,
"noImplicitThis": true,
"alwaysStrict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true
}
}
Common Patterns
API Response Typing
Bad:
async function fetchData(url: string) {
const response = await fetch(url);
return response.json();
}
Good:
interface ApiResponse<T> {
data: T;
status: number;
message: string;
}
async function fetchData<T>(url: string): Promise<ApiResponse<T>> {
const response = await fetch(url);
return response.json();
}
// Usage
const userResponse = await fetchData<User>('/api/user');
console.log(userResponse.data.name); // Fully typed
Event Handlers
Bad:
function handleClick(e) {
console.log(e.target.value);
}
Good:
function handleClick(e: React.MouseEvent<HTMLButtonElement>) {
console.log(e.currentTarget.value);
}
function handleChange(e: React.ChangeEvent<HTMLInputElement>) {
console.log(e.target.value);
}
State Management
Bad:
const [data, setData] = useState(null);
Good:
const [data, setData] = useState<User | null>(null);
// Or with initial value
const [users, setUsers] = useState<User[]>([]);
Props in React
Bad:
function UserCard(props) {
return <div>{props.name}</div>;
}
Good:
interface UserCardProps {
user: User;
onClick?: (id: string) => void;
className?: string;
}
function UserCard({ user, onClick, className }: UserCardProps) {
return (
<div className={className} => onClick?.(user.id)}>
{user.name}
</div>
);
}
Error Handling with Types
Bad:
try {
const data = await fetchUser();
} catch (error) {
console.log(error.message);
}
Good:
try {
const data = await fetchUser();
} catch (error) {
if (error instanceof Error) {
console.log(error.message);
} else {
console.log('Unknown error:', error);
}
}
// Or create custom error types
class ApiError extends Error {
constructor(
message: string,
public statusCode: number,
public response?: unknown
) {
super(message);
this.name = 'ApiError';
}
}
try {
const data = await fetchUser();
} catch (error) {
if (error instanceof ApiError) {
console.log(`API Error ${error.statusCode}: ${error.message}`);
} else if (error instanceof Error) {
console.log('Error:', error.message);
}
}
Validation Checklist
Before committing TypeScript code:
- No "any" types anywhere
- All function parameters have explicit types
- All function return types are explicit
- Interfaces defined for all objects
- Proper null/undefined handling with
?or union types - Type guards used when narrowing unknown types
- Utility types used instead of "any"
- Strict mode enabled in tsconfig.json
- No implicit any errors
- All React props properly typed
When to Use Each Approach
Use Interfaces for:
- Object shapes
- API contracts
- Props definitions
- Extending types
interface User {
id: string;
name: string;
}
interface AdminUser extends User {
permissions: string[];
}
Use Types for:
- Unions
- Intersections
- Primitives
- Tuples
type Status = 'active' | 'inactive';
type ID = string | number;
type Point = [number, number];
type UserWithTimestamp = User & { timestamp: Date };
Use Enums for:
- Fixed set of constants
- When you need reverse mapping
enum UserRole {
Admin = 'admin',
User = 'user',
Guest = 'guest'
}
Rejection Criteria
Reject pull requests that contain:
- ❌ Any usage of
anytype without absolute justification - ❌ Implicit any from missing type annotations
- ❌ Functions without return type declarations
- ❌ Untyped parameters
- ❌ Disabled TypeScript checks with
@ts-ignoreor@ts-nocheck - ❌ Type assertions with
as any
Only accept any if:
- Working with truly dynamic third-party library with no types
- Legacy code migration (temporary, must be documented)
- Extremely complex generic type that's impossible to express (rare)
Even then, prefer:
// Instead of any
const data: unknown = thirdPartyLib.getData();
// Or create minimal interface
interface ThirdPartyData {
[key: string]: unknown;
}
Summary: Type Safety is Non-Negotiable
Golden Rules:
- Never use "any" - use "unknown" and type guards
- Always type function parameters and returns explicitly
- Define interfaces for all data structures
- Use utility types (Partial, Pick, Omit, etc.)
- Enable strict mode in tsconfig.json
- Type guards for narrowing unknown types
- Specific types over broad types
- Reject code with "any" types
Remember: Type safety catches bugs at compile time, not runtime. It's not optional—it's essential for maintainable code.