Skip to content

Commit e2c5386

Browse files
committed
Extract rule: template-no-chained-this
1 parent 3e664f5 commit e2c5386

4 files changed

Lines changed: 152 additions & 0 deletions

File tree

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,7 @@ rules in templates can be disabled with eslint directives with mustache or html
195195
| [template-no-action-modifiers](docs/rules/template-no-action-modifiers.md) | disallow usage of {{action}} modifiers | | | |
196196
| [template-no-arguments-for-html-elements](docs/rules/template-no-arguments-for-html-elements.md) | disallow @arguments on HTML elements | | | |
197197
| [template-no-array-prototype-extensions](docs/rules/template-no-array-prototype-extensions.md) | disallow usage of Ember Array prototype extensions | | | |
198+
| [template-no-chained-this](docs/rules/template-no-chained-this.md) | disallow redundant `this.this` in templates | | 🔧 | |
198199
| [template-no-debugger](docs/rules/template-no-debugger.md) | disallow {{debugger}} in templates | | | |
199200
| [template-no-log](docs/rules/template-no-log.md) | disallow {{log}} in templates | | | |
200201

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
# ember/template-no-chained-this
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 chained property access on `this`.
8+
9+
Accessing deeply nested properties through `this` (like `this.user.name`) in templates makes components harder to refactor and test. It also creates tight coupling between the template and the component's internal structure. Use local variables or computed properties instead.
10+
11+
## Rule Details
12+
13+
This rule disallows chaining property access on `this` in templates (e.g., `this.foo.bar`).
14+
15+
## Examples
16+
17+
### Incorrect ❌
18+
19+
```gjs
20+
<template>
21+
{{this.user.name}}
22+
</template>
23+
```
24+
25+
```gjs
26+
<template>
27+
{{this.model.user.firstName}}
28+
</template>
29+
```
30+
31+
```gjs
32+
<template>
33+
<div>{{this.data.items.length}}</div>
34+
</template>
35+
```
36+
37+
### Correct ✅
38+
39+
```gjs
40+
<template>
41+
{{this.userName}}
42+
</template>
43+
```
44+
45+
```gjs
46+
<template>
47+
{{get this.user "name"}}
48+
</template>
49+
```
50+
51+
```gjs
52+
<template>
53+
{{userName}}
54+
</template>
55+
```
56+
57+
## Related Rules
58+
59+
- [template-no-implicit-this](./template-no-implicit-this.md)
60+
61+
## References
62+
63+
- [Ember Best Practices - Component Design](https://guides.emberjs.com/release/components/)
64+
- [eslint-plugin-ember template-no-this-in-template-only-components](https://github.com/ember-cli/eslint-plugin-ember/blob/master/docs/rules/template-no-this-in-template-only-components.md)
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
/** @type {import('eslint').Rule.RuleModule} */
2+
module.exports = {
3+
meta: {
4+
type: 'problem',
5+
docs: {
6+
description: 'disallow redundant `this.this` in templates',
7+
category: 'Best Practices',
8+
strictGjs: true,
9+
strictGts: true,
10+
url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-no-chained-this.md',
11+
},
12+
fixable: 'code',
13+
schema: [],
14+
messages: {
15+
noChainedThis:
16+
'this.this.* is not allowed in templates. This is likely a mistake — remove the redundant this.',
17+
},
18+
},
19+
20+
create(context) {
21+
const sourceCode = context.sourceCode;
22+
23+
return {
24+
GlimmerPathExpression(node) {
25+
if (node.original?.startsWith('this.this.')) {
26+
context.report({
27+
node,
28+
messageId: 'noChainedThis',
29+
fix(fixer) {
30+
const text = sourceCode.getText(node);
31+
return fixer.replaceText(node, text.replace('this.this.', 'this.'));
32+
},
33+
});
34+
}
35+
},
36+
GlimmerElementNode(node) {
37+
if (node.tag?.startsWith('this.this.')) {
38+
context.report({
39+
node,
40+
messageId: 'noChainedThis',
41+
});
42+
}
43+
},
44+
};
45+
},
46+
};
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
const rule = require('../../../lib/rules/template-no-chained-this');
2+
const RuleTester = require('eslint').RuleTester;
3+
4+
const ruleTester = new RuleTester({
5+
parser: require.resolve('ember-eslint-parser'),
6+
parserOptions: { ecmaVersion: 2022, sourceType: 'module' },
7+
});
8+
9+
ruleTester.run('template-no-chained-this', rule, {
10+
valid: [
11+
'<template>{{this.value}}</template>',
12+
'<template>{{this.thisvalue}}</template>',
13+
'<template>{{@argName}}</template>',
14+
'<template>{{this.user.name}}</template>',
15+
'<template><this.Component /></template>',
16+
'<template>{{component this.dynamicComponent}}</template>',
17+
],
18+
19+
invalid: [
20+
{
21+
code: '<template>{{this.this.value}}</template>',
22+
output: '<template>{{this.value}}</template>',
23+
errors: [{ messageId: 'noChainedThis' }],
24+
},
25+
{
26+
code: '<template>{{helper value=this.this.foo}}</template>',
27+
output: '<template>{{helper value=this.foo}}</template>',
28+
errors: [{ messageId: 'noChainedThis' }],
29+
},
30+
{
31+
code: '<template>{{#if this.this.condition}}true{{/if}}</template>',
32+
output: '<template>{{#if this.condition}}true{{/if}}</template>',
33+
errors: [{ messageId: 'noChainedThis' }],
34+
},
35+
{
36+
code: '<template><this.this.Component /></template>',
37+
output: null,
38+
errors: [{ messageId: 'noChainedThis' }],
39+
},
40+
],
41+
});

0 commit comments

Comments
 (0)