{"record":{"id":"89249696b248a2d0","repo":"can1357/oh-my-pi","slug":"parse-error-unexpected-else","errorCode":null,"errorMessage":"Parse error: unexpected else","messagePattern":"Parse error: unexpected else","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/utils/src/template.ts","lineNumber":253,"sourceCode":"\t\t\tif (cursor < source.length) target.push({ kind: \"text\", value: source.slice(cursor) });\n\t\t\tbreak;\n\t\t}\n\t\tif (open > cursor) target.push({ kind: \"text\", value: source.slice(cursor, open) });\n\t\tif (source.startsWith(\"{{!--\", open)) {\n\t\t\tconst end = source.indexOf(\"--}}\", open + 6);\n\t\t\tif (end < 0) throw new Error(\"Parse error: unclosed comment\");\n\t\t\tcursor = end + 4;\n\t\t\tcontinue;\n\t\t}\n\t\tconst triple = source.startsWith(\"{{{\", open);\n\t\tconst contentStart = open + (triple ? 3 : 2);\n\t\tconst end = findTagEnd(source, contentStart, triple);\n\t\tconst raw = source.slice(contentStart, end).trim();\n\t\tcursor = end + (triple ? 3 : 2);\n\t\tif (!raw || raw.startsWith(\"!\")) continue;\n\t\tif (raw === \"else\" || raw.startsWith(\"else \")) {\n\t\t\tconst current = stack[stack.length - 1];\n\t\t\tif (!current) throw new Error(\"Parse error: unexpected else\");\n\t\t\ttarget = current.node.inverse;\n\t\t\tif (raw.length > 4) {\n\t\t\t\tconst parentTarget = target;\n\t\t\t\tconst nested: BlockNode = {\n\t\t\t\t\tkind: \"block\",\n\t\t\t\t\texpression: parseCall(raw.slice(5)),\n\t\t\t\t\tbody: [],\n\t\t\t\t\tinverse: [],\n\t\t\t\t\tinverted: false,\n\t\t\t\t};\n\t\t\t\ttarget.push(nested);\n\t\t\t\ttarget = nested.body;\n\t\t\t\tstack.push({ node: nested, target: parentTarget, inverted: false });\n\t\t\t}\n\t\t\tcontinue;\n\t\t}\n\t\tif (raw.startsWith(\"#\") || raw.startsWith(\"^\")) {\n\t\t\tconst inverted = raw.startsWith(\"^\");","sourceCodeStart":235,"sourceCodeEnd":271,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/utils/src/template.ts#L235-L271","documentation":"During parseTemplate(), an `{{else}}` (or `{{else helper}}`) token is only valid while a block (`{{#if}}`, `{{#each}}`, `{{^}}`...) is open. If the block stack is empty when `else` is encountered, there is no block to attach the inverse section to, so the parser throws \"Parse error: unexpected else\".","triggerScenarios":"Parsing a template that contains `{{else}}` outside any open block — e.g. after the enclosing `{{#if ...}}...{{/if}}` was already closed, or a stray `{{else}}` at top level.","commonSituations":"Manually editing templates and deleting the opening `{{#if}}` while keeping the `{{else}}`; renaming a block and accidentally closing it early; generated templates where conditional logic was removed but the else branch remained.","solutions":["Find the `{{else}}` and either delete it or restore the opening block (`{{#if ...}}` / `{{#each ...}}`) it belongs to.","Check for an extra `{{/if}}` or `{{/each}}` earlier in the template that closes the block before the else.","Count block opens vs closes (`#`/`^` vs `/`) to confirm the else sits inside exactly one open block."],"exampleFix":"// before\n{{#if user}}Hi{{/if}}\n{{else}}Anonymous\n// after\n{{#if user}}Hi{{else}}Anonymous{{/if}}","handlingStrategy":"validation","validationCode":"function validateElsePlacement(src) {\n  const tokens = src.match(/\\{\\{[#/^]?[^}]*\\}\\}/g) ?? [];\n  let depth = 0;\n  for (const t of tokens) {\n    const raw = t.slice(2, -2).trim();\n    if (raw === 'else' || raw.startsWith('else ')) {\n      if (depth === 0) return { ok: false, at: t };\n    } else if (raw.startsWith('#') || raw.startsWith('^')) depth++;\n    else if (raw.startsWith('/')) depth--;\n  }\n  return { ok: true };\n}\nconst check = validateElsePlacement(src); if (!check.ok) throw new Error(`{{else}} outside block at ${check.at}`);","typeGuard":null,"tryCatchPattern":"try {\n  render(src, data);\n} catch (err) {\n  if (err instanceof Error && err.message === 'Parse error: unexpected else') {\n    throw new Error('Template has {{else}} outside any open block — fix template source');\n  }\n  throw err;\n}","preventionTips":["Edit blocks as complete units — when deleting `{{#if}}`, delete its `{{else}}` and `{{/if}}` too.","Use an editor plugin that highlights Handlebars block pairs.","Keep templates small; large files make unbalanced blocks harder to spot."],"tags":["template","parse-error","handlebars"],"backgroundTag":"unexpected-else-token","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}