Skip to main content
Glama

Git Stuff Server

by skurekjakub
liquidAstProcessor.ts4.27 kB
/** * Main AST processor that orchestrates the processing of Liquid nodes in the Markdown AST. */ import { visit } from 'unist-util-visit'; import { Node } from 'unist'; import { LiquidNode } from './types.js'; import { processExpression } from './expressionProcessor.js'; import { processTag } from './tagProcessor.js'; import { associateBlockTags } from './blockAssociator.js'; /** * Process Liquid nodes in the Markdown AST * This function orchestrates the whole processing pipeline: * 1. Collect liquid nodes from the AST * 2. Process expressions and tags separately * 3. Associate related block tags to build block structures */ export function processLiquidNodes(tree: Node): void { // First: Collect all liquid nodes from the AST const liquidNodes: LiquidNode[] = []; const liquidTagNodes: LiquidNode[] = []; visit(tree, (node: Node) => { // Process expression nodes immediately if (node.type === 'liquidExpression') { processExpression(node as LiquidNode); liquidNodes.push(node as LiquidNode); } // Collect tag nodes for multi-stage processing else if (node.type === 'liquidTag') { const tagNode = node as LiquidNode; liquidNodes.push(tagNode); liquidTagNodes.push(tagNode); } }); // Second: Initial processing of individual tag nodes liquidTagNodes.forEach(processTag); // Third: Perform block association on tag nodes if (liquidTagNodes.length > 0) { associateBlockTags(liquidTagNodes); // Validate associations validateBlockAssociations(liquidTagNodes); } } /** * Validate block associations to catch any remaining issues */ function validateBlockAssociations(nodes: LiquidNode[]): void { // Maps to track block associations const blockStartMap = new Map<string, LiquidNode[]>(); const blockEndMap = new Map<string, LiquidNode[]>(); // First pass: collect all block starts and ends by type for (const node of nodes) { if (!node.liquidAST) continue; const { tagName, isBlockStart, isBlockEnd } = node.liquidAST; if (isBlockStart) { const type = tagName; if (!blockStartMap.has(type)) { blockStartMap.set(type, []); } blockStartMap.get(type)!.push(node); } else if (isBlockEnd) { const type = tagName.substring(3); // Remove 'end' prefix if (!blockEndMap.has(type)) { blockEndMap.set(type, []); } blockEndMap.get(type)!.push(node); } } // Second pass: check that all blocks of each type are properly closed for (const [blockType, startNodes] of blockStartMap.entries()) { const endNodes = blockEndMap.get(blockType) || []; // If we have different counts of start and end tags for a block type if (startNodes.length !== endNodes.length) { // Mark orphaned start/end tags with errors if (startNodes.length > endNodes.length) { // More start tags than end tags - some blocks are unclosed const unclosedCount = startNodes.length - endNodes.length; // Identify which start nodes don't have matching end nodes const unmatched = startNodes.filter(node => !node.parseSuccess || !node.blockId || !node.relatedBlockNodes?.some(rel => rel.liquidAST?.isBlockEnd) ); // Mark appropriate number of start nodes as unclosed unmatched.slice(0, unclosedCount).forEach(node => { node.parseError = `Unclosed block tag '${blockType}'`; node.parseSuccess = false; }); } else if (endNodes.length > startNodes.length) { // More end tags than start tags - some end tags are orphaned const orphanedCount = endNodes.length - startNodes.length; // Identify which end nodes don't have matching start nodes const unmatched = endNodes.filter(node => !node.parseSuccess || !node.matchingBlockId || !node.relatedBlockNodes?.some(rel => rel.liquidAST?.isBlockStart) ); // Mark appropriate number of end nodes as orphaned unmatched.slice(0, orphanedCount).forEach(node => { node.parseError = `End tag 'end${blockType}' without a matching start tag`; node.parseSuccess = false; }); } } } }

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/skurekjakub/GitStuffServer'

If you have feedback or need assistance with the MCP directory API, please join our Discord server