UNPKG

legal-markdown-js

Version:

Node.js implementation of LegalMarkdown for processing legal documents with markdown and YAML - Complete feature parity with Ruby version

251 lines 9.27 kB
/** * AST-based Mixin Processor for Legal Markdown Documents * * This module provides a completely rewritten mixin processing system that uses * Abstract Syntax Tree (AST) parsing to avoid text contamination issues present * in the original string-replacement approach. * * Key improvements: * - AST-based parsing prevents variable values from contaminating other text * - Isolated node processing ensures clean variable substitution * - Maintains full compatibility with existing field tracking and highlighting * - Supports all existing mixin types: variables, helpers, conditionals * - Detects [bracket values] in frontmatter as missing values automatically * * Architecture: * 1. Parse content into AST nodes (text, variable, helper, conditional) * 2. Process each mixin node independently with isolated context * 3. Reconstruct document with resolved values * 4. Integrate with field tracking for highlighting and validation * * @example * ```typescript * import { processMixins } from './ast-mixin-processor.js'; * * const content = ` * Client: {{client.name}} * Amount: {{formatCurrency(amount, "EUR")}} * {{premium ? "Premium service included" : ""}} * `; * * const metadata = { * client: { name: "Acme Corp" }, * amount: 50000, * premium: true * }; * * const result = processMixins(content, metadata); * // No text contamination - each mixin processed independently * ``` * * @module */ import { LegalMarkdownOptions } from '../types/index.js'; import type { YamlValue } from '../types/index.js'; /** * Represents a single node in the parsed AST */ export interface MixinNode { /** Type of the node content */ type: 'text' | 'variable' | 'helper' | 'conditional'; /** Original content from the document */ content: string; /** Extracted variable/expression (without {{}} brackets) */ variable?: string; /** Position in the original document */ position: { start: number; end: number; }; /** Resolved value after processing (set during resolution phase) */ resolved?: YamlValue; /** Whether this node had processing errors */ hasError?: boolean; /** Error message if processing failed */ errorMessage?: string; } /** * Result of parsing content into AST */ export interface ParseResult { /** Array of parsed nodes in document order */ nodes: MixinNode[]; /** Whether any parsing errors occurred */ hasErrors: boolean; /** Detailed error information */ errors: Array<{ node: MixinNode; message: string; position: { start: number; end: number; }; }>; } /** * Interface for template loop ranges */ interface TemplateLoopRange { start: number; end: number; variable: string; } /** * Finds all template loop blocks in content and returns their ranges */ declare function findTemplateLoopRanges(content: string): TemplateLoopRange[]; /** * Classifies a mixin variable by its content to determine processing type * * @param variable - The variable content (without {{}} brackets) * @returns The classified type * * @example * ```typescript * classifyMixinType("client.name") // → "variable" * classifyMixinType("formatDate(@today, 'DD/MM')") // → "helper" * classifyMixinType("premium ? 'Yes' : 'No'") // → "conditional" * ``` */ export declare function classifyMixinType(variable: string): 'variable' | 'helper' | 'conditional'; /** * Parses document content into an AST of mixin nodes * * This function identifies all mixin patterns in the content and creates * a structured representation that can be processed without text contamination. * * @param content - Document content to parse * @returns Parsed AST with nodes and any errors encountered * * @example * ```typescript * const content = "Hello {{name}}, amount: {{formatCurrency(total, 'EUR')}}"; * const result = parseContentToAST(content); * * // result.nodes: * // [ * // { type: 'text', content: 'Hello ', position: { start: 0, end: 6 } }, * // { type: 'variable', content: '{{name}}', variable: 'name', position: { start: 6, end: 14 } }, * // { type: 'text', content: ', amount: ', position: { start: 14, end: 25 } }, * // { type: 'helper', content: '{{formatCurrency(total, \'EUR\')}}', variable: 'formatCurrency(total, \'EUR\')', position: { start: 25, end: 56 } } * // ] * ``` */ export declare function parseContentToAST(content: string): ParseResult; /** * Detects values in frontmatter that are wrapped in [brackets] and should be treated as missing values * * @param metadata - The frontmatter metadata object * @returns Set of field paths that contain bracket values * * @example * ```typescript * const metadata = { * client: { name: "[CLIENT NAME]" }, * amount: 50000, * description: "[PROJECT DESCRIPTION]" * }; * * const bracketFields = detectBracketValues(metadata); * // Returns: Set(["client.name", "description"]) * ``` */ export declare function detectBracketValues(metadata: Record<string, YamlValue>, prefix?: string): Set<string>; /** * If `value` is an escaped bracket literal like `\[MyCompany\]` (YAML single-quoted), * returns the unescaped form `[MyCompany]`. Otherwise returns `null`. * * This allows authors to write `'\[value\]'` in YAML to display a literal bracket * placeholder without triggering the "missing field" detection used for `[value]`. */ export declare function unescapeBracketLiteral(value: string): string | null; /** * Resolves a dot-notation path in an object, with support for array indices * * @param obj - The object to traverse * @param path - Dot-notation path with optional array indices * @returns The resolved value or undefined if not found * * @example * ```typescript * const obj = { * parties: [ * { name: "Company A", contact: { email: "a@example.com" } }, * { name: "Company B", contact: { email: "b@example.com" } } * ] * }; * * console.log(resolvePath(obj, "parties[0].name")); // "Company A" * console.log(resolvePath(obj, "parties[1].contact.email")); // "b@example.com" * ``` */ declare function resolvePath(obj: YamlValue, path: string): YamlValue | undefined; /** * Parses comma-separated arguments from a helper function call * * @param argsString - The arguments string to parse * @param metadata - Metadata context for variable resolution * @returns Array of parsed arguments */ declare function parseArguments(argsString: string, metadata: Record<string, YamlValue>): (YamlValue | undefined)[]; /** * Resolves a helper function expression * * @param expression - Helper function expression (e.g., "formatDate(@today, 'long')") * @param metadata - Metadata context for argument resolution * @returns The result of the helper function call, or undefined if invalid */ declare function resolveHelper(expression: string, metadata: Record<string, YamlValue>): YamlValue | undefined; /** * Resolves a conditional expression (ternary operator) * * @param expression - Conditional expression (e.g., "premium ? 'Yes' : 'No'") * @param metadata - Metadata context for condition evaluation * @returns The result of the conditional expression */ declare function resolveConditional(expression: string, metadata: Record<string, YamlValue>): YamlValue | undefined; /** * Processes parsed AST nodes and resolves all mixin values * * This is the core processing function that takes parsed nodes and resolves * each mixin independently, preventing text contamination. * * @param nodes - Parsed AST nodes to process * @param metadata - Document metadata for variable resolution * @param options - Processing options * @returns Processed document content with resolved mixins */ export declare function processMixinAST(nodes: MixinNode[], metadata: Record<string, YamlValue>, options?: LegalMarkdownOptions): string; /** * Main entry point for mixin processing with AST-based approach * * This function provides complete API compatibility with the original processMixins * while using the new AST-based processing to prevent text contamination. * * @param content - The document content containing mixin references * @param metadata - Document metadata with variable values * @param options - Processing options * @returns Processed content with mixins resolved * * @example * ```typescript * // API identical to original processMixins * const content = ` * Client: {{client.name}} * Amount: {{formatCurrency(amount, "EUR")}} * {{premium ? "Premium service" : "Standard service"}} * `; * * const metadata = { * client: { name: "Acme Corp" }, * amount: 50000, * premium: true * }; * * const result = processMixins(content, metadata, { enableFieldTrackingInMarkdown: true }); * // Clean output without text contamination * ``` */ export declare function processMixins(content: string, metadata: Record<string, YamlValue>, options?: LegalMarkdownOptions): string; export { findTemplateLoopRanges as _findTemplateLoopRanges, resolvePath as _resolvePath, parseArguments as _parseArguments, resolveHelper as _resolveHelper, resolveConditional as _resolveConditional, }; //# sourceMappingURL=ast-mixin-processor.d.ts.map