@@ -461,14 +461,20 @@ module.exports = {
461461 }
462462 }
463463
464- function validateClosingTag ( node ) {
464+ function validateClosingTag ( node , lastChildEndLine ) {
465465 // `</tag>` is `2 + tag.length + 1` chars: `</` + tag + `>`
466466 const actualColumnStartLocation = 3 + node . tag . length ;
467467 const actualColumn = node . loc . end . column - actualColumnStartLocation ;
468468 const expectedColumn = node . loc . start . column ;
469-
470- if ( actualColumn !== expectedColumn ) {
471- const actualLine = node . loc . end . line ;
469+ const actualLine = node . loc . end . line ;
470+
471+ // Closing tag must not appear before the last child ends (line check),
472+ // and must be column-aligned with the opening tag.
473+ // Note: Glimmer AST may not include trailing whitespace TextNodes, so the
474+ // closing tag can be on a LATER line than lastChildEndLine (whitespace gap).
475+ // The original ember-template-lint's strict line equality works because
476+ // Handlebars AST includes trailing TextNodes that span to the closing tag line.
477+ if ( actualLine < lastChildEndLine || actualColumn !== expectedColumn ) {
472478 context . report ( {
473479 node,
474480 messageId : 'incorrectClosingTag' ,
@@ -477,7 +483,7 @@ module.exports = {
477483 tagName : node . tag ,
478484 actualLine,
479485 actualColumn,
480- expectedLine : actualLine ,
486+ expectedLine : lastChildEndLine ,
481487 expectedColumn,
482488 } ,
483489 } ) ;
@@ -528,7 +534,12 @@ module.exports = {
528534 }
529535
530536 if ( node . children . length > 0 ) {
531- validateClosingTag ( node ) ;
537+ const lastChild = node . children . at ( - 1 ) ;
538+ const expectedStartLine =
539+ lastChild . type === 'GlimmerBlockStatement'
540+ ? lastChild . loc . end . line + 1
541+ : lastChild . loc . end . line ;
542+ validateClosingTag ( node , expectedStartLine ) ;
532543 }
533544 }
534545 }
0 commit comments