Skip to content

Commit 8f5a5e7

Browse files
committed
Extract rule: template-no-potential-path-strings
1 parent 0464492 commit 8f5a5e7

4 files changed

Lines changed: 321 additions & 0 deletions

File tree

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,7 @@ rules in templates can be disabled with eslint directives with mustache or html
257257
| [template-no-obsolete-elements](docs/rules/template-no-obsolete-elements.md) | disallow obsolete HTML elements | | | |
258258
| [template-no-outlet-outside-routes](docs/rules/template-no-outlet-outside-routes.md) | disallow {{outlet}} outside of route templates | | | |
259259
| [template-no-page-title-component](docs/rules/template-no-page-title-component.md) | disallow usage of ember-page-title component | | | |
260+
| [template-no-potential-path-strings](docs/rules/template-no-potential-path-strings.md) | disallow potential path strings in templates | | | |
260261
| [template-no-splattributes-with-class](docs/rules/template-no-splattributes-with-class.md) | disallow splattributes with class attribute | | | |
261262
| [template-no-trailing-spaces](docs/rules/template-no-trailing-spaces.md) | disallow trailing whitespace at the end of lines in templates | | 🔧 | |
262263
| [template-no-unavailable-this](docs/rules/template-no-unavailable-this.md) | disallow `this` in templates that are not inside a class or function | | | |
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
# ember/template-no-potential-path-strings
2+
3+
<!-- end auto-generated rule header -->
4+
5+
Disallow potential path strings that should be dynamic values in templates.
6+
7+
## Rule Details
8+
9+
It might happen sometimes that `{{` and `}}` are forgotten when invoking a component, and the string that is passed was actually supposed to be a property path or argument.
10+
11+
This rule warns about attribute values and text content that look like they should be dynamic paths. Specifically, it catches:
12+
13+
- **Attribute values** that start with `this.` or `@` (e.g. `<img src="this.picture">` or `<img src="@img">`)
14+
- **Text content** that contains path-like strings (e.g. `<div>this.propertyName</div>` or `<div>foo.bar</div>`)
15+
16+
## Examples
17+
18+
Examples of **incorrect** code for this rule:
19+
20+
```gjs
21+
<template>
22+
<img src="this.picture">
23+
</template>
24+
```
25+
26+
```gjs
27+
<template>
28+
<img src="@img">
29+
</template>
30+
```
31+
32+
```gjs
33+
<template>
34+
<div>this.propertyName</div>
35+
</template>
36+
```
37+
38+
```gjs
39+
<template>
40+
<div>foo.bar</div>
41+
</template>
42+
```
43+
44+
Examples of **correct** code for this rule:
45+
46+
```gjs
47+
<template>
48+
<img src={{this.picture}}>
49+
</template>
50+
```
51+
52+
```gjs
53+
<template>
54+
<img src={{@img}}>
55+
</template>
56+
```
57+
58+
```gjs
59+
<template>
60+
<div>{{this.propertyName}}</div>
61+
</template>
62+
```
63+
64+
```gjs
65+
<template>
66+
<div>{{this.foo.bar}}</div>
67+
</template>
68+
```
69+
70+
## Migration
71+
72+
- Replace the surrounding `"` characters with `{{`/`}}`
73+
74+
## Related Rules
75+
76+
- [no-arguments-for-html-elements](template-no-arguments-for-html-elements.md)
77+
78+
## References
79+
80+
- [Component Arguments and HTML Attributes](https://guides.emberjs.com/release/components/component-arguments-and-html-attributes/)
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
/** @type {import('eslint').Rule.RuleModule} */
2+
module.exports = {
3+
meta: {
4+
type: 'suggestion',
5+
docs: {
6+
description: 'disallow potential path strings in templates',
7+
category: 'Best Practices',
8+
recommended: false,
9+
url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-no-potential-path-strings.md',
10+
templateMode: 'both',
11+
},
12+
fixable: null,
13+
schema: [],
14+
messages: {
15+
noPotentialPathStrings:
16+
'Potential path string detected. Use dynamic values instead of path strings.',
17+
},
18+
originallyFrom: {
19+
name: 'ember-template-lint',
20+
rule: 'lib/rules/no-potential-path-strings.js',
21+
docs: 'docs/rule/no-potential-path-strings.md',
22+
tests: 'test/unit/rules/no-potential-path-strings-test.js',
23+
},
24+
},
25+
26+
create(context) {
27+
const attrTextNodes = new WeakSet();
28+
29+
return {
30+
GlimmerAttrNode(node) {
31+
if (node.value && node.value.type === 'GlimmerTextNode') {
32+
attrTextNodes.add(node.value);
33+
const text = node.value.chars;
34+
// Check for potential paths in attribute values:
35+
// - this.something (should be {{this.something}})
36+
// - @argName without / \ | (should be {{@argName}})
37+
if (/^this\.\w+/.test(text) || /^@[\w-]+$/.test(text)) {
38+
context.report({
39+
node: node.value,
40+
messageId: 'noPotentialPathStrings',
41+
});
42+
}
43+
}
44+
},
45+
46+
GlimmerTextNode(node) {
47+
if (!node.chars || attrTextNodes.has(node)) {
48+
return;
49+
}
50+
51+
// Check if text content looks like it could be a path (e.g., "foo.bar" or "this.foo")
52+
const pathPattern = /\b(this\.\w+|\w+\.\w+)\b/;
53+
if (pathPattern.test(node.chars)) {
54+
context.report({
55+
node,
56+
messageId: 'noPotentialPathStrings',
57+
});
58+
}
59+
},
60+
};
61+
},
62+
};
Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
const { RuleTester } = require('eslint');
2+
const rule = require('../../../lib/rules/template-no-potential-path-strings');
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-potential-path-strings', rule, {
10+
valid: [
11+
{
12+
filename: 'my-component.gjs',
13+
code: `
14+
import Component from '@glimmer/component';
15+
export default class MyComponent extends Component {
16+
<template>
17+
<div>{{this.propertyName}}</div>
18+
</template>
19+
}
20+
`,
21+
output: null,
22+
},
23+
{
24+
filename: 'my-component.gjs',
25+
code: `
26+
import Component from '@glimmer/component';
27+
export default class MyComponent extends Component {
28+
<template>
29+
<div>Hello world</div>
30+
</template>
31+
}
32+
`,
33+
output: null,
34+
},
35+
36+
'<template><img src="foo.png"></template>',
37+
'<template><img src={{picture}}></template>',
38+
'<template><img src={{this.picture}}></template>',
39+
'<template><img src={{@img}}></template>',
40+
'<template><SomeComponent @foo={{@bar}} /></template>',
41+
'<template><Ui::Demo @title="@my-org/my-package" /></template>',
42+
'<template><Ui::Demo @title="@my-org\\my-package" /></template>',
43+
'<template><Ui::Demo @title="@my-org|my-package" /></template>',
44+
],
45+
46+
invalid: [
47+
{
48+
filename: 'my-component.gjs',
49+
code: `
50+
import Component from '@glimmer/component';
51+
export default class MyComponent extends Component {
52+
<template>
53+
<div>this.propertyName</div>
54+
</template>
55+
}
56+
`,
57+
output: null,
58+
errors: [
59+
{
60+
messageId: 'noPotentialPathStrings',
61+
},
62+
],
63+
},
64+
{
65+
filename: 'my-component.gjs',
66+
code: `
67+
import Component from '@glimmer/component';
68+
export default class MyComponent extends Component {
69+
<template>
70+
<div>foo.bar</div>
71+
</template>
72+
}
73+
`,
74+
output: null,
75+
errors: [
76+
{
77+
messageId: 'noPotentialPathStrings',
78+
},
79+
],
80+
},
81+
82+
{
83+
code: '<template><img src="this.picture"></template>',
84+
output: null,
85+
errors: [{ messageId: 'noPotentialPathStrings' }],
86+
},
87+
{
88+
code: '<template><img src=this.picture></template>',
89+
output: null,
90+
errors: [{ messageId: 'noPotentialPathStrings' }],
91+
},
92+
{
93+
code: '<template><img src="@img"></template>',
94+
output: null,
95+
errors: [{ messageId: 'noPotentialPathStrings' }],
96+
},
97+
{
98+
code: '<template><img src=@img></template>',
99+
output: null,
100+
errors: [{ messageId: 'noPotentialPathStrings' }],
101+
},
102+
{
103+
code: '<template><SomeComponent @foo=@bar /></template>',
104+
output: null,
105+
errors: [{ messageId: 'noPotentialPathStrings' }],
106+
},
107+
{
108+
code: '<template><SomeComponent @foo=this.bar /></template>',
109+
output: null,
110+
errors: [{ messageId: 'noPotentialPathStrings' }],
111+
},
112+
],
113+
});
114+
115+
const hbsRuleTester = new RuleTester({
116+
parser: require.resolve('ember-eslint-parser/hbs'),
117+
parserOptions: {
118+
ecmaVersion: 2022,
119+
sourceType: 'module',
120+
},
121+
});
122+
123+
hbsRuleTester.run('template-no-potential-path-strings', rule, {
124+
valid: [
125+
'<img src="foo.png">',
126+
'<img src={{picture}}>',
127+
'<img src={{this.picture}}>',
128+
'<img src={{@img}}>',
129+
'<SomeComponent @foo={{@bar}} />',
130+
'<Ui::Demo @title="@my-org/my-package" />',
131+
'<Ui::Demo @title="@my-org\\my-package" />',
132+
'<Ui::Demo @title="@my-org|my-package" />',
133+
],
134+
invalid: [
135+
{
136+
code: '<img src="this.picture">',
137+
output: null,
138+
errors: [
139+
{ message: 'Potential path string detected. Use dynamic values instead of path strings.' },
140+
],
141+
},
142+
{
143+
code: '<img src=this.picture>',
144+
output: null,
145+
errors: [
146+
{ message: 'Potential path string detected. Use dynamic values instead of path strings.' },
147+
],
148+
},
149+
{
150+
code: '<img src="@img">',
151+
output: null,
152+
errors: [
153+
{ message: 'Potential path string detected. Use dynamic values instead of path strings.' },
154+
],
155+
},
156+
{
157+
code: '<img src=@img>',
158+
output: null,
159+
errors: [
160+
{ message: 'Potential path string detected. Use dynamic values instead of path strings.' },
161+
],
162+
},
163+
{
164+
code: '<SomeComponent @foo=@bar />',
165+
output: null,
166+
errors: [
167+
{ message: 'Potential path string detected. Use dynamic values instead of path strings.' },
168+
],
169+
},
170+
{
171+
code: '<SomeComponent @foo=this.bar />',
172+
output: null,
173+
errors: [
174+
{ message: 'Potential path string detected. Use dynamic values instead of path strings.' },
175+
],
176+
},
177+
],
178+
});

0 commit comments

Comments
 (0)