{"record":{"id":"5090745f8dac2082","repo":"quasarframework/quasar","slug":"message-at-line-lineno-col-colno-unclose","errorCode":null,"errorMessage":"${message} at line ${lineNo} col ${colNo} (unclosed comment/unclosed string/unclosed tag)","messagePattern":"(.+?) at line (.+?) col (.+?) \\(unclosed comment/unclosed string/unclosed tag\\)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"app-vite/lib/utils/template.js","lineNumber":44,"sourceCode":"function throwParseError(message, str, index) {\n  const whitespace = str.slice(0, index).split(newlineRE)\n\n  const lineNo = whitespace.length\n  const colNo = whitespace[lineNo - 1].length + 1\n\n  message +=\n    ' at line ' +\n    lineNo +\n    ' col ' +\n    colNo +\n    ':\\n\\n' +\n    '  ' +\n    str.split(newlineRE)[lineNo - 1] +\n    '\\n' +\n    ' '.repeat(colNo + 1) +\n    '^'\n\n  throw new Error(message)\n}\n\nfunction escapeRegExp(str) {\n  // From MDN\n  return str.replace(escapeRegexpRE, String.raw`\\$&`) // $& means the whole matched string\n}\n\nfunction trimWS(str, wsLeft, wsRight) {\n  // Slightly confusing,\n  // but _}} will trim the left side of the following string\n  const leftTrim = wsLeft || wsLeft === false ? wsLeft : 'nl'\n  const rightTrim = wsRight || wsRight === false ? wsRight : false\n\n  if (!rightTrim && !leftTrim) return str\n\n  if (leftTrim === 'slurp' && rightTrim === 'slurp') {\n    return str.trim()\n  }","sourceCodeStart":26,"sourceCodeEnd":62,"githubUrl":"https://github.com/quasarframework/quasar/blob/4841521b5f635a971eb9e2710e5542efd194691b/app-vite/lib/utils/template.js#L26-L62","documentation":"Quasar's HTML template preprocessor parses the index.html template into an AST; when the htmlparser2 tokenizer fails it reports the failure as a generic parse error annotated with the line and column plus the likely causes (unclosed comment, unclosed string, unclosed tag). throwParseError also prints the offending source line with a caret for context.","triggerScenarios":"An index.html (or custom app template) passed to getAST contains malformed markup: an unterminated <!-- comment, an attribute string never closed, or a tag missing its closing '>' / matching end tag.","commonSituations":"Hand-editing index.html and leaving '<!-- TODO' open; a copy/paste that dropped a closing quote in an attribute; a build-time template injection inserting raw '<' into the head; merge conflicts leaving stray conflict markers in the template.","solutions":["Open the file and go to the reported line/column; fix the unclosed comment, quote or tag","Look one or two lines above the caret — the actual mistake often starts earlier","Validate the template with an HTML validator or by opening it in a browser's devtools parser","Check for leftover Git conflict markers (<<<<<<<, =======) in index.html"],"exampleFix":"// before (index.html)\n<!-- build configuration\n<title>App</title>\n// after\n<!-- build configuration -->\n<title>App</title>","handlingStrategy":"try-catch","validationCode":"// lightweight sanity check before build-time template parsing\nconst html = fs.readFileSync('index.html', 'utf8')\nif ((html.match(/<!--/g) || []).length !== (html.match(/-->/g) || []).length) {\n  throw new Error('index.html has an unclosed HTML comment')\n}","typeGuard":null,"tryCatchPattern":"try {\n  const ast = getAST(templateHtml)\n} catch (err) {\n  if (err.message.includes('unclosed comment/unclosed string/unclosed tag')) {\n    console.error(err.message) // includes line/col + caret context\n    process.exit(1)\n  } else throw err\n}","preventionTips":["Never leave TODO comments unterminated in index.html","Validate the template after every manual edit or merge","Watch for plugins/injections writing raw '<' into the template","Run an HTML validator in CI on the source template"],"tags":["html","template","parse"],"backgroundTag":"html-parse-error","analyzedSha":"4841521b5f635a971eb9e2710e5542efd194691b","analyzedAt":"2026-08-30T01:13:14.944Z","schemaVersion":2},"datasetVersion":"2026-08-30T03:17:51.788Z"}