Skip to content

Scanner — Utilities

Utils

Functions

splitParams

function splitParams(str: string): string[]
TypeScript

Use splitParams to split a function's parameter string into individual parameter tokens without breaking on commas inside nested type expressions.

When scanning source code and extracting function signatures, a naive str.split(',') breaks on generics like Map<string, number> or tuples like [string, number]. Reach for splitParams any time you're parsing a raw parameter string pulled from a language scanner and need each parameter as a discrete element.

It walks the string character by character, tracking bracket depth across <>, (), [], and {}. Commas are only treated as delimiters when depth is zero — so nested type arguments, destructured objects, and generic constraints are preserved intact.

Parameters

NameTypeRequiredDescription
strstringYesThe raw parameter substring from a function signature — everything between the outermost parentheses, e.g. "key: string, value: Map<string, number>".

Returns

Returns a string[] where each element is one parameter token with its type annotation intact. Pass each element to your parameter parser to extract the name, type, and default value separately.

Heads up

  • The input should be the inner content of the parameter list — don't include the surrounding ( and ), or they'll be counted as bracket depth and the whole string will be returned as a single element.
  • Returns [''] for an empty string rather than [], consistent with ''.split(',') behavior — guard with a length/trim check if you need to detect zero-parameter functions.

Example:

// Inline implementation — do not import from autodocs
function splitParams(str: string): string[] {
  const parts: string[] = []
  let depth = 0
  let current = ''

  for (const char of str) {
    if ('<([{'.includes(char)) depth++
    else if ('>)]}' .includes(char)) depth--

    if (char === ',' && depth === 0) {
      parts.push(current.trim())
      current = ''
    } else {
      current += char
    }
  }

  if (current.trim()) parts.push(current.trim())
  return parts
}

// Simulates a raw parameter string extracted from a scanned function signature:
// function merge(target: Record<string, unknown>, sources: Array<Map<string, number>>, overwrite: boolean): void
const rawParams = 'target: Record<string, unknown>, sources: Array<Map<string, number>>, overwrite: boolean'

try {
  const params = splitParams(rawParams)
  console.log(params)
  // Expected output:
  // [
  //   'target: Record<string, unknown>',
  //   'sources: Array<Map<string, number>>',
  //   'overwrite: boolean'
  // ]

  // Each token can now be parsed individually for name + type
  params.forEach((param, i) => {
    const [name, type] = param.split(':').map(s => s.trim())
    console.log(`param[${i}] → name: "${name}", type: "${type}"`)
  })
  // param[0] → name: "target",    type: "Record<string, unknown>"
  // param[1] → name: "sources",   type: "Array<Map<string, number>>"
  // param[2] → name: "overwrite", type: "boolean"
} catch (err) {
  console.error('Failed to split params:', err)
}
TypeScript

splitParamsNoAngleBrackets

function splitParamsNoAngleBrackets(str: string): string[]
TypeScript

Use splitParamsNoAngleBrackets to split a Go function's parameter string into individual parameter tokens without misinterpreting angle brackets as nesting delimiters.

Reach for this when parsing raw Go function signatures extracted from source code — for example, splitting "ctx context.Context, ch chan<- error, m map[string]int" into its three distinct parameters. It's the Go-specific counterpart to the generic param splitter used for TypeScript and other languages.

Unlike the standard param splitter, this variant treats < and > as plain characters rather than bracket pairs. This matters for Go because chan<- and map[K]V use angle brackets as operators or type syntax, not as generic delimiters — so a depth-tracking splitter that counted them would produce incorrect splits.

Parameters

NameTypeRequiredDescription
strstringYesThe raw parameter list from a Go function signature, without the surrounding parentheses — e.g. "w http.ResponseWriter, r *http.Request".

Returns

Returns a string[] where each element is one parameter token, with surrounding whitespace preserved. Pass each element to your parameter-name/type parser to extract the identifier and type annotation for documentation generation.

Heads up

  • Pass the content between the outer parentheses, not the full signature including them. Including the outer ( and ) will produce a single-element array wrapping the entire string.
  • Nested brackets inside map or channel types — like map[string][]int — are handled correctly because [ and ] depth is tracked.

Example:

