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
TypeScript
/**
* 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