Skip to content

Commit 70c460a

Browse files
committed
Extract rule: template-no-unbalanced-curlies
1 parent 6a244f4 commit 70c460a

4 files changed

Lines changed: 304 additions & 0 deletions

File tree

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -406,6 +406,7 @@ rules in templates can be disabled with eslint directives with mustache or html
406406
| :------------------------------------------------------------------------------------------- | :-------------------------------------------------------- | :- | :- | :- |
407407
| [template-no-extra-mut-helper-argument](docs/rules/template-no-extra-mut-helper-argument.md) | disallow passing more than one argument to the mut helper | | | |
408408
| [template-no-jsx-attributes](docs/rules/template-no-jsx-attributes.md) | disallow JSX-style camelCase attributes | | 🔧 | |
409+
| [template-no-unbalanced-curlies](docs/rules/template-no-unbalanced-curlies.md) | disallow unbalanced mustache curlies | | | |
409410

410411
### Routes
411412

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
# ember/template-no-unbalanced-curlies
2+
3+
<!-- end auto-generated rule header -->
4+
5+
Disallow unbalanced mustache curlies in templates.
6+
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:
14+
15+
```gjs
16+
<template>
17+
{{value}
18+
</template>
19+
```
20+
21+
```gjs
22+
<template>
23+
{{{value}}
24+
</template>
25+
```
26+
27+
```gjs
28+
<template>
29+
{{#if condition}}
30+
{{value}
31+
{{/if}}
32+
</template>
33+
```
34+
35+
Examples of **correct** code for this rule:
36+
37+
```gjs
38+
<template>
39+
{{value}}
40+
</template>
41+
```
42+
43+
```gjs
44+
<template>
45+
{{#if condition}}
46+
{{value}}
47+
{{/if}}
48+
</template>
49+
```
50+
51+
```gjs
52+
<template>
53+
{{helper param1 param2}}
54+
</template>
55+
```
56+
57+
## Migration
58+
59+
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:
60+
61+
```hbs
62+
<p>This is a closing double curly: {{'}}'}}</p>
63+
<p>This is a closing triple curly: {{'}}}'}}</p>
64+
```
65+
66+
## References
67+
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)
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
/** @type {import('eslint').Rule.RuleModule} */
2+
module.exports = {
3+
meta: {
4+
type: 'problem',
5+
docs: {
6+
description: 'disallow unbalanced mustache curlies',
7+
category: 'Possible Errors',
8+
url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-no-unbalanced-curlies.md',
9+
templateMode: 'both',
10+
},
11+
schema: [],
12+
messages: {
13+
noUnbalancedCurlies:
14+
'Unbalanced mustache curlies detected. This may indicate a syntax error.',
15+
},
16+
originallyFrom: {
17+
name: 'ember-template-lint',
18+
rule: 'lib/rules/no-unbalanced-curlies.js',
19+
docs: 'docs/rule/no-unbalanced-curlies.md',
20+
tests: 'test/unit/rules/no-unbalanced-curlies-test.js',
21+
},
22+
},
23+
24+
create(context) {
25+
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+
});
43+
}
44+
},
45+
};
46+
},
47+
};
Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
//------------------------------------------------------------------------------
2+
// Requirements
3+
//------------------------------------------------------------------------------
4+
5+
const rule = require('../../../lib/rules/template-no-unbalanced-curlies');
6+
const RuleTester = require('eslint').RuleTester;
7+
8+
const ruleTester = new RuleTester({
9+
parser: require.resolve('ember-eslint-parser'),
10+
parserOptions: { ecmaVersion: 2022, sourceType: 'module' },
11+
});
12+
13+
// Note: Unbalanced curlies cause parser errors before the rule can run
14+
// This rule is designed to catch edge cases in the AST if they somehow exist
15+
// The parser itself will catch most unbalanced curly issues
16+
ruleTester.run('template-no-unbalanced-curlies', rule, {
17+
valid: [
18+
'<template>{{value}}</template>',
19+
'<template>{{#if condition}}{{value}}{{/if}}</template>',
20+
'<template>{{helper param1 param2}}</template>',
21+
'<template>{{{unescaped}}}</template>',
22+
23+
'<template>{foo}</template>',
24+
'<template>{{foo}}</template>',
25+
'<template>{{{foo}}}</template>',
26+
`<template>{{{foo
27+
}}}</template>`,
28+
'<template>\\{{foo}}</template>',
29+
'<template>\\{{foo}}\\{{foo}}</template>',
30+
'<template>\\{{foo}}{{foo}}</template>',
31+
`<template>\\{{foo
32+
}}</template>`,
33+
],
34+
invalid: [
35+
// Parser catches these before the rule runs, so no invalid cases to test
36+
37+
{
38+
code: '<template>foo}}</template>',
39+
output: null,
40+
errors: [{ messageId: 'noUnbalancedCurlies' }],
41+
},
42+
{
43+
code: '<template>{foo}}</template>',
44+
output: null,
45+
errors: [{ messageId: 'noUnbalancedCurlies' }],
46+
},
47+
{
48+
code: '<template>foo}}}</template>',
49+
output: null,
50+
errors: [{ messageId: 'noUnbalancedCurlies' }],
51+
},
52+
{
53+
code: '<template>{foo}}}</template>',
54+
output: null,
55+
errors: [{ messageId: 'noUnbalancedCurlies' }],
56+
},
57+
{
58+
code: `<template>{foo
59+
}}}</template>`,
60+
output: null,
61+
errors: [{ messageId: 'noUnbalancedCurlies' }],
62+
},
63+
{
64+
code: `<template>{foo
65+
}}}
66+
bar</template>`,
67+
output: null,
68+
errors: [{ messageId: 'noUnbalancedCurlies' }],
69+
},
70+
{
71+
code: `<template>{foor
72+
barr
73+
r
74+
baz}}}</template>`,
75+
output: null,
76+
errors: [{ messageId: 'noUnbalancedCurlies' }],
77+
},
78+
{
79+
code: '<template>{foorbarrrbaz}}}</template>',
80+
output: null,
81+
errors: [{ messageId: 'noUnbalancedCurlies' }],
82+
},
83+
{
84+
code: '<template>{foo\r\nbar\r\n\r\nbaz}}}</template>',
85+
output: null,
86+
errors: [{ messageId: 'noUnbalancedCurlies' }],
87+
},
88+
{
89+
code: '<template>{foo\rbar\r\rbaz}}}</template>',
90+
output: null,
91+
errors: [{ messageId: 'noUnbalancedCurlies' }],
92+
},
93+
],
94+
});
95+
96+
const hbsRuleTester = new RuleTester({
97+
parser: require.resolve('ember-eslint-parser/hbs'),
98+
parserOptions: {
99+
ecmaVersion: 2022,
100+
sourceType: 'module',
101+
},
102+
});
103+
104+
hbsRuleTester.run('template-no-unbalanced-curlies', rule, {
105+
valid: [
106+
'{foo}',
107+
'{{foo}}',
108+
'{{{foo}}}',
109+
`{{{foo
110+
}}}`,
111+
'\\{{foo}}',
112+
'\\{{foo}}\\{{foo}}',
113+
'\\{{foo}}{{foo}}',
114+
`\\{{foo
115+
}}`,
116+
],
117+
invalid: [
118+
{
119+
code: 'foo}}',
120+
output: null,
121+
errors: [
122+
{ message: 'Unbalanced mustache curlies detected. This may indicate a syntax error.' },
123+
],
124+
},
125+
{
126+
code: '{foo}}',
127+
output: null,
128+
errors: [
129+
{ message: 'Unbalanced mustache curlies detected. This may indicate a syntax error.' },
130+
],
131+
},
132+
{
133+
code: 'foo}}}',
134+
output: null,
135+
errors: [
136+
{ message: 'Unbalanced mustache curlies detected. This may indicate a syntax error.' },
137+
],
138+
},
139+
{
140+
code: '{foo}}}',
141+
output: null,
142+
errors: [
143+
{ message: 'Unbalanced mustache curlies detected. This may indicate a syntax error.' },
144+
],
145+
},
146+
{
147+
code: `{foo
148+
}}}`,
149+
output: null,
150+
errors: [
151+
{ message: 'Unbalanced mustache curlies detected. This may indicate a syntax error.' },
152+
],
153+
},
154+
{
155+
code: `{foo
156+
}}}
157+
bar`,
158+
output: null,
159+
errors: [
160+
{ message: 'Unbalanced mustache curlies detected. This may indicate a syntax error.' },
161+
],
162+
},
163+
{
164+
code: `{foo
165+
bar
166+
167+
baz}}}`,
168+
output: null,
169+
errors: [
170+
{ message: 'Unbalanced mustache curlies detected. This may indicate a syntax error.' },
171+
],
172+
},
173+
{
174+
code: '{foo\r\nbar\r\n\r\nbaz}}}',
175+
output: null,
176+
errors: [
177+
{ message: 'Unbalanced mustache curlies detected. This may indicate a syntax error.' },
178+
],
179+
},
180+
{
181+
code: '{foo\rbar\r\rbaz}}}',
182+
output: null,
183+
errors: [
184+
{ message: 'Unbalanced mustache curlies detected. This may indicate a syntax error.' },
185+
],
186+
},
187+
],
188+
});

0 commit comments

Comments
 (0)