Skip to content

Scanner — TypeScript

Typescript

Classes

TypeScriptScanner

class TypeScriptScanner implements Scanner
TypeScript

Use TypeScriptScanner to extract API signatures, parameters, and return types from TypeScript and JavaScript source files so skrypt can generate documentation for them.

This is the scanner skrypt uses internally when it encounters .ts, .tsx, .js, .jsx, .mjs, or .cjs files during a skrypt generate run. You'd reach for it directly when building a custom pipeline — for example, scanning files programmatically before passing results to your own doc renderer, or integrating skrypt's extraction logic into a CI step.

TypeScriptScanner implements the Scanner interface, meaning it fits into the same plugin slot as scanners for other languages. Call canHandle() first to confirm the file is in scope, then call scanFile() to get the structured API data back. Declaration files (.d.ts) are intentionally skipped — the scanner targets source, not compiled output.

Parameters

canHandle(filePath)

NameTypeRequiredDescription
filePathstringYesAbsolute or relative path to the file you want to check. Returns false for .d.ts files even if the extension otherwise matches.

scanFile(filePath)

NameTypeRequiredDescription
filePathstringYesPath to the TypeScript or JavaScript source file to parse. Must be a file canHandle() already confirmed — passing an unsupported file type produces undefined behavior.

Returns

canHandle() returns a boolean — use it as a guard before calling scanFile().

scanFile() returns a Promise<ScanResult> containing the extracted APIElement[] array for that file. Each element carries the name, signature, parameters, return type, and source language. Pass these elements to skrypt's doc generator or your own template renderer to produce documentation.

Heads up

  • languages is a public property set to ['typescript', 'javascript'] — you can read it to discover what a scanner instance supports without calling canHandle() on a specific path.
  • scanFile() reads the file from disk at call time, so make sure the file exists and is readable before invoking it in async pipelines.

Example:

const path = require('path')
const fs = require('fs')
const os = require('os')
const ts = require('typescript')

// Inline types matching the Scanner interface
/**
 * @typedef {{ name: string, signature: string, language: string, parameters: Array<{name: string, type: string}>, returnType: string }} APIElement
 * @typedef {{ elements: APIElement[], filePath: string }} ScanResult
 */

// Minimal self-contained implementation mirroring TypeScriptScanner behavior
class TypeScriptScanner {
  constructor() {
    this.languages = ['typescript', 'javascript']
  }

  canHandle(filePath) {
    return /\.(ts|tsx|js|jsx|mjs|cjs)$/.test(filePath) && !filePath.includes('.d.ts')
  }

  async scanFile(filePath) {
    const source = fs.readFileSync(filePath, 'utf-8')
    const sourceFile = ts.createSourceFile(filePath, source, ts.ScriptTarget.Latest, true)
    const language = filePath.endsWith('.ts') || filePath.endsWith('.tsx') ? 'typescript' : 'javascript'
    const elements = []

    ts.forEachChild(sourceFile, (node) => {
      if (ts.isFunctionDeclaration(node) && node.name) {
        const params = node.parameters.map((p) => ({
          name: p.name.getText(sourceFile),
          type: p.type ? p.type.getText(sourceFile) : 'any',
        }))
        const returnType = node.type ? node.type.getText(sourceFile) : 'void'
        elements.push({
          name: node.name.getText(sourceFile),
          signature: source.slice(node.pos, node.end).trim().split('{')[0].trim(),
          language,
          parameters: params,
          returnType,
        })
      }
    })

    return { filePath, elements }
  }
}

// Write a temporary TypeScript file to scan
const tmpFile = path.join(os.tmpdir(), 'example-api.ts')
fs.writeFileSync(tmpFile, `
export function createUser(name: string, email: string, role: 'admin' | 'member'): Promise<{ id: string }> {
  return fetch('/users', { method: 'POST', body: JSON.stringify({ name, email, role }) })
    .then(r => r.json())
}

export function deleteUser(userId: string): Promise<void> {
  return fetch(\`/users/\${userId}\`, { method: 'DELETE' }).then(() => {})
}
`)

