Skip to content

Commit 87775b8

Browse files
Merge pull request #2537 from johanrd/autofix-complex/no-curly-component-invocation
Restore autofix: `template-no-curly-component-invocation`
2 parents 2daf6f0 + 6efa3e1 commit 87775b8

4 files changed

Lines changed: 51 additions & 15 deletions

File tree

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -215,7 +215,7 @@ rules in templates can be disabled with eslint directives with mustache or html
215215
| [template-no-capital-arguments](docs/rules/template-no-capital-arguments.md) | disallow capital arguments (use lowercase @arg instead of @Arg) | | | |
216216
| [template-no-chained-this](docs/rules/template-no-chained-this.md) | disallow redundant `this.this` in templates | | 🔧 | |
217217
| [template-no-class-bindings](docs/rules/template-no-class-bindings.md) | disallow passing classBinding or classNameBindings as arguments in templates | | | |
218-
| [template-no-curly-component-invocation](docs/rules/template-no-curly-component-invocation.md) | disallow curly component invocation, use angle bracket syntax instead | | | |
218+
| [template-no-curly-component-invocation](docs/rules/template-no-curly-component-invocation.md) | disallow curly component invocation, use angle bracket syntax instead | | 🔧 | |
219219
| [template-no-debugger](docs/rules/template-no-debugger.md) | disallow {{debugger}} in templates | | | |
220220
| [template-no-duplicate-attributes](docs/rules/template-no-duplicate-attributes.md) | disallow duplicate attribute names in templates | | 🔧 | |
221221
| [template-no-duplicate-id](docs/rules/template-no-duplicate-id.md) | disallow duplicate id attributes | | | |

docs/rules/template-no-curly-component-invocation.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
# ember/template-no-curly-component-invocation
22

3+
🔧 This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix).
4+
35
> **HBS Only**: This rule applies to classic `.hbs` template files only (loose mode). It is not relevant for `gjs`/`gts` files (strict mode), where these patterns cannot occur.
46
57
<!-- end auto-generated rule header -->

