Skip to content

Commit b0ce57a

Browse files
committed
Sync with template-lint
1 parent 70c460a commit b0ce57a

3 files changed

Lines changed: 157 additions & 219 deletions

File tree

docs/rules/template-no-unbalanced-curlies.md

Lines changed: 26 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -2,67 +2,58 @@
22

33
<!-- end auto-generated rule header -->
44

5-
Disallow unbalanced mustache curlies in templates.
5+
Normally, the compiler will find stray curlies and throw a syntax error. However, it won't be able to catch every case.
66

7-
## Rule Details
8-
9-
This rule detects unbalanced opening `{{` and closing `}}` mustache braces in templates, which typically indicates a syntax error or typo.
10-
11-
## Examples
12-
13-
Examples of **incorrect** code for this rule:
7+
For example, these are all syntax errors:
148

159
```gjs
1610
<template>
17-
{{value}
11+
{{ x }
12+
{{ x }}}
13+
{{{ x }
14+
{{{ x }}
1815
</template>
1916
```
2017

21-
```gjs
22-
<template>
23-
{{{value}}
24-
</template>
25-
```
18+
Whereas these are not:
2619

2720
```gjs
2821
<template>
29-
{{#if condition}}
30-
{{value}
31-
{{/if}}
22+
{ x }}
23+
{ x }
24+
}
25+
}}
26+
}}}
27+
}}}}... (any number of closing curlies past one)
3228
</template>
3329
```
3430

35-
Examples of **correct** code for this rule:
31+
This rule focuses on closing double `}}` and triple `}}}` curlies with no matching opening curlies.
3632

37-
```gjs
38-
<template>
39-
{{value}}
40-
</template>
41-
```
33+
## Examples
4234

43-
```gjs
44-
<template>
45-
{{#if condition}}
46-
{{value}}
47-
{{/if}}
48-
</template>
49-
```
35+
This rule **forbids** the following:
5036

5137
```gjs
5238
<template>
53-
{{helper param1 param2}}
39+
foo}}
40+
{foo}}
41+
foo}}}
42+
{foo}}}
5443
</template>
5544
```
5645

5746
## Migration
5847

5948
If you have curlies in your code that you wish to show verbatim, but are flagged by this rule, you can formulate them as a handlebars expression:
6049

61-
```hbs
62-
<p>This is a closing double curly: {{'}}'}}</p>
63-
<p>This is a closing triple curly: {{'}}}'}}</p>
50+
```gjs
51+
<template>
52+
<p>This is a closing double curly: {{ '}}' }}</p>
53+
<p>This is a closing triple curly: {{ '}}}' }}</p>
54+
</template>
6455
```
6556

6657
## References
6758

68-
- [eslint-plugin-ember template-no-unbalanced-curlies](https://github.com/ember-cli/eslint-plugin-ember/blob/master/docs/rules/template-no-unbalanced-curlies.md)
59+
- [Handlebars docs/expressions](https://handlebarsjs.com/guide/expressions.html)

lib/rules/template-no-unbalanced-curlies.js

Lines changed: 53 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
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} */
27
module.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

Comments
 (0)