DEV Community

JW SOLUTIONS
JW SOLUTIONS

Posted on Originally published at jw-solutions.com

Refactoring Legacy any Types in TypeScript with Type Guards and TSDoc

The Problem with any in Legacy Codebases

During rapid migrations from JavaScript to TypeScript, developers frequently annotate complex data structures with any. While this allows the project to compile immediately, it completely disables TypeScript's static type checker, hiding potential runtime exceptions and breaking downstream automation tools like Auto-Doc AI.

Overusing any introduces technical debt that trickles down to modern pipelines. It destroys IDE IntelliSense support and prevents documentation engines from inferring true domain models.

Untyped Legacy Code (Before)

In the following snippet, an API payload is handled using an implicit or explicit any type. There is no runtime checking, type safety, or structured TSDoc metadata.

// ❌ Legacy untyped code susceptible to runtime errors
function processUserData(data: any) {
  console.log("User ID:", data.id);
  if (data.role === "admin") {
    return data.permissions.includes("write");
  }
  return false;
}
Enter fullscreen mode Exit fullscreen mode

Strict Refactoring with Type Guards and TSDoc (After)

To align with TypeScript's strict mode requirements and enable Auto-Doc AI to generate automated API specifications, we replace any with unknown and implement a custom Type Guard alongside formatted TSDoc comments.

/**
 * Validated user permissions structure.
 */
export interface UserPayload {
  id: string;
  role: 'admin' | 'user';
  permissions: string[];
}

/**
 * Custom type guard predicate verifying object structure.
 *
 * @param data - Unstructured payload from remote API.
 * @returns `true` if payload conforms to {@link UserPayload}.
 */
export function isUserPayload(data: unknown): data is UserPayload {
  return (
    typeof data === 'object' &&
    data !== null &&
    'id' in data &&
    typeof (data as Record<string, unknown>).id === 'string' &&
    'role' in data &&
    ['admin', 'user'].includes((data as Record<string, unknown>).role as string) &&
    'permissions' in data &&
    Array.isArray((data as Record<string, unknown>).permissions)
  );
}

/**
 * Evaluates user write privileges with strict runtime validation.
 *
 * @param data - Raw unvalidated input object.
 * @returns True if user has active write permission.
 * @throws {TypeError} If payload structure validation fails.
 */
export function processUserDataStrict(data: unknown): boolean {
  if (!isUserPayload(data)) {
    throw new TypeError('Invalid payload structure: does not satisfy UserPayload');
  }
  if (data.role === 'admin') {
    return data.permissions.includes('write');
  }
  return false;
}
Enter fullscreen mode Exit fullscreen mode

Prettier Integration and Auto-Doc AI Standards

Ensure code consistency across large scale enterprise applications by adding JSDoc/TSDoc formatting plugins to your .prettierrc configuration:

{
  "singleQuote": true,
  "trailingComma": "all",
  "printWidth": 100,
  "tsdoc": true
}
Enter fullscreen mode Exit fullscreen mode

Key Refactoring Best Practices:

  • Replace any with unknown: Forces developers to narrow types safely prior to accessing properties.
  • Leverage Type Predicates (data is Type): Enables the TypeScript compiler to infer types inside validated conditional blocks.
  • Standardize with TSDoc: Explicit @param, @returns, and @throws tags empower Auto-Doc AI to generate complete API documentation automatically.
  • Enforce Rules via CI/CD: Run Prettier and strict TypeScript compilation checks on every pull request.

Try it interactively: Format, validate, and auto-document your typescript directly in JW Solutions Code Formatter & Auto-Doc AI.
Originally published at jw-solutions.com.

Top comments (0)