{"record":{"id":"65f2cbf1b5a73787","repo":"can1357/oh-my-pi","slug":"parse-error-mismatched-raw","errorCode":null,"errorMessage":"Parse error: mismatched ${raw}","messagePattern":"Parse error: mismatched (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/utils/src/template.ts","lineNumber":287,"sourceCode":"\t\t}\n\t\tif (raw.startsWith(\"#\") || raw.startsWith(\"^\")) {\n\t\t\tconst inverted = raw.startsWith(\"^\");\n\t\t\tconst node: BlockNode = {\n\t\t\t\tkind: \"block\",\n\t\t\t\texpression: parseCall(raw.slice(1)),\n\t\t\t\tbody: [],\n\t\t\t\tinverse: [],\n\t\t\t\tinverted,\n\t\t\t};\n\t\t\ttarget.push(node);\n\t\t\tstack.push({ node, target, inverted });\n\t\t\ttarget = node.body;\n\t\t\tcontinue;\n\t\t}\n\t\tif (raw.startsWith(\"/\")) {\n\t\t\tconst current = stack.pop();\n\t\t\tif (!current || current.node.expression.name !== raw.slice(1).trim())\n\t\t\t\tthrow new Error(`Parse error: mismatched ${raw}`);\n\t\t\tif (current.inverted) [current.node.body, current.node.inverse] = [current.node.inverse, current.node.body];\n\t\t\ttarget = current.target;\n\t\t\tcontinue;\n\t\t}\n\t\tif (raw.startsWith(\">\")) target.push({ kind: \"partial\", expression: parseCall(raw.slice(1)) });\n\t\telse\n\t\t\ttarget.push({\n\t\t\t\tkind: \"output\",\n\t\t\t\texpression: parseCall(raw.startsWith(\"&\") ? raw.slice(1) : raw),\n\t\t\t\tescaped: !triple && !raw.startsWith(\"&\"),\n\t\t\t});\n\t}\n\tif (stack.length) throw new Error(`Parse error: unclosed block ${stack[stack.length - 1].node.expression.name}`);\n\treturn root;\n}\n\nfunction pathParts(path: string): string[] {\n\tconst parts: string[] = [];","sourceCodeStart":269,"sourceCodeEnd":305,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/utils/src/template.ts#L269-L305","documentation":"When parseTemplate() sees a closing tag `{{/name}}`, it pops the open-block stack and requires that the top block has the same name. If the stack is empty (a close with no open block) or the names differ, it throws `Parse error: mismatched /name`. This catches mis-nested or mis-named block closers at parse time.","triggerScenarios":"Rendering a template where `{{/each}}` closes an `{{#if}}` block, `{{/if}}` appears with no open block, or closers are nested in the wrong order (e.g. `{{#each}}{{#if}}{{/each}}{{/if}}`).","commonSituations":"Hand-editing templates and swapping sections around; copy-pasting a block but forgetting to change its closer name; IDE auto-inserting `{{/if}}` after an `{{#each}}` edit.","solutions":["Make each closing tag's name match its opening block (`{{#if x}}...{{/if}}`, `{{#each list}}...{{/each}}`).","Verify nesting order — inner blocks must be closed before outer blocks.","Remove any closer whose block opener was deleted."],"exampleFix":"// before\n{{#each items}}\n  {{#if active}}{{name}}\n{{/each}}\n{{/if}}\n// after\n{{#each items}}\n  {{#if active}}{{name}}{{/if}}\n{{/each}}","handlingStrategy":"validation","validationCode":"function validateBlockNames(src) {\n  const stack = [];\n  for (const t of src.match(/\\{\\{[#/^][^}]*\\}\\}/g) ?? []) {\n    const raw = t.slice(2, -2).trim();\n    if (raw.startsWith('#') || raw.startsWith('^')) stack.push(raw.slice(1).split(/[\\s(]/)[0]);\n    else if (raw.startsWith('/')) {\n      const name = raw.slice(1).trim();\n      if (stack.pop() !== name) return { ok: false, tag: raw };\n    }\n  }\n  return { ok: stack.length === 0, tag: stack[0] };\n}","typeGuard":null,"tryCatchPattern":"try {\n  render(src, data);\n} catch (err) {\n  const m = /^Parse error: mismatched (.+)$/.exec(err?.message ?? '');\n  if (m) throw new Error(`Block closer ${m[1]} does not match the open block — fix nesting/naming`);\n  throw err;\n}","preventionTips":["Copy-paste blocks including their open and close tags together.","Rename open/close tags atomically with editor multi-cursor or find-replace.","Run templates through a parse check in unit tests."],"tags":["template","parse-error","handlebars"],"backgroundTag":"mismatched-block-close","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}