Skip to content

Commit 933206c

Browse files
committed
Fix template-no-capital-arguments: add AttrNode, reserved args, underscore
- Add GlimmerAttrNode visitor to catch @name in attribute positions - Add reserved argument detection (@arguments, @Args, @block, @else) - Extend check to catch underscore-prefixed args (@_name) - Add comprehensive test cases from original
1 parent e20ba32 commit 933206c

2 files changed

Lines changed: 130 additions & 24 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: 103 additions & 14 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,23 @@ 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+
`,
3444
},
3545
],
3646

3747
invalid: [
48+
// Capital path expression
3849
{
3950
filename: 'my-component.gjs',
4051
code: `
@@ -45,12 +56,7 @@ ruleTester.run('template-no-capital-arguments', rule, {
4556
</template>
4657
}
4758
`,
48-
output: null,
49-
errors: [
50-
{
51-
messageId: 'noCapitalArguments',
52-
},
53-
],
59+
errors: [{ messageId: 'noCapitalArguments' }],
5460
},
5561
{
5662
filename: 'my-component.gjs',
@@ -62,12 +68,95 @@ ruleTester.run('template-no-capital-arguments', rule, {
6268
</template>
6369
}
6470
`,
65-
output: null,
66-
errors: [
67-
{
68-
messageId: 'noCapitalArguments',
69-
},
70-
],
71+
errors: [{ messageId: 'noCapitalArguments' }],
72+
},
73+
// Capital attr node
74+
{
75+
filename: 'my-component.gjs',
76+
code: `
77+
import Component from '@glimmer/component';
78+
export default class MyComponent extends Component {
79+
<template>
80+
<Foo @Name="bar" />
81+
</template>
82+
}
83+
`,
84+
errors: [{ messageId: 'noCapitalArguments' }],
85+
},
86+
// Underscore prefix
87+
{
88+
filename: 'my-component.gjs',
89+
code: `
90+
import Component from '@glimmer/component';
91+
export default class MyComponent extends Component {
92+
<template>
93+
<div>{{@_Name}}</div>
94+
</template>
95+
}
96+
`,
97+
errors: [{ messageId: 'noCapitalArguments' }],
98+
},
99+
{
100+
filename: 'my-component.gjs',
101+
code: `
102+
import Component from '@glimmer/component';
103+
export default class MyComponent extends Component {
104+
<template>
105+
<Foo @_ame="bar" />
106+
</template>
107+
}
108+
`,
109+
errors: [{ messageId: 'noCapitalArguments' }],
110+
},
111+
// Reserved arguments in path expression
112+
{
113+
filename: 'my-component.gjs',
114+
code: `
115+
import Component from '@glimmer/component';
116+
export default class MyComponent extends Component {
117+
<template>
118+
<div>{{@arguments}}</div>
119+
</template>
120+
}
121+
`,
122+
errors: [{ messageId: 'reservedArgument' }],
123+
},
124+
{
125+
filename: 'my-component.gjs',
126+
code: `
127+
import Component from '@glimmer/component';
128+
export default class MyComponent extends Component {
129+
<template>
130+
<div>{{@args}}</div>
131+
</template>
132+
}
133+
`,
134+
errors: [{ messageId: 'reservedArgument' }],
135+
},
136+
// Reserved arguments in attr position
137+
{
138+
filename: 'my-component.gjs',
139+
code: `
140+
import Component from '@glimmer/component';
141+
export default class MyComponent extends Component {
142+
<template>
143+
<Foo @arguments={{42}} />
144+
</template>
145+
}
146+
`,
147+
errors: [{ messageId: 'reservedArgument' }],
148+
},
149+
{
150+
filename: 'my-component.gjs',
151+
code: `
152+
import Component from '@glimmer/component';
153+
export default class MyComponent extends Component {
154+
<template>
155+
<Foo @block={{42}} />
156+
</template>
157+
}
158+
`,
159+
errors: [{ messageId: 'reservedArgument' }],
71160
},
72161
],
73162
});

0 commit comments

Comments
 (0)