typescript-guardian
SKILL.md
TypeScript Guardian
Quick Start
This skill enforces TypeScript strict mode and type safety:
- Strict mode compliance: Verify all strict flags enabled
- Any type elimination: Replace
anywith specific types,unknown, or generics - Return type annotations: Add explicit return types to all functions
- Type narrowing: Implement type guards and predicates
When to Use
- TypeScript compiler errors need resolution
anytypes detected in production code- Missing return type annotations
- Strict mode violations (unchecked index access, null/undefined)
Strict Mode Configuration
Current tsconfig.json requirements (already enforced):
{
"compilerOptions": {
"strict": true,
"noUncheckedIndexedAccess": true,
"strictNullChecks": true,
"noImplicitAny": true,
"strictFunctionTypes": true
}
}
Verification commands:
# Check for TypeScript errors
npx tsc --noEmit
# Run type-aware linting
npm run lint:ci
Common Violations & Fixes
1. Implicit Any
// ❌ Implicit any
function process(data) {
return data.value;
}
// ✅ Explicit types
function process(data: { value: string }): string {
return data.value;
}
2. Unchecked Index Access
// ❌ No check for undefined
const users = ['Alice', 'Bob'];
const firstUser = users[0]; // string | undefined
console.log(firstUser.toUpperCase()); // Runtime error if empty
// ✅ Check for undefined
const firstUser = users[0];
if (firstUser !== undefined) {
console.log(firstUser.toUpperCase());
}
// ✅ Or use optional chaining
console.log(users[0]?.toUpperCase());
3. Null/Undefined Not Checked
// ❌ Assuming value exists
interface User {
profile?: { name: string };
}
function greet(user: User): string {
return `Hello, ${user.profile.name}`; // Error: profile might be undefined
}
// ✅ Check for existence
function greet(user: User): string {
if (user.profile === undefined) {
return 'Hello, stranger';
}
return `Hello, ${user.profile.name}`;
}
// ✅ Or use optional chaining
function greet(user: User): string {
return `Hello, ${user.profile?.name ?? 'stranger'}`;
}
Any Type Elimination
Detection Strategy
# Find any types in source code
grep -r ": any" src/ --include="*.ts" --include="*.tsx" --exclude="*.test.ts"
# Find any[] array types
grep -r ": any\\[\\]" src/
# Find type assertions with any
grep -r "as any" src/
ESLint enforces these rules (already configured):
@typescript-eslint/no-explicit-any: error@typescript-eslint/no-unsafe-assignment: error@typescript-eslint/no-unsafe-call: error
Replacement Patterns
Replace Any with Unknown:
// ❌ any (no type safety)
function processData(data: any): void {
console.log(data.value);
}
// ✅ unknown (requires type narrowing)
function processData(data: unknown): void {
if (typeof data === 'object' && data !== null && 'value' in data) {
console.log((data as { value: string }).value);
}
}
// ✅ Best: specific type
interface Data {
value: string;
}
function processData(data: Data): void {
console.log(data.value);
}
Replace Any with Generic:
// ❌ any loses type information
function identity(value: any): any {
return value;
}
// ✅ Generic preserves type
function identity<T>(value: T): T {
return value;
}
Replace Any with Union Type:
// ❌ any accepts anything
function format(value: any): string {
return String(value);
}
// ✅ Union type is specific
function format(value: string | number | boolean): string {
return String(value);
}
Replace Any with Type Predicate:
// ❌ any in type guard
function isString(value: any): boolean {
return typeof value === 'string';
}
// ✅ Type predicate with unknown
function isString(value: unknown): value is string {
return typeof value === 'string';
}
Return Type Annotations
ALWAYS add explicit return types (enforced by ESLint):
// ❌ Missing return type
function getUser(id: string) {
return db.query('SELECT * FROM users WHERE id = ?', [id]);
}
// ✅ Explicit return type
function getUser(id: string): Promise<User | null> {
return db.query('SELECT * FROM users WHERE id = ?', [id]);
}
// ✅ Void for no return
function logError(error: Error): void {
console.error('Error:', error.message);
}
// ✅ Never for functions that don't return
function throwError(message: string): never {
throw new Error(message);
}
Type Narrowing Patterns
Type Guards
// User-defined type guard
function isUser(value: unknown): value is User {
return (
typeof value === 'object' &&
value !== null &&
'id' in value &&
'name' in value &&
typeof (value as User).id === 'string' &&
typeof (value as User).name === 'string'
);
}
// Usage
function processData(data: unknown): void {
if (isUser(data)) {
console.log(data.name); // data is User here
}
}
Discriminated Unions
// Result type with discriminated union
type Result<T, E = Error> =
| { success: true; data: T }
| { success: false; error: E };
function handleResult<T>(result: Result<T>): T {
if (result.success) {
return result.data; // TypeScript knows this is success branch
} else {
throw result.error; // TypeScript knows this is error branch
}
}
Assertion Functions
// Assert function (throws if condition fails)
function assertDefined<T>(
value: T | null | undefined,
message = 'Value is null or undefined',
): asserts value is T {
if (value === null || value === undefined) {
throw new Error(message);
}
}
// Usage
function process(user: User | null): void {
assertDefined(user, 'User not found');
console.log(user.name); // user is User here
}
Generic Constraints
// ❌ Unconstrained generic
function merge<T>(obj1: T, obj2: T): T {
return { ...obj1, ...obj2 }; // Error: Spread requires object
}
// ✅ Constrained to objects
function merge<T extends object>(obj1: T, obj2: T): T {
return { ...obj1, ...obj2 };
}
// ✅ More specific constraint
function merge<T extends Record<string, unknown>>(
obj1: T,
obj2: Partial<T>,
): T {
return { ...obj1, ...obj2 };
}
Workflow
Phase 1: Analysis
- Run
tsc --noEmitto find errors - Scan for
anytypes:grep -r ": any" src/ - Identify missing return types
- Prioritize by severity (compiler errors first)
Phase 2: Remediation
- Replace
anywithunknownor specific types - Add explicit return type annotations
- Fix null/undefined handling with guards
- Add type narrowing where needed
Phase 3: Validation
- Run
tsc --noEmit(must pass) - Run
npm run lint:ci(must pass) - Verify zero
anytypes remain - Check all functions have return types
Common Issues
Array index flagged as potentially undefined
- Solution: Check for undefined or use optional chaining
Object property access on potentially null/undefined
- Solution: Use optional chaining and nullish coalescing:
user?.profile?.name ?? 'Unknown'
Function parameters inferred as any
- Solution: Add explicit type annotations:
function process(data: { value: string })
Type assertion needed for external library
- Solution: Create type definition file in
types/directory
Best Practices
Prefer Unknown Over Any:
// ✅ unknown requires type narrowing
function parse(json: string): unknown {
return JSON.parse(json);
}
Use Type Assertions Sparingly:
// ⚠️ Type assertion (use only when certain)
const user = data as User;
// ✅ Type guard with runtime check
if (isUser(data)) {
const user = data; // Type narrowed safely
}
Document Complex Types:
/**
* Result type for async operations
* @typeParam T - Success value type
* @typeParam E - Error type (defaults to Error)
*/
type Result<T, E = Error> =
| { success: true; data: T }
| { success: false; error: E };
Success Criteria
- Zero TypeScript compiler errors
- Zero ESLint type errors
- No
anytypes in production code - All functions have explicit return types
- Strict mode compliance: 100%
References
- TypeScript Strict Mode: https://www.typescriptlang.org/tsconfig#strict
- ESLint TypeScript Plugin: https://typescript-eslint.io/
- TypeScript Handbook: https://www.typescriptlang.org/docs/handbook/intro.html
Weekly Installs
4
Repository
d-oit/do-novelist-aiFirst Seen
Jan 31, 2026
Security Audits
Installed on
opencode4
cursor4
gemini-cli3
claude-code3
github-copilot3
codex3