Skip to content

Commit 6aaf941

Browse files
Merge pull request #2499 from johanrd/fix/2414
Post-merge review of #2414 (`template-no-capital-arguments`)
2 parents af753b4 + a671be2 commit 6aaf941

2 files changed

Lines changed: 163 additions & 22 deletions

File tree

lib/rules/template-no-capital-arguments.js

Lines changed: 27 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
const RESERVED = new Set(['@arguments', '@args', '@block', '@else']);
2+
const ALLOWED_PREFIX = /^[a-z]/;
3+
14
/** @type {import('eslint').Rule.RuleModule} */
25
module.exports = {
36
meta: {
@@ -13,34 +16,48 @@ module.exports = {
1316
messages: {
1417
noCapitalArguments:
1518
'Argument names should start with lowercase. Use @{{lowercase}} instead of @{{name}}.',
19+
reservedArgument: '{{name}} is a reserved argument name, try to use another.',
1620
},
1721
strictGjs: true,
1822
strictGts: true,
1923
},
2024

2125
create(context) {
22-
function checkPath(node, path) {
23-
if (!path || !path.head) {
26+
function checkArgName(node, name) {
27+
if (!name || !name.startsWith('@')) {
2428
return;
2529
}
2630

27-
const name = path.head.name || path.head;
28-
if (typeof name === 'string' && name.startsWith('@') && /^@[A-Z]/.test(name)) {
29-
const lowercase = name.charAt(0) + name.charAt(1).toLowerCase() + name.slice(2);
31+
const part = name.slice(1);
32+
const firstChar = part.charAt(0);
33+
34+
if (RESERVED.has(name)) {
35+
context.report({
36+
node,
37+
messageId: 'reservedArgument',
38+
data: { name },
39+
});
40+
} else if (!ALLOWED_PREFIX.test(firstChar)) {
41+
const lowercase = `@${firstChar.toLowerCase()}${part.slice(1)}`;
3042
context.report({
3143
node,
3244
messageId: 'noCapitalArguments',
33-
data: {
34-
name,
35-
lowercase,
36-
},
45+
data: { name, lowercase },
3746
});
3847
}
3948
}
4049

4150
return {
4251
GlimmerPathExpression(node) {
43-
checkPath(node, node);
52+
const name = node.original || (node.head && (node.head.name || node.head));
53+
if (typeof name === 'string' && name.startsWith('@')) {
54+
checkArgName(node, name);
55+
}
56+
},
57+
GlimmerAttrNode(node) {
58+
if (node.name && node.name.startsWith('@')) {
59+
checkArgName(node, node.name);
60+
}
4461
},
4562
};
4663
},

tests/lib/rules/template-no-capital-arguments.js

Lines changed: 136 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ ruleTester.run('template-no-capital-arguments', rule, {
1818
</template>
1919
}
2020
`,
21-
output: null,
2221
},
2322
{
2423
filename: 'my-component.gjs',
@@ -30,11 +29,35 @@ ruleTester.run('template-no-capital-arguments', rule, {
3029
</template>
3130
}
3231
`,
33-
output: null,
32+
},
33+
// @name in attribute position (lowercase) is valid
34+
{
35+
filename: 'my-component.gjs',
36+
code: `
37+
import Component from '@glimmer/component';
38+
export default class MyComponent extends Component {
39+
<template>
40+
<Foo @name="bar" />
41+
</template>
42+
}
43+
`,
44+
},
45+
// Nested lowercase path is valid
46+
{
47+
filename: 'my-component.gjs',
48+
code: `
49+
import Component from '@glimmer/component';
50+
export default class MyComponent extends Component {
51+
<template>
52+
<div>{{@arg.nested}}</div>
53+
</template>
54+
}
55+
`,
3456
},
3557
],
3658

3759
invalid: [
60+
// Capital path expression
3861
{
3962
filename: 'my-component.gjs',
4063
code: `
@@ -46,11 +69,7 @@ ruleTester.run('template-no-capital-arguments', rule, {
4669
}
4770
`,
4871
output: null,
49-
errors: [
50-
{
51-
messageId: 'noCapitalArguments',
52-
},
53-
],
72+
errors: [{ messageId: 'noCapitalArguments' }],
5473
},
5574
{
5675
filename: 'my-component.gjs',
@@ -63,11 +82,116 @@ ruleTester.run('template-no-capital-arguments', rule, {
6382
}
6483
`,
6584
output: null,
66-
errors: [
67-
{
68-
messageId: 'noCapitalArguments',
69-
},
70-
],
85+
errors: [{ messageId: 'noCapitalArguments' }],
86+
},
87+
// Capital attr node
88+
{
89+
filename: 'my-component.gjs',
90+
code: `
91+
import Component from '@glimmer/component';
92+
export default class MyComponent extends Component {
93+
<template>
94+
<Foo @Name="bar" />
95+
</template>
96+
}
97+
`,
98+
output: null,
99+
errors: [{ messageId: 'noCapitalArguments' }],
100+
},
101+
// Nested capital path
102+
{
103+
filename: 'my-component.gjs',
104+
code: `
105+
import Component from '@glimmer/component';
106+
export default class MyComponent extends Component {
107+
<template>
108+
<div>{{@Arg.nested}}</div>
109+
</template>
110+
}
111+
`,
112+
output: null,
113+
errors: [{ messageId: 'noCapitalArguments' }],
114+
},
115+
// Underscore prefix
116+
{
117+
filename: 'my-component.gjs',
118+
code: `
119+
import Component from '@glimmer/component';
120+
export default class MyComponent extends Component {
121+
<template>
122+
<div>{{@_Name}}</div>
123+
</template>
124+
}
125+
`,
126+
output: null,
127+
errors: [{ messageId: 'noCapitalArguments' }],
128+
},
129+
{
130+
filename: 'my-component.gjs',
131+
code: `
132+
import Component from '@glimmer/component';
133+
export default class MyComponent extends Component {
134+
<template>
135+
<Foo @_ame="bar" />
136+
</template>
137+
}
138+
`,
139+
output: null,
140+
errors: [{ messageId: 'noCapitalArguments' }],
141+
},
142+
// Reserved arguments in path expression
143+
{
144+
filename: 'my-component.gjs',
145+
code: `
146+
import Component from '@glimmer/component';
147+
export default class MyComponent extends Component {
148+
<template>
149+
<div>{{@arguments}}</div>
150+
</template>
151+
}
152+
`,
153+
output: null,
154+
errors: [{ messageId: 'reservedArgument' }],
155+
},
156+
{
157+
filename: 'my-component.gjs',
158+
code: `
159+
import Component from '@glimmer/component';
160+
export default class MyComponent extends Component {
161+
<template>
162+
<div>{{@args}}</div>
163+
</template>
164+
}
165+
`,
166+
output: null,
167+
errors: [{ messageId: 'reservedArgument' }],
168+
},
169+
// Reserved arguments in attr position
170+
{
171+
filename: 'my-component.gjs',
172+
code: `
173+
import Component from '@glimmer/component';
174+
export default class MyComponent extends Component {
175+
<template>
176+
<Foo @arguments={{42}} />
177+
</template>
178+
}
179+
`,
180+
output: null,
181+
errors: [{ messageId: 'reservedArgument' }],
182+
},
183+
{
184+
filename: 'my-component.gjs',
185+
code: `
186+
import Component from '@glimmer/component';
187+
export default class MyComponent extends Component {
188+
<template>
189+
<Foo @block={{42}} />
190+
</template>
191+
}
192+
`,
193+
output: null,
194+
errors: [{ messageId: 'reservedArgument' }],
71195
},
72196
],
73197
});

0 commit comments

Comments
 (0)