Skip to content

Commit d1299c4

Browse files
committed
Extract rule: template-no-unnecessary-curly-parens
1 parent 58a690f commit d1299c4

4 files changed

Lines changed: 226 additions & 0 deletions

File tree

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -256,6 +256,7 @@ rules in templates can be disabled with eslint directives with mustache or html
256256
| [template-no-obsolete-elements](docs/rules/template-no-obsolete-elements.md) | disallow obsolete HTML elements | | | |
257257
| [template-no-outlet-outside-routes](docs/rules/template-no-outlet-outside-routes.md) | disallow {{outlet}} outside of route templates | | | |
258258
| [template-no-page-title-component](docs/rules/template-no-page-title-component.md) | disallow usage of ember-page-title component | | | |
259+
| [template-no-unnecessary-curly-parens](docs/rules/template-no-unnecessary-curly-parens.md) | disallow unnecessary parentheses enclosing statements in curlies | | 🔧 | |
259260
| [template-no-unused-block-params](docs/rules/template-no-unused-block-params.md) | disallow unused block parameters in templates | | | |
260261
| [template-no-valueless-arguments](docs/rules/template-no-valueless-arguments.md) | disallow valueless named arguments | | | |
261262
| [template-no-whitespace-for-layout](docs/rules/template-no-whitespace-for-layout.md) | disallow using whitespace for layout purposes | | | |
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
# ember/template-no-unnecessary-curly-parens
2+
3+
🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix).
4+
5+
<!-- end auto-generated rule header -->
6+
7+
Disallow unnecessary parentheses enclosing statements in curlies. When invoking a helper with arguments, the outer parentheses around the entire expression are unnecessary.
8+
9+
## Rule Details
10+
11+
This rule flags `{{(helper args)}}` where the parentheses around the helper call can be removed, becoming `{{helper args}}`.
12+
13+
## Examples
14+
15+
Examples of **incorrect** code for this rule:
16+
17+
```gjs
18+
<template>
19+
{{(concat "a" "b")}}
20+
</template>
21+
```
22+
23+
```gjs
24+
<template>
25+
{{(helper a="b")}}
26+
</template>
27+
```
28+
29+
Examples of **correct** code for this rule:
30+
31+
```gjs
32+
<template>
33+
{{concat "a" "b"}}
34+
</template>
35+
```
36+
37+
```gjs
38+
<template>
39+
{{(foo)}}
40+
</template>
41+
```
42+
43+
```gjs
44+
<template>
45+
{{this.property}}
46+
</template>
47+
```
48+
49+
## References
50+
51+
- [eslint-plugin-ember template-no-unnecessary-curly-parens](https://github.com/ember-cli/eslint-plugin-ember/blob/master/docs/rules/template-no-unnecessary-curly-parens.md)
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
function isFixableMustache(node) {
2+
// Check if the mustache's "path" is actually a SubExpression with params or hash
3+
// e.g., {{(helper arg)}} where the path is (helper arg)
4+
return (
5+
node.path?.type === 'GlimmerSubExpression' &&
6+
((node.path.params && node.path.params.length > 0) ||
7+
(node.path.hash?.pairs && node.path.hash.pairs.length > 0))
8+
);
9+
}
10+
11+
/** @type {import('eslint').Rule.RuleModule} */
12+
module.exports = {
13+
meta: {
14+
type: 'suggestion',
15+
docs: {
16+
description: 'disallow unnecessary parentheses enclosing statements in curlies',
17+
category: 'Best Practices',
18+
url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-no-unnecessary-curly-parens.md',
19+
templateMode: 'both',
20+
},
21+
fixable: 'code',
22+
schema: [],
23+
messages: {
24+
noUnnecessaryCurlyParens: 'Unnecessary parentheses enclosing statement.',
25+
},
26+
originallyFrom: {
27+
name: 'ember-template-lint',
28+
rule: 'lib/rules/no-unnecessary-curly-parens.js',
29+
docs: 'docs/rule/no-unnecessary-curly-parens.md',
30+
tests: 'test/unit/rules/no-unnecessary-curly-parens-test.js',
31+
},
32+
},
33+
34+
create(context) {
35+
const sourceCode = context.sourceCode || context.getSourceCode();
36+
37+
return {
38+
GlimmerMustacheStatement(node) {
39+
if (isFixableMustache(node)) {
40+
const subExpr = node.path;
41+
context.report({
42+
node,
43+
messageId: 'noUnnecessaryCurlyParens',
44+
fix(fixer) {
45+
// Replace {{(helper params hash)}} with {{helper params hash}}
46+
const helperName = subExpr.path?.original || '';
47+
let replacement = `{{${helperName}`;
48+
49+
for (const param of subExpr.params || []) {
50+
replacement += ` ${sourceCode.getText(param)}`;
51+
}
52+
for (const pair of subExpr.hash?.pairs || []) {
53+
replacement += ` ${sourceCode.getText(pair)}`;
54+
}
55+
replacement += '}}';
56+
57+
return fixer.replaceText(node, replacement);
58+
},
59+
});
60+
}
61+
},
62+
};
63+
},
64+
};
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
//------------------------------------------------------------------------------
2+
// Requirements
3+
//------------------------------------------------------------------------------
4+
5+
const rule = require('../../../lib/rules/template-no-unnecessary-curly-parens');
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+
ruleTester.run('template-no-unnecessary-curly-parens', rule, {
14+
valid: [
15+
'<template>{{helper param}}</template>',
16+
'<template>{{#if condition}}text{{/if}}</template>',
17+
'<template>{{this.property}}</template>',
18+
19+
'<template>{{foo}}</template>',
20+
'<template>{{this.foo}}</template>',
21+
'<template>{{(helper)}}</template>',
22+
'<template>{{(this.helper)}}</template>',
23+
'<template>{{concat "a" "b"}}</template>',
24+
'<template>{{concat (capitalize "foo") "-bar"}}</template>',
25+
],
26+
invalid: [
27+
{
28+
code: '<template>{{(helper value)}}</template>',
29+
output: '<template>{{helper value}}</template>',
30+
errors: [{ messageId: 'noUnnecessaryCurlyParens' }],
31+
},
32+
{
33+
code: '<template>{{(concat "a" "b")}}</template>',
34+
output: '<template>{{concat "a" "b"}}</template>',
35+
errors: [{ messageId: 'noUnnecessaryCurlyParens' }],
36+
},
37+
{
38+
code: '<template>{{(if condition "yes" "no")}}</template>',
39+
output: '<template>{{if condition "yes" "no"}}</template>',
40+
errors: [{ messageId: 'noUnnecessaryCurlyParens' }],
41+
},
42+
43+
{
44+
code: '<template><FooBar @x="{{index}}X{{(someHelper foo)}}" /></template>',
45+
output: '<template><FooBar @x="{{index}}X{{someHelper foo}}" /></template>',
46+
errors: [{ messageId: 'noUnnecessaryCurlyParens' }],
47+
},
48+
{
49+
code: '<template><FooBar @x="{{index}}XY{{(someHelper foo)}}" /></template>',
50+
output: '<template><FooBar @x="{{index}}XY{{someHelper foo}}" /></template>',
51+
errors: [{ messageId: 'noUnnecessaryCurlyParens' }],
52+
},
53+
{
54+
code: '<template><FooBar @x="{{index}}--{{(someHelper foo)}}" /></template>',
55+
output: '<template><FooBar @x="{{index}}--{{someHelper foo}}" /></template>',
56+
errors: [{ messageId: 'noUnnecessaryCurlyParens' }],
57+
},
58+
{
59+
code: '<template>{{(helper a="b")}}</template>',
60+
output: '<template>{{helper a="b"}}</template>',
61+
errors: [{ messageId: 'noUnnecessaryCurlyParens' }],
62+
},
63+
],
64+
});
65+
66+
const hbsRuleTester = new RuleTester({
67+
parser: require.resolve('ember-eslint-parser/hbs'),
68+
parserOptions: {
69+
ecmaVersion: 2022,
70+
sourceType: 'module',
71+
},
72+
});
73+
74+
hbsRuleTester.run('template-no-unnecessary-curly-parens', rule, {
75+
valid: [
76+
'{{foo}}',
77+
'{{this.foo}}',
78+
'{{(helper)}}',
79+
'{{(this.helper)}}',
80+
'{{concat "a" "b"}}',
81+
'{{concat (capitalize "foo") "-bar"}}',
82+
],
83+
invalid: [
84+
{
85+
code: '<FooBar @x="{{index}}X{{(someHelper foo)}}" />',
86+
output: '<FooBar @x="{{index}}X{{someHelper foo}}" />',
87+
errors: [{ message: 'Unnecessary parentheses enclosing statement.' }],
88+
},
89+
{
90+
code: '<FooBar @x="{{index}}XY{{(someHelper foo)}}" />',
91+
output: '<FooBar @x="{{index}}XY{{someHelper foo}}" />',
92+
errors: [{ message: 'Unnecessary parentheses enclosing statement.' }],
93+
},
94+
{
95+
code: '<FooBar @x="{{index}}--{{(someHelper foo)}}" />',
96+
output: '<FooBar @x="{{index}}--{{someHelper foo}}" />',
97+
errors: [{ message: 'Unnecessary parentheses enclosing statement.' }],
98+
},
99+
{
100+
code: '{{(concat "a" "b")}}',
101+
output: '{{concat "a" "b"}}',
102+
errors: [{ message: 'Unnecessary parentheses enclosing statement.' }],
103+
},
104+
{
105+
code: '{{(helper a="b")}}',
106+
output: '{{helper a="b"}}',
107+
errors: [{ message: 'Unnecessary parentheses enclosing statement.' }],
108+
},
109+
],
110+
});

0 commit comments

Comments
 (0)