Skip to content

Commit d9befc8

Browse files
committed
Apply PR Feedback
1 parent 77fbe79 commit d9befc8

4 files changed

Lines changed: 106 additions & 39 deletions

File tree

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -202,7 +202,7 @@ rules in templates can be disabled with eslint directives with mustache or html
202202
| [template-builtin-component-arguments](docs/rules/template-builtin-component-arguments.md) | disallow setting certain attributes on builtin components | | | |
203203
| [template-no-action-modifiers](docs/rules/template-no-action-modifiers.md) | disallow usage of {{action}} modifiers | | | |
204204
| [template-no-action-on-submit-button](docs/rules/template-no-action-on-submit-button.md) | disallow action attribute on submit buttons | | | |
205-
| [template-no-args-paths](docs/rules/template-no-args-paths.md) | disallow @args in paths | | | |
205+
| [template-no-args-paths](docs/rules/template-no-args-paths.md) | disallow args.foo paths in templates, use @foo instead | | 🔧 | |
206206
| [template-no-arguments-for-html-elements](docs/rules/template-no-arguments-for-html-elements.md) | disallow @arguments on HTML elements | | | |
207207
| [template-no-array-prototype-extensions](docs/rules/template-no-array-prototype-extensions.md) | disallow usage of Ember Array prototype extensions | | | |
208208
| [template-no-at-ember-render-modifiers](docs/rules/template-no-at-ember-render-modifiers.md) | disallow usage of @ember/render-modifiers | | | |

docs/rules/template-no-args-paths.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
# ember/template-no-args-paths
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
<!-- end auto-generated rule header -->
46

57
Arguments that are passed to components are prefixed with the `@` symbol in Angle bracket syntax.

lib/rules/template-no-args-paths.js