async function main() {
  const scanner = new TypeScriptScanner()

  if (!scanner.canHandle(tmpFile)) {
    console.error('File type not supported')
    process.exit(1)
  }

  try {
    const result = await scanner.scanFile(tmpFile)
    console.log(`Scanned: ${result.filePath}`)
    console.log(`Found ${result.elements.length} API elements:\n`)
    for (const el of result.elements) {
      console.log(`  ${el.name}`)
      console.log(`    Signature : ${el.signature}`)
      console.log(`    Parameters: ${el.parameters.map(p => `${p.name}: ${p.type}`).join(', ')}`)
      console.log(`    Returns   : ${el.returnType}`)
      console.log()
    }
  } catch (err) {
    console.error('Scan failed:', err.message)
  } finally {
    fs.unlinkSync(tmpFile)
  }
}

main()
// Expected output:
// Scanned: /tmp/example-api.ts
// Found 2 API elements:
//
//   createUser
//     Signature : export function createUser(name: string, email: string, role: 'admin' | 'member'): Promise<{ id: string }>
//     Parameters: name: string, email: string, role: 'admin' | 'member'
//     Returns   : Promise<{ id: string }>
//
//   deleteUser
//     Signature : export function deleteUser(userId: string): Promise<void>
//     Parameters: userId: string
//     Returns   : Promise<void>
TypeScript

Methods

canHandle

canHandle(filePath: string): boolean
TypeScript

Use canHandle to determine whether a TypeScriptScanner instance is capable of processing a given file before attempting to scan it.

Call this when routing files to the correct scanner — for example, when iterating over a mixed-language codebase and dispatching each file to the appropriate scanner. It's the gating check that prevents the TypeScript scanner from being invoked on Python or Go files.

Returns true for .ts, .tsx, .js, .jsx, .mjs, and .cjs files. TypeScript declaration files (.d.ts) are explicitly excluded, since they contain no implementation to document.

Parameters

NameTypeRequiredDescription
filePathstringYesAbsolute or relative path to the file being evaluated — the extension and path determine the result.

Returns

A booleantrue if the scanner can process the file, false otherwise. Use this as a guard before calling scanFile; passing an unsupported file directly to scanFile will produce unexpected results.

Heads up

  • .d.ts files return false even though they end in .ts. If you're seeing a TypeScript file skipped unexpectedly, check whether it's a declaration file.
  • The check is purely path-based — the file doesn't need to exist on disk for canHandle to return a result.

Example:

// Inline types — no import from autodocs
interface Scanner {
  languages: string[];
  canHandle(filePath: string): boolean;
}

class TypeScriptScanner implements Scanner {
  languages = ["typescript", "javascript"];

  canHandle(filePath: string): boolean {
    return (
      /\.(ts|tsx|js|jsx|mjs|cjs)$/.test(filePath) &&
      !filePath.includes(".d.ts")
    );
  }
}

const scanner = new TypeScriptScanner();

const files = [
  "/project/src/api/users.ts",
  "/project/src/components/Button.tsx",
  "/project/src/utils/helpers.js",
  "/project/src/types/index.d.ts",
  "/project/src/server.py",
  "/project/src/main.go",
  "/project/dist/index.mjs",
];

try {
  const results = files.map((filePath) => ({
    filePath,
    supported: scanner.canHandle(filePath),
  }));

  const supported = results.filter((r) => r.supported).map((r) => r.filePath);
  const skipped = results.filter((r) => !r.supported).map((r) => r.filePath);

  console.log("Will scan:", supported);
  // Will scan: [
  //   '/project/src/api/users.ts',
  //   '/project/src/components/Button.tsx',
  //   '/project/src/utils/helpers.js',
  //   '/project/dist/index.mjs'
  // ]

  console.log("Skipped:", skipped);
  // Skipped: [
  //   '/project/src/types/index.d.ts',  ← declaration file excluded
  //   '/project/src/server.py',
  //   '/project/src/main.go'
  // ]
} catch (err) {
  console.error("Routing error:", err);
}
TypeScript

scanFile

async scanFile(filePath: string): Promise<ScanResult>
TypeScript

Use scanFile to extract all exported API elements from a TypeScript or JavaScript source file, returning structured metadata ready for documentation generation.

Reach for this when you're building a custom documentation pipeline and need to programmatically scan individual files rather than running skrypt generate across an entire directory. It's the core step in TypeScriptScanner's workflow — call it per file, then pass the results to your doc generator.

