Skip to content

Commit d312d08

Browse files
committed
Extract rule: template-no-unavailable-this
1 parent e0f9805 commit d312d08

4 files changed

Lines changed: 176 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-unavailable-this](docs/rules/template-no-unavailable-this.md) | disallow `this` in templates that are not inside a class or function | | | |
259260
| [template-no-unnecessary-component-helper](docs/rules/template-no-unnecessary-component-helper.md) | disallow unnecessary component helper | | 🔧 | |
260261
| [template-no-unnecessary-concat](docs/rules/template-no-unnecessary-concat.md) | disallow unnecessary string concatenation | | 🔧 | |
261262
| [template-no-unnecessary-curly-parens](docs/rules/template-no-unnecessary-curly-parens.md) | disallow unnecessary parentheses enclosing statements in curlies | | 🔧 | |
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
# ember/template-no-unavailable-this
2+
3+
<!-- end auto-generated rule header -->
4+
5+
Disallow `this` in templates that are not inside a class or function.
6+
7+
## Rule Details
8+
9+
In Ember and Glimmer, `this` refers to the component instance. When a `<template>` tag is used at module level (not inside a class body or function), `this` has no meaningful value and will be `undefined`. This rule catches accidental usage of `this` in such templates.
10+
11+
## Examples
12+
13+
Examples of **incorrect** code for this rule:
14+
15+
```gjs
16+
<template>
17+
{{this.name}}
18+
</template>
19+
```
20+
21+
```gjs
22+
<template>
23+
{{yield this}}
24+
</template>
25+
```
26+
27+
Examples of **correct** code for this rule:
28+
29+
```gjs
30+
import Component from '@glimmer/component';
31+
32+
class MyComponent extends Component {
33+
<template>{{this.name}}</template>
34+
}
35+
```
36+
37+
```gjs
38+
function myComponent() {
39+
return <template>{{this.name}}</template>;
40+
}
41+
```
42+
43+
```gjs
44+
<template>
45+
{{@value}}
46+
</template>
47+
```
48+
49+
## References
50+
51+
- [Glimmer Components](https://guides.emberjs.com/release/upgrading/current-edition/glimmer-components/)
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
function isInsideClassOrFunction(node) {
2+
let current = node.parent;
3+
while (current) {
4+
if (
5+
current.type === 'ClassBody' ||
6+
current.type === 'FunctionExpression' ||
7+
current.type === 'FunctionDeclaration'
8+
) {
9+
return true;
10+
}
11+
current = current.parent;
12+
}
13+
return false;
14+
}
15+
16+
/** @type {import('eslint').Rule.RuleModule} */
17+
module.exports = {
18+
meta: {
19+
type: 'problem',
20+
docs: {
21+
description: 'disallow `this` in templates that are not inside a class or function',
22+
category: 'Best Practices',
23+
url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-no-unavailable-this.md',
24+
templateMode: 'strict',
25+
},
26+
schema: [],
27+
messages: {
28+
noUnavailableThis:
29+
'`this` is not available in a template that is not inside a class or function.',
30+
},
31+
},
32+
33+
create(context) {
34+
return {
35+
GlimmerPathExpression(node) {
36+
if (
37+
(node.original === 'this' || node.original?.startsWith('this.')) &&
38+
!isInsideClassOrFunction(node)
39+
) {
40+
context.report({
41+
node,
42+
messageId: 'noUnavailableThis',
43+
});
44+
}
45+
},
46+
};
47+
},
48+
};
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
const rule = require('../../../lib/rules/template-no-unavailable-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-unavailable-this', rule, {
10+
valid: [
11+
// this inside a class body
12+
`import Component from '@glimmer/component';
13+
class MyComponent extends Component {
14+
<template>{{this.name}}</template>
15+
}`,
16+
// this inside a function
17+
`function myComponent() {
18+
return <template>{{this.name}}</template>;
19+
}`,
20+
// this inside an arrow function that is inside a class (arrow inherits this from class)
21+
`import Component from '@glimmer/component';
22+
class MyComponent extends Component {
23+
foo = () => {
24+
return <template>{{this.name}}</template>;
25+
}
26+
}`,
27+
// No this at all
28+
'<template>{{@value}}</template>',
29+
'<template><div>Content</div></template>',
30+
// yield this inside a class (this is valid, other rules handle yield specifics)
31+
`import Component from '@glimmer/component';
32+
class MyComponent extends Component {
33+
<template>{{yield this}}</template>
34+
}`,
35+
],
36+
invalid: [
37+
// this.property at module level
38+
{
39+
code: '<template>{{this.name}}</template>',
40+
output: null,
41+
errors: [{ messageId: 'noUnavailableThis' }],
42+
},
43+
// bare this at module level
44+
{
45+
code: '<template>{{this}}</template>',
46+
output: null,
47+
errors: [{ messageId: 'noUnavailableThis' }],
48+
},
49+
// yield this at module level (this rule catches the `this`, yield rule catches yield semantics)
50+
{
51+
code: '<template>{{yield this}}</template>',
52+
output: null,
53+
errors: [{ messageId: 'noUnavailableThis' }],
54+
},
55+
// multiple this references at module level
56+
{
57+
code: '<template>{{this.foo}} {{this.bar}}</template>',
58+
output: null,
59+
errors: [{ messageId: 'noUnavailableThis' }, { messageId: 'noUnavailableThis' }],
60+
},
61+
// deeply nested this.property at module level
62+
{
63+
code: '<template>{{this.foo.bar.baz}}</template>',
64+
output: null,
65+
errors: [{ messageId: 'noUnavailableThis' }],
66+
},
67+
// arrow function at module level (arrow functions don't have their own this)
68+
{
69+
code: `const myComponent = () => {
70+
return <template>{{this}}</template>;
71+
}`,
72+
output: null,
73+
errors: [{ messageId: 'noUnavailableThis' }],
74+
},
75+
],
76+
});

0 commit comments

Comments
 (0)