1+ const hbsParser = require ( 'ember-eslint-parser/hbs' ) ;
2+
3+ const SUSPECT_CHARS = '}}' ;
4+ const reLines = / ( .* ?(?: \r \n ? | \n | $ ) ) / gm;
5+
16/** @type {import('eslint').Rule.RuleModule } */
27module . exports = {
38 meta : {
@@ -10,8 +15,7 @@ module.exports = {
1015 } ,
1116 schema : [ ] ,
1217 messages : {
13- noUnbalancedCurlies :
14- 'Unbalanced mustache curlies detected. This may indicate a syntax error.' ,
18+ noUnbalancedCurlies : 'Unbalanced curlies detected' ,
1519 } ,
1620 originallyFrom : {
1721 name : 'ember-template-lint' ,
@@ -22,24 +26,54 @@ module.exports = {
2226 } ,
2327
2428 create ( context ) {
29+ const sourceCode = context . sourceCode || context . getSourceCode ( ) ;
30+
2531 return {
26- GlimmerTemplate ( node ) {
27- const sourceCode = context . sourceCode || context . getSourceCode ( ) ;
28- const text = sourceCode . getText ( node ) ;
29-
30- // Count opening and closing curlies
31- // Note: The parser typically catches unbalanced curlies before rules run
32- // This serves as a safety check for edge cases
33- // eslint-disable-next-line unicorn/better-regex -- need to escape braces
34- const openingCount = ( text . match ( / \{ \{ / g) || [ ] ) . length ;
35- // eslint-disable-next-line unicorn/better-regex -- need to escape braces
36- const closingCount = ( text . match ( / \} \} / g) || [ ] ) . length ;
37-
38- if ( openingCount !== closingCount ) {
39- context . report ( {
40- node,
41- messageId : 'noUnbalancedCurlies' ,
42- } ) ;
32+ GlimmerTextNode ( node ) {
33+ const chars = node . chars ;
34+
35+ if ( ! chars . includes ( SUSPECT_CHARS ) ) {
36+ return ;
37+ }
38+
39+ let isMustache = false ;
40+
41+ try {
42+ const ast = hbsParser . parseForESLint ( chars ) . ast ;
43+ const body = ast . body ?. [ 0 ] ?. body ?? ast . body ?? [ ] ;
44+ isMustache = body . length > 0 && body [ 0 ] . type === 'GlimmerMustacheStatement' ;
45+ } catch {
46+ // Not a valid standalone mustache; continue checking for stray closing curlies.
47+ }
48+
49+ if ( isMustache ) {
50+ return ;
51+ }
52+
53+ let lineNum = node . loc . start . line ;
54+ let colNum = node . loc . start . column ;
55+ const source = sourceCode . getText ( node ) ;
56+ const lines = chars . match ( reLines ) || [ ] ;
57+
58+ for ( const line of lines ) {
59+ const suspectIndex = line . indexOf ( SUSPECT_CHARS ) ;
60+
61+ if ( suspectIndex !== - 1 ) {
62+ const length = line . slice ( suspectIndex ) . startsWith ( '}}}' ) ? 3 : 2 ;
63+
64+ context . report ( {
65+ node,
66+ messageId : 'noUnbalancedCurlies' ,
67+ loc : {
68+ start : { line : lineNum , column : colNum + suspectIndex } ,
69+ end : { line : lineNum , column : colNum + suspectIndex + length } ,
70+ } ,
71+ source,
72+ } ) ;
73+ }
74+
75+ lineNum ++ ;
76+ colNum = 1 ;
4377 }
4478 } ,
4579 } ;
0 commit comments