/** * The acorn TypeScript parser and the two guards every play-source analyzer * needs, in one dependency-free module. * * Extracted so modules that must not import each other can still share one * parser configuration: `docflow-binding` (binding resolution) and * `play-exports` (which plays a file exports) both parse the same source, and * a second parser config would be a silent divergence rather than a loud one. */ import { Parser } from 'acorn'; import { tsPlugin } from 'acorn-typescript'; export type AstNode = { type: string; [key: string]: unknown; }; export const TypeScriptParser = Parser.extend( tsPlugin({ allowSatisfies: true, jsx: { allowNamespaces: true, allowNamespacedObjects: true, }, }) as unknown as (BaseParser: typeof Parser) => typeof Parser, ); export function isAstNode(value: unknown): value is AstNode { return value !== null && typeof value === 'object' && 'type' in value; } export function astArray(value: unknown): AstNode[] { return Array.isArray(value) ? value.filter(isAstNode) : []; } /** Parses play source for analysis. `null` when acorn cannot: the caller * abstains, and the TypeScript diagnostics own the syntax error. */ export function parsePlaySourceForAnalysis(sourceCode: string): AstNode | null { try { return TypeScriptParser.parse(sourceCode, { ecmaVersion: 'latest', sourceType: 'module', allowHashBang: true, }) as unknown as AstNode; } catch { return null; } }