Skip to content

Commit 983b592

Browse files
committed
Sync with template-lint
1 parent d25c2d3 commit 983b592

3 files changed

Lines changed: 155 additions & 179 deletions

File tree

docs/rules/template-no-unnecessary-component-helper.md

Lines changed: 32 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -6,30 +6,47 @@
66
77
<!-- end auto-generated rule header -->
88

9-
Disallow unnecessary usage of the `{{component}}` helper with static component names.
9+
The `component` template helper can be used to dynamically pick the component being rendered based on the provided property. But if the component name is passed as a string because it's already known, then the component should be invoked directly, instead of using the `component` helper.
1010

11-
## Rule Details
11+
## Examples
1212

13-
This rule disallows using `{{component "component-name"}}` when you could use angle bracket invocation instead.
13+
This rule **forbids** the following:
1414

15-
## Examples
15+
```gjs
16+
<template>
17+
{{component "my-component"}}
18+
</template>
19+
```
1620

17-
Examples of **incorrect** code for this rule:
21+
This rule **allows** the following:
1822

19-
```hbs
20-
{{component 'my-component'}}
21-
{{component 'MyComponent' arg='value'}}
23+
```gjs
24+
<template>
25+
{{component SOME_COMPONENT_NAME}}
26+
</template>
2227
```
2328

24-
Examples of **correct** code for this rule:
29+
```gjs
30+
<template>
31+
{{!-- the `component` helper is needed to invoke this --}}
32+
{{component "addon-name@component-name"}}
33+
</template>
34+
```
35+
36+
```gjs
37+
<template>
38+
{{my-component}}
39+
</template>
40+
```
2541

26-
```hbs
27-
<MyComponent />
28-
{{component this.dynamicComponentName}}
29-
{{component @componentName}}
42+
```gjs
43+
<template>
44+
{{my-component close=(component "link-to" "index")}}
45+
<MyComponent @close={{component "link-to" "index"}} />
46+
</template>
3047
```
3148

3249
## References
3350

34-
- [Ember Guides - Components](https://guides.emberjs.com/release/components/)
35-
- [RFC #311 - Angle Bracket Invocation](https://github.com/emberjs/rfcs/blob/master/text/0311-angle-bracket-invocation.md)
51+
- [component helper guide](https://guides.emberjs.com/release/components/defining-a-component/#toc_dynamically-rendering-a-component)
52+
- [component helper spec](https://www.emberjs.com/api/ember/release/classes/Ember.Templates.helpers/methods/component?anchor=component)

lib/rules/template-no-unnecessary-component-helper.js

Lines changed: 37 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,30 @@ function isComponentWithStringLiteral(node) {
1010
);
1111
}
1212

13+
function getComponentInvocationText(sourceCode, node, componentName) {
14+
const parts = [];
15+
16+
for (const param of node.params.slice(1)) {
17+
parts.push(sourceCode.getText(param));
18+
}
19+
20+
for (const pair of node.hash?.pairs || []) {
21+
parts.push(sourceCode.getText(pair));
22+
}
23+
24+
return [componentName, ...parts].join(' ');
25+
}
26+
27+
function getOpenInvocationEnd(node) {
28+
if (node.hash?.pairs?.length) {
29+
return node.hash.range[1];
30+
}
31+
32+
const lastParam = node.params.at(-1);
33+
34+
return lastParam ? lastParam.range[1] : node.path.range[1];
35+
}
36+
1337
/** @type {import('eslint').Rule.RuleModule} */
1438
module.exports = {
1539
meta: {
@@ -23,8 +47,7 @@ module.exports = {
2347
fixable: 'code',
2448
schema: [],
2549
messages: {
26-
noUnnecessaryComponent:
27-
'Unnecessary use of (component) helper. Use the component name directly.',
50+
noUnnecessaryComponent: 'Invoke component directly instead of using `component` helper',
2851
},
2952
originallyFrom: {
3053
name: 'ember-template-lint',
@@ -55,23 +78,12 @@ module.exports = {
5578
}
5679

5780
const componentName = node.params[0].value || node.params[0].original;
81+
const invocation = getComponentInvocationText(sourceCode, node, componentName);
5882
context.report({
5983
node,
6084
messageId: 'noUnnecessaryComponent',
6185
fix(fixer) {
62-
const restParams = node.params.slice(1);
63-
const hashPairs = node.hash?.pairs || [];
64-
65-
let replacement = `{{${componentName}`;
66-
for (const param of restParams) {
67-
replacement += ` ${sourceCode.getText(param)}`;
68-
}
69-
for (const pair of hashPairs) {
70-
replacement += ` ${sourceCode.getText(pair)}`;
71-
}
72-
replacement += '}}';
73-
74-
return fixer.replaceText(node, replacement);
86+
return fixer.replaceText(node, `{{${invocation}}}`);
7587
},
7688
});
7789
},
@@ -84,38 +96,21 @@ module.exports = {
8496
return;
8597
}
8698

87-
context.report({
88-
node,
89-
messageId: 'noUnnecessaryComponent',
90-
});
91-
},
92-
93-
GlimmerSubExpression(node) {
94-
if (inAttribute > 0) {
95-
return;
96-
}
97-
if (!isComponentWithStringLiteral(node)) {
98-
return;
99-
}
100-
10199
const componentName = node.params[0].value || node.params[0].original;
100+
const invocation = getComponentInvocationText(sourceCode, node, componentName);
101+
102102
context.report({
103103
node,
104104
messageId: 'noUnnecessaryComponent',
105105
fix(fixer) {
106-
const restParams = node.params.slice(1);
107-
const hashPairs = node.hash?.pairs || [];
108-
109-
let replacement = `(${componentName}`;
110-
for (const param of restParams) {
111-
replacement += ` ${sourceCode.getText(param)}`;
112-
}
113-
for (const pair of hashPairs) {
114-
replacement += ` ${sourceCode.getText(pair)}`;
115-
}
116-
replacement += ')';
117-
118-
return fixer.replaceText(node, replacement);
106+
const openInvocationEnd = getOpenInvocationEnd(node);
107+
const closingPathEnd = node.range[1] - 2;
108+
const closingPathStart = closingPathEnd - node.path.original.length;
109+
110+
return [
111+
fixer.replaceTextRange([node.path.range[0], openInvocationEnd], invocation),
112+
fixer.replaceTextRange([closingPathStart, closingPathEnd], componentName),
113+
];
119114
},
120115
});
121116
},

0 commit comments

Comments
 (0)