avoiding-any-types
npx skills add https://github.com/djankies/claude-configs --skill avoiding-any-types
Agent 安装分布
Skill 文档
- Code contains
anytype annotations - Working with external data (APIs, JSON, user input)
- Designing generic functions or types
- User mentions type safety, type checking, or avoiding
any - Files contain patterns like
: any,<any>,as any
The unknown type is any‘s type-safe counterpart. It accepts any value like any, but requires type narrowing before use.
Key Pattern: any â unknown â type guard â safe access
Impact: Prevents runtime errors while maintaining flexibility for truly dynamic data.
Step 1: Identify any Usage
When you see any, ask:
- Is this truly unknowable at compile time? (external data, plugin systems)
- Can I use a more specific type? (union types, generics with constraints)
- Is this laziness or necessity?
Step 2: Choose Replacement Strategy
Strategy A: Use Specific Types (preferred)
- Known structure â Use interfaces/types
- Multiple possible types â Use union types
- Shared shape â Use generics with constraints
Strategy B: Use unknown (when truly dynamic)
- External data with unknown structure
- Plugin/extension systems
- Gradual migration from JavaScript
Strategy C: Keep any (rare, justified cases)
- Interop with poorly-typed libraries
- Complex type manipulation that TypeScript can’t express
- Performance-critical code where type checks are prohibitive
Step 3: Implement Type Guards
When using unknown, implement type guards:
- Runtime validation (Zod, io-ts, custom validators)
- Type predicates for custom guards
- Built-in guards (typeof, instanceof, in)
For detailed patterns and examples:
- Type Guard Patterns: Use the using-type-guards skill for comprehensive type guard implementation
- Runtime Validation: Use the using-runtime-checks skill for validating unknown data
- Generic Constraints: Use the using-generics skill for constraining generic types
â Using any (unsafe)
async function fetchUser(id: string): Promise<any> {
const response = await fetch(`/api/users/${id}`);
return response.json();
}
const user = await fetchUser("123");
console.log(user.name.toUpperCase());
Problem: If API returns { username: string } instead of { name: string }, this crashes at runtime. TypeScript provides no protection.
â
Using unknown + validation (safe)
async function fetchUser(id: string): Promise<unknown> {
const response = await fetch(`/api/users/${id}`);
return response.json();
}
function isUser(value: unknown): value is { name: string } {
return (
typeof value === "object" &&
value !== null &&
"name" in value &&
typeof value.name === "string"
);
}
const userData = await fetchUser("123");
if (isUser(userData)) {
console.log(userData.name.toUpperCase());
} else {
throw new Error("Invalid user data");
}
Better: Use Zod for complex validation (use the using-runtime-checks skill)
Example 2: Generic Function Defaults
â Using any in generic default (unsafe)
interface ApiResponse<T = any> {
data: T;
status: number;
}
const response: ApiResponse = { data: "anything", status: 200 };
response.data.nonexistent.property;
Problem: Generic defaults to any, losing all type safety.
â
Using unknown default (safe)
interface ApiResponse<T = unknown> {
data: T;
status: number;
}
const response: ApiResponse = { data: "anything", status: 200 };
if (typeof response.data === "string") {
console.log(response.data.toUpperCase());
}
Even Better: Require explicit type parameter
interface ApiResponse<T> {
data: T;
status: number;
}
const response: ApiResponse<User> = await fetchUser();
Example 3: Error Handling
â Using any for caught errors (unsafe)
try {
await riskyOperation();
} catch (error: any) {
console.log(error.message);
}
Problem: Not all thrown values are Error objects. This crashes if someone throws a string or number.
â
Using unknown + type guard (safe)
try {
await riskyOperation();
} catch (error: unknown) {
if (error instanceof Error) {
console.log(error.message);
} else {
console.log("Unknown error:", String(error));
}
}
Example 4: Validation Function
â Using any parameter (unsafe)
function validate(data: any): boolean {
return data.email && data.password;
}
Problem: Typos like data.emial are not caught. No autocomplete support.
â
Using unknown + type guard (safe)
interface LoginData {
email: string;
password: string;
}
function isLoginData(data: unknown): data is LoginData {
return (
typeof data === "object" &&
data !== null &&
"email" in data &&
"password" in data &&
typeof data.email === "string" &&
typeof data.password === "string"
);
}
function validate(data: unknown): data is LoginData {
if (!isLoginData(data)) {
return false;
}
return data.email.includes("@") && data.password.length >= 8;
}
- Use
unknownfor external data (APIs, JSON.parse, user input) - Use
unknownfor generic defaults when type is truly dynamic - Implement type guards before accessing
unknownvalues - Use runtime validation libraries (Zod, io-ts) for complex structures
SHOULD:
- Prefer specific types (interfaces, unions) over
unknownwhen structure is known - Use type predicates (
value is Type) for reusable type guards - Narrow
unknownprogressively (check object â check properties â check types)
NEVER:
- Use
anyfor external data - Use
as anyto silence TypeScript errors - Use
anyin public API surfaces - Default generics to
any - Cast
unknownto specific types without validation
After replacing any with unknown:
-
Type Guard Exists:
- Every
unknownvalue has a corresponding type guard - Type guards check structure AND types
- Guards return early on invalid data
- Every
-
Safe Access Only:
- No property access before type narrowing
- No method calls before type narrowing
- IDE autocomplete works after narrowing
-
Error Handling:
- Invalid data handled gracefully
- Clear error messages
- No silent failures
-
Compilation:
npx tsc --noEmitShould pass without
anysuppressions.
1. Library Interop (temporary)
import poorlyTypedLib from "poorly-typed-lib";
const result = poorlyTypedLib.method() as any;
Consider contributing types to DefinitelyTyped or wrapping in typed facade.
2. Type Manipulation Edge Cases
type DeepPartial<T> = {
[P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : T[P];
};
Sometimes TypeScript’s type system can’t express complex patterns. Document why.
3. Explicit Opt-Out
const configSchema: any = generateFromSpec();
Explicitly choosing to skip type checking for this value. Document decision.
Phase 1: Audit
grep -rn ": any" src/
grep -rn "<any>" src/
grep -rn "= any" src/
Phase 2: Classify
- External data â
unknown+ validation - Known structure â specific types
- Generic defaults â remove default or use
unknown - Justified
anyâ document with comment explaining why
Phase 3: Replace
- Start with external boundaries (API layer, JSON parsing)
- Work inward toward core logic
- Add tests to verify runtime behavior unchanged
Phase 4: Prevent
- Enable
noImplicitAnyin tsconfig.json - Add lint rules forbidding
any - Use hooks to catch new
anyintroduction