Lines changed: 71 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,17 @@ module.exports = {
33
meta: {
44
type: 'problem',
55
docs: {
6-
description: 'disallow @args in paths',
6+
description: 'disallow args.foo paths in templates, use @foo instead',
77
category: 'Best Practices',
88
url: 'https://github.com/ember-cli/eslint-plugin-ember/tree/master/docs/rules/template-no-args-paths.md',
99
templateMode: 'both',
1010
},
11+
fixable: 'code',
1112
schema: [],
12-
messages: { argsPath: 'Do not use paths with @args, use @argName directly instead.' },
13+
messages: {
14+
argsPath:
15+
'Component templates should avoid "{{path}}" usage, try "@{{replacement}}" instead.',
16+
},
1317
originallyFrom: {
1418
name: 'ember-template-lint',
1519
rule: 'lib/rules/no-args-paths.js',
@@ -18,16 +22,75 @@ module.exports = {
1822
},
1923
},
2024
create(context) {
25+
const localScopes = [];
26+
27+
function pushLocals(params) {
28+
localScopes.push(new Set(params || []));
29+
}
30+
31+
function popLocals() {
32+
localScopes.pop();
33+
}
34+
35+
function isLocal(name) {
36+
for (const scope of localScopes) {
37+
if (scope.has(name)) {
38+
return true;
39+
}
40+
}
41+
return false;
42+
}
43+
2144
return {
45+
GlimmerBlockStatement(node) {
46+
if (node.program && node.program.blockParams) {
47+
pushLocals(node.program.blockParams);
48+
}
49+
},
50+
'GlimmerBlockStatement:exit'(node) {
51+
if (node.program && node.program.blockParams) {
52+
popLocals();
53+
}
54+
},
55+
56+
GlimmerElementNode(node) {
57+
if (node.blockParams && node.blockParams.length > 0) {
58+
pushLocals(node.blockParams);
59+
}
60+
},
61+
'GlimmerElementNode:exit'(node) {
62+
if (node.blockParams && node.blockParams.length > 0) {
63+
popLocals();
64+
}
65+
},
66+
2267
GlimmerPathExpression(node) {
2368
const path = node.original;
24-
if (
25-
path?.startsWith('@args.') ||
26-
path?.startsWith('args.') ||
27-
path?.startsWith('this.args.')
28-
) {
29-
context.report({ node, messageId: 'argsPath' });
69+
70+
// @args.foo is a valid named argument — skip paths starting with @
71+
if (node.head?.type === 'AtHead') {
72+
return;
73+
}
74+
75+
if (!path?.startsWith('args.') && !path?.startsWith('this.args.')) {
76+
return;
77+
}
78+
79+
// Skip when 'args' is a block param in the current scope
80+
if (isLocal('args')) {
81+
return;
3082
}
83+
84+
const replacement = path.replace(/^(this\.)?args\./, '');
85+
86+
context.report({
87+
node,
88+
messageId: 'argsPath',
89+
data: { path, replacement },
90+
fix(fixer) {
91+
return fixer.replaceText(node, `@${replacement}`);
92+
},
93+
});
3194
},
3295
};
3396
},

tests/lib/rules/template-no-args-paths.js

Lines changed: 32 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ const ruleTester = new RuleTester({
88
ruleTester.run('template-no-args-paths', rule, {
99
valid: [
1010
'<template>{{@foo}}</template>',
11+
// @args.foo is a valid named argument, not a path violation
12+
'<template>{{@args.foo}}</template>',
1113
'<template><div @foo={{cleanup this.args}}></div></template>',
1214
'<template>{{foo (name this.args)}}</template>',
1315
'<template>{{foo name=this.args}}</template>',
@@ -16,52 +18,48 @@ ruleTester.run('template-no-args-paths', rule, {
1618
'<template><Foo {{mod this.args}} /></template>',
1719
'<template><Foo {{mod items=this.args}} /></template>',
1820
'<template><Foo {{mod items=(extract this.args)}} /></template>',
21+
// args as a block param is not flagged
22+
'<template>{{#each items as |args|}}{{args.name}}{{/each}}</template>',
1923
],
2024
invalid: [
21-
{
22-
code: '<template>{{@args.foo}}</template>',
23-
output: null,
24-
errors: [{ messageId: 'argsPath' }],
25-
},
26-
2725
{
2826
code: '<template>{{hello (format value=args.foo)}}</template>',
29-
output: null,
27+
output: '<template>{{hello (format value=@foo)}}</template>',
3028
errors: [{ messageId: 'argsPath' }],
3129
},
3230
{
3331
code: '<template>{{hello value=args.foo}}</template>',
34-
output: null,
32+
output: '<template>{{hello value=@foo}}</template>',
3533
errors: [{ messageId: 'argsPath' }],
3634
},
3735
{
3836
code: '<template>{{hello (format args.foo.bar)}}</template>',
39-
output: null,
37+
output: '<template>{{hello (format @foo.bar)}}</template>',
4038
errors: [{ messageId: 'argsPath' }],
4139
},
4240
{
4341
code: '<template><br {{hello args.foo.bar}}></template>',
44-
output: null,
42+
output: '<template><br {{hello @foo.bar}}></template>',
4543
errors: [{ messageId: 'argsPath' }],
4644
},
4745
{
4846
code: '<template>{{hello args.foo.bar}}</template>',
49-
output: null,
47+
output: '<template>{{hello @foo.bar}}</template>',
5048
errors: [{ messageId: 'argsPath' }],
5149
},
5250
{
5351
code: '<template>{{args.foo.bar}}</template>',
54-
output: null,
52+
output: '<template>{{@foo.bar}}</template>',
5553
errors: [{ messageId: 'argsPath' }],
5654
},
5755
{
5856
code: '<template>{{args.foo}}</template>',
59-
output: null,
57+
output: '<template>{{@foo}}</template>',
6058
errors: [{ messageId: 'argsPath' }],
6159
},
6260
{
6361
code: '<template>{{this.args.foo}}</template>',
64-
output: null,
62+
output: '<template>{{@foo}}</template>',
6563
errors: [{ messageId: 'argsPath' }],
6664
},
6765
],
@@ -77,6 +75,8 @@ const hbsRuleTester = new RuleTester({
7775

7876
hbsRuleTester.run('template-no-args-paths', rule, {
7977
valid: [
78+
// @args.foo is a valid named argument
79+
'{{@args.foo}}',
8080
'<div @foo={{cleanup this.args}}></div>',
8181
'{{foo (name this.args)}}',
8282
'{{foo name=this.args}}',
@@ -85,47 +85,49 @@ hbsRuleTester.run('template-no-args-paths', rule, {
8585
'<Foo {{mod this.args}} />',
8686
'<Foo {{mod items=this.args}} />',
8787
'<Foo {{mod items=(extract this.args)}} />',
88+
// args as a block param is not flagged
89+
'{{#each items as |args|}}{{args.name}}{{/each}}',
8890
],
8991
invalid: [
9092
{
9193
code: '{{hello (format value=args.foo)}}',
92-
output: null,
93-
errors: [{ message: 'Do not use paths with @args, use @argName directly instead.' }],
94+
output: '{{hello (format value=@foo)}}',
95+
errors: [{ messageId: 'argsPath' }],
9496
},
9597
{
9698
code: '{{hello value=args.foo}}',
97-
output: null,
98-
errors: [{ message: 'Do not use paths with @args, use @argName directly instead.' }],
99+
output: '{{hello value=@foo}}',
100+
errors: [{ messageId: 'argsPath' }],
99101
},
100102
{
101103
code: '{{hello (format args.foo.bar)}}',
102-
output: null,
103-
errors: [{ message: 'Do not use paths with @args, use @argName directly instead.' }],
104+
output: '{{hello (format @foo.bar)}}',
105+
errors: [{ messageId: 'argsPath' }],
104106
},
105107
{
106108
code: '<br {{hello args.foo.bar}}>',
107-
output: null,
108-
errors: [{ message: 'Do not use paths with @args, use @argName directly instead.' }],
109+
output: '<br {{hello @foo.bar}}>',
110+
errors: [{ messageId: 'argsPath' }],
109111
},
110112
{
111113
code: '{{hello args.foo.bar}}',
112-
output: null,
113-
errors: [{ message: 'Do not use paths with @args, use @argName directly instead.' }],
114+
output: '{{hello @foo.bar}}',
115+
errors: [{ messageId: 'argsPath' }],
114116
},
115117
{
116118
code: '{{args.foo.bar}}',
117-
output: null,
118-
errors: [{ message: 'Do not use paths with @args, use @argName directly instead.' }],
119+
output: '{{@foo.bar}}',
120+
errors: [{ messageId: 'argsPath' }],
119121
},
120122
{
121123
code: '{{args.foo}}',
122-
output: null,
123-
errors: [{ message: 'Do not use paths with @args, use @argName directly instead.' }],
124+
output: '{{@foo}}',
125+
errors: [{ messageId: 'argsPath' }],
124126
},
125127
{
126128
code: '{{this.args.foo}}',
127-
output: null,
128-
errors: [{ message: 'Do not use paths with @args, use @argName directly instead.' }],
129+
output: '{{@foo}}',
130+
errors: [{ messageId: 'argsPath' }],
129131
},
130132
],
131133
});

0 commit comments

Comments
 (0)