// Inline implementation — do not import from autodocs
function splitParamsNoAngleBrackets(str: string): string[] {
  const parts: string[] = []
  let depth = 0
  let current = ''

  for (const char of str) {
    if (char === '(' || char === '[' || char === '{') {
      depth++
      current += char
    } else if (char === ')' || char === ']' || char === '}') {
      depth--
      current += char
    } else if (char === ',' && depth === 0) {
      parts.push(current.trim())
      current = ''
    } else {
      current += char
    }
  }

  if (current.trim()) {
    parts.push(current.trim())
  }

  return parts
}

// Realistic Go function signatures from an HTTP handler and a worker function
const httpHandlerParams = 'w http.ResponseWriter, r *http.Request'
const workerParams = 'ctx context.Context, ch chan<- error, results map[string][]int'
const noParams = ''

try {
  console.log(splitParamsNoAngleBrackets(httpHandlerParams))
  // ["w http.ResponseWriter", "r *http.Request"]

  console.log(splitParamsNoAngleBrackets(workerParams))
  // ["ctx context.Context", "ch chan<- error", "results map[string][]int"]

  console.log(splitParamsNoAngleBrackets(noParams))
  // []
} catch (err) {
  console.error('Failed to split params:', err)
}
TypeScript

getLineNumber

function getLineNumber(source: string, index: number): number
TypeScript

Use getLineNumber to convert a character index into a human-readable, 1-based line number — the format expected by editors, error messages, and diagnostic output.

Reach for this when your scanner or parser has matched something via a regex (which gives you match.index) and you need to report where in the file that match lives. It's the bridge between raw string offsets and the line numbers developers actually see in their editors.

It works by slicing the source up to the given index and counting the newline-delimited segments — so an index on line 1 returns 1, never 0.

NameTypeRequiredDescription
sourcestringYesThe full source file content to scan. The entire string is needed — not just a substring — so line counting starts from the true beginning of the file.
indexnumberYesThe character offset of the match within source, typically match.index from a RegExp exec result. Must be within bounds of source.

Returns a 1-based line number as a number. Use it directly in diagnostic messages, source maps, or to pass into a context-extraction helper that renders a snippet around the matched line.

Heads up:

  • Passing an index equal to source.length (i.e., one past the last character) returns the last line number, not an out-of-bounds error — the slice just covers the whole string.
  • This counts logical newlines (\n), so Windows-style \r\n line endings won't cause incorrect counts, but \r-only endings (classic Mac) will.

Example:

function getLineNumber(source: string, index: number): number {
  return source.slice(0, index).split('\n').length;
}

const sourceFile = `import { createClient } from './client';

const client = createClient({
  apiKey: 'sk_live_4xKj9mNpQr2wZvYt',
  baseUrl: 'https://api.example.com/v1',
});

export async function fetchUser(id: string) {
  return client.users.get(id);
}`;

// Simulate a regex scanner finding a function declaration
const pattern = /export async function (\w+)/g;
let match: RegExpExecArray | null;

while ((match = pattern.exec(sourceFile)) !== null) {
  const line = getLineNumber(sourceFile, match.index);
  console.log(`Found exported function "${match[1]}" at line ${line}`);
}

// Found exported function "fetchUser" at line 8
TypeScript

getSourceContext

function getSourceContext(lines: string[], lineNumber: number, context: number = 5): string
TypeScript

Use getSourceContext to extract a readable snippet of source code centered on a specific line — ideal for embedding relevant code excerpts into generated documentation.

When skrypt scans your codebase and identifies a declaration (a function, class, or type), it needs to show more than just the signature. Pass the full file's lines and the declaration's line number to get the surrounding context that makes the snippet meaningful.

The function slices a window of lines around the target, defaulting to 5 lines before and after. Line numbers follow the 1-based convention used by most editors and parsers — the same format you'd get from a stack trace or a language server.

Parameters

NameTypeRequiredDescription
linesstring[]YesThe source file pre-split by newline — pass source.split('\n') directly. Splitting once and reusing avoids redundant work when processing multiple declarations in the same file.
lineNumbernumberYesThe 1-based line number of the declaration to center the snippet on. Off-by-one errors are common here — most parsers return 1-based numbers, but double-check your AST output.
contextnumberNoLines to include before and after the target line. Defaults to 5. Increase this for verbose declarations like multi-line class definitions; decrease it for tight inline snippets.