scanFile reads the file at the given path, parses it with the TypeScript compiler, and walks the AST to collect exported functions, classes, types, and their signatures. It automatically detects whether to treat the file as TypeScript or JavaScript based on the file extension.

Parameters

NameTypeRequiredDescription
filePathstringYesAbsolute or relative path to the .ts, .tsx, .js, or .jsx file to scan. Declaration files (.d.ts) are not supported and will cause canHandle to return false.

Returns

Returns a Promise<ScanResult> containing the detected language ("typescript" or "javascript") and an array of APIElement objects — one per exported symbol. Each APIElement includes the name, signature, parameters, return type, and source location. Pass the elements array directly to your AI doc generator or merge results across multiple scanFile calls before writing MDX output.

Heads up

  • Only exported symbols are included. Internal functions and unexported types are silently skipped — if an element is missing from results, check that it has an export keyword.
  • scanFile does not follow imports or resolve cross-file types. Parameter types that reference other modules will appear as their raw string representation.

Example:

import { readFileSync, writeFileSync } from "fs";
import { resolve } from "path";

// Inline types matching ScanResult shape (do not import from autodocs)
interface Parameter {
  name: string;
  type: string;
  required: boolean;
}

interface APIElement {
  name: string;
  kind: "function" | "class" | "type" | "interface";
  signature: string;
  parameters: Parameter[];
  returnType: string;
  filePath: string;
  line: number;
}

interface ScanResult {
  language: "typescript" | "javascript";
  elements: APIElement[];
}

// Minimal mock of TypeScriptScanner for demonstration
class TypeScriptScanner {
  canHandle(filePath: string): boolean {
    return /\.(ts|tsx|js|jsx|mjs|cjs)$/.test(filePath) && !filePath.includes(".d.ts");
  }

  async scanFile(filePath: string): Promise<ScanResult> {
    const language =
      filePath.endsWith(".ts") || filePath.endsWith(".tsx")
        ? "typescript"
        : "javascript";

    // Mock AST walk — in production this uses the TypeScript compiler API
    const mockElements: APIElement[] = [
      {
        name: "createUser",
        kind: "function",
        signature: "async createUser(email: string, role: UserRole): Promise<User>",
        parameters: [
          { name: "email", type: "string", required: true },
          { name: "role", type: "UserRole", required: true },
        ],
        returnType: "Promise<User>",
        filePath,
        line: 12,
      },
      {
        name: "UserRole",
        kind: "type",
        signature: 'type UserRole = "admin" | "member" | "viewer"',
        parameters: [],
        returnType: "never",
        filePath,
        line: 4,
      },
    ];

    return { language, elements: mockElements };
  }
}

async function main() {
  const scanner = new TypeScriptScanner();
  const targetFile = resolve("./src/users.ts");

  if (!scanner.canHandle(targetFile)) {
    throw new Error(`Scanner cannot handle file: ${targetFile}`);
  }

  try {
    const result = await scanner.scanFile(targetFile);

    console.log(`Language detected: ${result.language}`);
    console.log(`Exported elements found: ${result.elements.length}`);

    for (const element of result.elements) {
      console.log(`\n[${element.kind.toUpperCase()}] ${element.name} (line ${element.line})`);
      console.log(`  Signature : ${element.signature}`);
      console.log(`  Returns   : ${element.returnType}`);
      if (element.parameters.length > 0) {
        console.log(`  Params    : ${element.parameters.map((p) => p.name).join(", ")}`);
      }
    }

    // Typical next step: pass elements to your doc generator
    // await generateDocs(result.elements, { outputDir: "./content/docs" });
  } catch (err) {
    console.error("Failed to scan file:", err);
    process.exit(1);
  }
}

main();

// Expected output:
// Language detected: typescript
// Exported elements found: 2
//
// [FUNCTION] createUser (line 12)
//   Signature : async createUser(email: string, role: UserRole): Promise<User>
//   Returns   : Promise<User>
//   Params    : email, role
//
// [TYPE] UserRole (line 4)
//   Signature : type UserRole = "admin" | "member" | "viewer"
//   Returns   : never
TypeScript
Was this helpful?