{"record":{"id":"1bba48900940bc9d","repo":"emberjs/ember.js","slug":"invalid-end-tag-closing-tag-must-not-have-attribu","errorCode":null,"errorMessage":"Invalid end tag: closing tag must not have attributes","messagePattern":"Invalid end tag: closing tag must not have attributes","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/@glimmer/syntax/lib/parser/tokenizer-event-handlers.ts","lineNumber":292,"sourceCode":"      // the tokenizer line/column have already been advanced, correct location info\n      if (char === '\\n') {\n        loc = lastPart ? lastPart.loc.getEnd() : this.currentAttr.valueSpan.getStart();\n      } else {\n        loc = loc.move(-1);\n      }\n\n      this.currentAttr.currentPart = b.text({ chars: char, loc: loc.collapsed() });\n    }\n  }\n\n  finishAttributeValue(): void {\n    this.finalizeTextPart();\n\n    let tag = this.currentTag;\n    let tokenizerPos = this.offset();\n\n    if (tag.type === 'EndTag') {\n      throw generateSyntaxError(\n        `Invalid end tag: closing tag must not have attributes`,\n        this.source.spanFor({ start: tag.start.toJSON(), end: tokenizerPos.toJSON() })\n      );\n    }\n\n    let { name, parts, start, isQuoted, isDynamic, valueSpan } = this.currentAttr;\n\n    // Just trying to be helpful with `<Hello |foo|>` rather than letting it through as an attribute\n    if (name.startsWith('|') && parts.length === 0 && !isQuoted && !isDynamic) {\n      throw generateSyntaxError(\n        'Invalid block parameters syntax: block parameters must be preceded by the `as` keyword',\n        start.until(start.move(name.length))\n      );\n    }\n\n    let value = this.assembleAttributeValue(parts, isQuoted, isDynamic, start.until(tokenizerPos));\n    value.loc = valueSpan.withEnd(tokenizerPos);\n","sourceCodeStart":274,"sourceCodeEnd":310,"githubUrl":"https://github.com/emberjs/ember.js/blob/26f97246a8bf2e28edf26ac3093da2e86c04ffc5/packages/@glimmer/syntax/lib/parser/tokenizer-event-handlers.ts#L274-L310","documentation":"HTML closing tags cannot carry attributes. While parsing attribute values, finishAttributeValue checks the current tag; if it is an EndTag (attributes were seen after </name), it throws a syntax error spanning from the tag start to the tokenizer position.","triggerScenarios":"Writing attributes on a closing tag: </div class=\"x\">, </a href={{url}}>. The tokenizer encounters attribute content while this.currentTag.type === 'EndTag', and finishAttributeValue (called from MustacheStatement-driven attribute parsing) throws before building the attribute node.","commonSituations":"Copy/paste errors duplicating an entire element including its attributes onto the closing tag; sloppy hand edits like </div class=\"active\">; string-generated templates that emit attributes on both open and close tags.","solutions":["Remove all attributes (and their values) from the closing tag: </div class=\"x\"> → </div>.","If the attribute was intended to affect the element, move it to the opening tag: <div class=\"x\"> ... </div>.","Search the template for '</' followed by attribute patterns and run ember-template-lint to catch malformed tags early."],"exampleFix":"// before\n<div id=\"main\">\n  content\n</div id=\"main\">\n\n// after\n<div id=\"main\">\n  content\n</div>","handlingStrategy":"validation","validationCode":"// reject attributes on closing tags before compiling\nfunction assertNoEndTagAttributes(template) {\n  const bad = template.match(/<\\s*\\/\\s*[A-Za-z][^>]*\\s+[\\w@][^>]*>/g);\n  if (bad) throw new Error(`Closing tag must not have attributes: ${bad.join(', ')}`);\n}\nassertNoEndTagAttributes(template);","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Keep closing tags bare: only the tag name after '</'","Place all attributes on the opening tag","Check copy/pasted blocks for duplicated attributes on the closing tag","Use a formatter/linter that normalizes tag structure in CI"],"tags":["glimmer","handlebars","template-syntax","html-parsing","end-tag"],"backgroundTag":"glimmer-invalid-end-tag","analyzedSha":"26f97246a8bf2e28edf26ac3093da2e86c04ffc5","analyzedAt":"2026-09-01T05:01:28.182Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}