Returns

A single string with the extracted lines joined by newlines, ready to embed in MDX, pass to an AI prompt, or render in a code block. Feed this directly into your AI provider call to give the model enough surrounding code to generate accurate documentation.

Heads up

  • lineNumber is 1-based, not 0-based. Passing a 0-based index will silently shift your window up by one line.
  • The window is clamped to file boundaries — you won't get an error if the declaration is near the top or bottom of the file, but the snippet will be asymmetric.

Example:

function getSourceContext(lines, lineNumber, context = 5) {
  const start = Math.max(0, lineNumber - context - 1)
  const end = Math.min(lines.length, lineNumber + context)
  return lines.slice(start, end).join('\n')
}

const sourceFile = `import { z } from 'zod'
import { db } from './db'

/**
 * Creates a new user account.
 */
export async function createUser(email: string, password: string) {
  const parsed = z.string().email().parse(email)
  const hashed = await hashPassword(password)
  return db.users.create({ email: parsed, password: hashed })
}

export function deleteUser(id: string) {
  return db.users.delete(id)
}
`

const lines = sourceFile.split('\n')

// Line 7 is where createUser is declared (1-based)
const snippet = getSourceContext(lines, 7, 3)

console.log(snippet)
// export async function createUser(email: string, password: string) {
//   const parsed = z.string().email().parse(email)
//   const hashed = await hashPassword(password)
//   return db.users.create({ email: parsed, password: hashed })
// }
//
// export function deleteUser(id: string) {
TypeScript

findMatchingBrace

function findMatchingBrace(source: string, openIndex: number): number
TypeScript

Use findMatchingBrace to locate the closing } that matches a given opening { in a source string, correctly handling nested braces.

Reach for this when building a language scanner that needs to extract the full body of a class, function, or block — for example, isolating a Java class definition so you can parse its methods without processing the rest of the file.

It walks forward from openIndex, tracking brace depth so nested {} pairs don't cause a false match. The search starts at the { you provide, not the character after it.

Parameters

NameTypeRequiredDescription
sourcestringYesThe full source code to search — pass the entire file contents, not a pre-sliced substring, so index positions stay accurate.
openIndexnumberYesThe index of the opening { in source. Must point exactly at { — passing any other character will cause incorrect depth tracking and likely return -1.

Returns

The index of the matching } in source. Slice source.substring(openIndex, result + 1) to get the complete braced block. Returns -1 if no matching closing brace is found (e.g., malformed or truncated source).

Heads up

  • openIndex must point directly at a { character. If you have a match result from a regex, use match.index + match[0].indexOf('{') to find the exact brace position rather than the start of the match.
  • A return value of -1 means the source is unbalanced — guard against it before slicing, or you'll get an unintended substring.

Example:

function findMatchingBrace(source, openIndex) {
  let depth = 0;
  for (let i = openIndex; i < source.length; i++) {
    if (source[i] === '{') depth++;
    else if (source[i] === '}') {
      depth--;
      if (depth === 0) return i;
    }
  }
  return -1;
}

const javaSource = `
public class PaymentService {
  private final String apiKey = "sk_live_4xKj9mN2pQ8rT1vW";

  public Receipt charge(String customerId, int amount) {
    if (amount <= 0) {
      throw new IllegalArgumentException("Amount must be positive");
    }
    return gateway.process(customerId, amount);
  }
}

public class RefundService {
  // ...
}
`.trim();

// Find the opening brace of PaymentService
const classMatch = javaSource.match(/public class PaymentService\s*\{/);
const openIndex = classMatch.index + classMatch[0].lastIndexOf('{');

const closeIndex = findMatchingBrace(javaSource, openIndex);

if (closeIndex === -1) {
  console.error("Unbalanced braces — skipping class");
} else {
  const classBody = javaSource.substring(openIndex, closeIndex + 1);
  console.log("Extracted class body:");
  console.log(classBody);
  // {
  //   private final String apiKey = "sk_live_4xKj9mN2pQ8rT1vW";
  //
  //   public Receipt charge(String customerId, int amount) {
  //     if (amount <= 0) {
  //       throw new IllegalArgumentException("Amount must be positive");
  //     }
  //     return gateway.process(customerId, amount);
  //   }
  // }
}
TypeScript
Was this helpful?