lib/rules/template-no-curly-component-invocation.js

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ module.exports = {
8383
url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-no-curly-component-invocation.md',
8484
templateMode: 'loose',
8585
},
86-
fixable: null,
86+
fixable: 'code',
8787
schema: [
8888
{
8989
type: 'object',
@@ -117,6 +117,39 @@ module.exports = {
117117

118118
create(context) {
119119
const config = parseConfig(context.options[0]);
120+
const sourceCode = context.sourceCode ?? context.getSourceCode();
121+
122+
/**
123+
* Build a fix function for block statement curly→angle bracket conversion.
124+
* Matches ember-template-lint's fixForBlockNode behavior.
125+
*/
126+
function buildBlockFix(node, angleBracketName) {
127+
return function fix(fixer) {
128+
// Convert hash pairs to @key attributes
129+
const attrs = (node.hash?.pairs ?? []).map((pair) => {
130+
const valueText = sourceCode.getText(pair.value);
131+
if (pair.value.type === 'GlimmerStringLiteral') {
132+
return `@${pair.key}="${pair.value.value}"`;
133+
}
134+
return `@${pair.key}={{${valueText}}}`;
135+
});
136+
137+
// Get block params
138+
const blockParams = node.program?.blockParams ?? [];
139+
const blockParamsStr = blockParams.length > 0 ? ` as |${blockParams.join(' ')}|` : '';
140+
141+
// Get body content
142+
const bodyText = node.program?.body
143+
? node.program.body.map((n) => sourceCode.getText(n)).join('')
144+
: '';
145+
146+
const attrStr = attrs.length > 0 ? ` ${attrs.join(' ')}` : '';
147+
return fixer.replaceText(
148+
node,
149+
`<${angleBracketName}${attrStr}${blockParamsStr}>${bodyText}</${angleBracketName}>`
150+
);
151+
};
152+
}
120153

121154
// Stack of block-param name arrays, one entry per active GlimmerBlockStatement.
122155
const blockParamStack = [];
@@ -282,6 +315,7 @@ module.exports = {
282315
context.report({
283316
node,
284317
message: `You are using the component {{#${pathOriginal}}} with curly component syntax. You should use <${angleBracketName}> instead. If it is actually a helper you must manually add it to the 'no-curly-component-invocation' rule configuration, e.g. \`'no-curly-component-invocation': { allow: ['${pathOriginal}'] }\`.`,
318+
fix: buildBlockFix(node, angleBracketName),
285319
});
286320
},
287321

tests/lib/rules/template-no-curly-component-invocation.js

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ ruleTester.run('template-no-curly-component-invocation', rule, {
6666
},
6767
{
6868
code: '<template>{{#foo-bar}}content{{/foo-bar}}</template>',
69-
output: null,
69+
output: '<template><FooBar>content</FooBar></template>',
7070
errors: [
7171
{
7272
message:
@@ -76,7 +76,7 @@ ruleTester.run('template-no-curly-component-invocation', rule, {
7676
},
7777
{
7878
code: '<template>{{#foo-bar}}{{/foo-bar}}</template>',
79-
output: null,
79+
output: '<template><FooBar></FooBar></template>',
8080
errors: [
8181
{
8282
message:
@@ -86,7 +86,7 @@ ruleTester.run('template-no-curly-component-invocation', rule, {
8686
},
8787
{
8888
code: '<template>{{#foo-bar/baz/boo-foo}}block{{/foo-bar/baz/boo-foo}}</template>',
89-
output: null,
89+
output: '<template><FooBar::Baz::BooFoo>block</FooBar::Baz::BooFoo></template>',
9090
errors: [
9191
{
9292
message:
@@ -96,7 +96,7 @@ ruleTester.run('template-no-curly-component-invocation', rule, {
9696
},
9797
{
9898
code: '<template>{{#some-component foo="bar"}}foo{{/some-component}}</template>',
99-
output: null,
99+
output: '<template><SomeComponent @foo="bar">foo</SomeComponent></template>',
100100
errors: [
101101
{
102102
message:
@@ -296,17 +296,17 @@ hbsRuleTester.run('template-no-curly-component-invocation', rule, {
296296
},
297297
{
298298
code: '{{#foo-bar}}{{/foo-bar}}',
299-
output: null,
299+
output: '<FooBar></FooBar>',
300300
errors: [{ message: generateBlockError('foo-bar') }],
301301
},
302302
{
303303
code: '{{#foo-bar as |foo-baz|}}{{foo-baz}}{{/foo-bar}}',
304-
output: null,
304+
output: '<FooBar as |foo-baz|>{{foo-baz}}</FooBar>',
305305
errors: [{ message: generateBlockError('foo-bar') }, { message: generateError('foo-baz') }],
306306
},
307307
{
308308
code: '{{#foo-bar as |foo-baz|}}{{#foo-baz as |foo-boo|}}{{foo-boo}}{{/foo-baz}}{{/foo-bar}}',
309-
output: null,
309+
output: '<FooBar as |foo-baz|>{{#foo-baz as |foo-boo|}}{{foo-boo}}{{/foo-baz}}</FooBar>',
310310
errors: [
311311
{ message: generateBlockError('foo-bar') },
312312
{ message: generateBlockError('foo-baz') },
@@ -315,41 +315,41 @@ hbsRuleTester.run('template-no-curly-component-invocation', rule, {
315315
},
316316
{
317317
code: '{{#foo-bar as |foo-baz|}}{{foos-baz}}{{/foo-bar}}',
318-
output: null,
318+
output: '<FooBar as |foo-baz|>{{foos-baz}}</FooBar>',
319319
errors: [{ message: generateBlockError('foo-bar') }, { message: generateError('foos-baz') }],
320320
},
321321
{
322322
code: '{{#this.foo-bar as |foo-baz|}}{{foos-baz}}{{/this.foo-bar}}',
323-
output: null,
323+
output: '<This.fooBar as |foo-baz|>{{foos-baz}}</This.fooBar>',
324324
errors: [
325325
{ message: generateThisBlockError('this.foo-bar') },
326326
{ message: generateError('foos-baz') },
327327
],
328328
},
329329
{
330330
code: '{{#this.fooBar as |foo-baz|}}{{foos-baz}}{{/this.fooBar}}',
331-
output: null,
331+
output: '<This.fooBar as |foo-baz|>{{foos-baz}}</This.fooBar>',
332332
errors: [
333333
{ message: generateBlockError('this.fooBar') },
334334
{ message: generateError('foos-baz') },
335335
],
336336
},
337337
{
338338
code: '{{#@foo-bar as |foo-baz|}}{{foos-baz}}{{/@foo-bar}}',
339-
output: null,
339+
output: '<@fooBar as |foo-baz|>{{foos-baz}}</@fooBar>',
340340
errors: [
341341
{ message: generateThisBlockError('@foo-bar') },
342342
{ message: generateError('foos-baz') },
343343
],
344344
},
345345
{
346346
code: '{{#@fooBar as |foo-baz|}}{{foos-baz}}{{/@fooBar}}',
347-
output: null,
347+
output: '<@fooBar as |foo-baz|>{{foos-baz}}</@fooBar>',
348348
errors: [{ message: generateBlockError('@fooBar') }, { message: generateError('foos-baz') }],
349349
},
350350
{
351351
code: '{{#let (component "foo") as |my-component|}}{{#my-component}}{{/my-component}}{{/let}}',
352-
output: null,
352+
output: '{{#let (component "foo") as |my-component|}}<MyComponent></MyComponent>{{/let}}',
353353
errors: [{ message: generateBlockError('my-component') }],
354354
},
355355
// Curly component invocations with hash params

0 commit comments

Comments
 (0)