Skip to content

Commit 3782507

Browse files
committed
ope
1 parent f471c9e commit 3782507

35 files changed

Lines changed: 988 additions & 461 deletions

docs/rules/template-no-implicit-this.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,24 @@ Examples of **correct** code for this rule:
109109
{{if this.condition 'yes' 'no'}}
110110
```
111111

112+
## Options
113+
114+
- object -- An object with the following keys:
115+
- `allow` -- An array of string names to allow as implicit this. Paths that exactly match an entry in this list will not be flagged.
116+
117+
Example:
118+
119+
```json
120+
{
121+
"ember/template-no-implicit-this": [
122+
"error",
123+
{
124+
"allow": ["book-details"]
125+
}
126+
]
127+
}
128+
```
129+
112130
## Migration
113131

114132
- use [ember-no-implicit-this-codemod](https://github.com/ember-codemods/ember-no-implicit-this-codemod)

docs/rules/template-no-multiple-empty-lines.md

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,12 @@ Multiple consecutive blank lines reduce readability and should be limited.
1010

1111
This rule enforces a maximum number of consecutive empty lines (default: 1).
1212

13-
## Config
13+
## Configuration
1414

15-
This rule has no configuration options.
15+
The following values are valid configuration:
16+
17+
- object -- An object with the following keys:
18+
- `max` -- An integer specifying the maximum number of consecutive empty lines allowed. Defaults to `1`.
1619

1720
## Examples
1821

docs/rules/template-no-obsolete-elements.md

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,11 +37,9 @@ This rule **forbids** the following:
3737
<plaintext></plaintext>
3838
<rb></rb>
3939
<rtc></rtc>
40-
<s></s>
4140
<spacer></spacer>
4241
<strike></strike>
4342
<tt></tt>
44-
<u></u>
4543
<xmp></xmp>
4644
</template>
4745
```

lib/rules/template-no-action-on-submit-button.js

Lines changed: 4 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ module.exports = {
5959
schema: [],
6060
messages: {
6161
noActionOnSubmitButton:
62-
'Do not use action attribute on submit buttons. Use on modifier instead or handle form submission.',
62+
'In a `<form>`, a `<button>` with `type="submit"` should have no click action',
6363
},
6464
originallyFrom: {
6565
name: 'ember-template-lint',
@@ -71,51 +71,16 @@ module.exports = {
7171

7272
create(context) {
7373
return {
74-
// eslint-disable-next-line complexity
7574
GlimmerElementNode(node) {
76-
if (node.tag !== 'button' && node.tag !== 'input') {
75+
if (node.tag !== 'button') {
7776
return;
7877
}
7978

80-
let hasActionAttribute = false;
81-
let isSubmitButtonExplicit = false;
82-
let hasNonSubmitType = false;
83-
84-
for (const attr of node.attributes || []) {
85-
if (attr.type === 'GlimmerAttrNode') {
86-
if (attr.name === 'action') {
87-
hasActionAttribute = true;
88-
}
89-
if (attr.name === 'type') {
90-
const value = attr.value;
91-
if (value.type === 'GlimmerTextNode' && value.chars === 'submit') {
92-
isSubmitButtonExplicit = true;
93-
} else if (value.type === 'GlimmerTextNode' && value.chars !== 'submit') {
94-
hasNonSubmitType = true;
95-
}
96-
}
97-
}
98-
}
99-
100-
const isDefaultSubmitButton =
101-
node.tag === 'button' && !hasNonSubmitType && !isSubmitButtonExplicit;
102-
103-
// Existing: check for action HTML attribute on submit buttons
104-
if (hasActionAttribute && (isSubmitButtonExplicit || isDefaultSubmitButton)) {
105-
context.report({
106-
node,
107-
messageId: 'noActionOnSubmitButton',
108-
});
79+
if (!isInsideForm(node)) {
10980
return;
11081
}
11182

112-
// New: check for action/on click modifiers on submit buttons inside forms
113-
if (
114-
node.tag === 'button' &&
115-
isSubmitButton(node) &&
116-
isInsideForm(node) &&
117-
hasClickHandlingModifier(node)
118-
) {
83+
if (isSubmitButton(node) && hasClickHandlingModifier(node)) {
11984
context.report({
12085
node,
12186
messageId: 'noActionOnSubmitButton',

lib/rules/template-no-action.js

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,10 @@ module.exports = {
3232
messages: {
3333
subExpression:
3434
'Do not use `action` as (action ...). Instead, use the `on` modifier and `fn` helper.',
35-
mustache: 'Do not use `action` in templates. Instead, use the `on` modifier and `fn` helper.',
35+
mustache:
36+
'Do not use the `action` mustache helper. Instead, use the `on` modifier and `fn` helper.',
37+
elementModifier:
38+
'Do not use the `action` modifier on <{{tag}}>. Instead, use the `on` modifier and `fn` helper.',
3639
},
3740
originallyFrom: {
3841
name: 'ember-template-lint',
@@ -77,7 +80,7 @@ module.exports = {
7780
) {
7881
context.report({
7982
node,
80-
messageId: 'subExpression',
83+
messageId: 'mustache',
8184
});
8285
}
8386
return;
@@ -91,9 +94,11 @@ module.exports = {
9194

9295
GlimmerElementModifierStatement(node) {
9396
if (isActionHelper(node)) {
97+
const parentTag = node.parent && node.parent.tag ? node.parent.tag : 'unknown';
9498
context.report({
9599
node,
96-
messageId: 'subExpression',
100+
messageId: 'elementModifier',
101+
data: { tag: parentTag },
97102
});
98103
}
99104
},

lib/rules/template-no-bare-strings.js

Lines changed: 36 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ const DEFAULT_ALLOWLIST = [
8484
'|',
8585
];
8686

87-
const IGNORED_ELEMENTS = ['pre', 'script', 'style', 'textarea'];
87+
const IGNORED_ELEMENTS = ['pre', 'script', 'style', 'template', 'textarea'];
8888

8989
function sanitizeConfigArray(arr = []) {
9090
return arr.filter((o) => o !== '').sort((a, b) => b.length - a.length);
@@ -264,14 +264,46 @@ module.exports = {
264264
}
265265

266266
let currentElementNode = null;
267+
let templateRange = null;
267268

268269
return {
270+
GlimmerTemplate(node) {
271+
// Only track the template range in GJS/GTS mode (not HBS mode).
272+
// In GJS/GTS, the outermost <template> is a structural wrapper that should
273+
// NOT be treated as an ignored HTML <template> element.
274+
// In HBS mode, any <template> element is a real HTML element.
275+
const isHbsParser = context.parserPath && context.parserPath.includes('hbs-parser');
276+
if (!isHbsParser) {
277+
templateRange = node.range;
278+
}
279+
},
280+
269281
GlimmerElementNode(node) {
270282
currentElementNode = node;
271-
elementStack.push(node.tag);
283+
284+
// Skip the structural <template> wrapper in GJS/GTS mode.
285+
// In GJS/GTS, the wrapper has tag='template' and same range as GlimmerTemplate.
286+
// A real HTML <template> element will have a different range.
287+
const isGjsWrapper =
288+
templateRange &&
289+
node.tag === 'template' &&
290+
node.range[0] === templateRange[0] &&
291+
node.range[1] === templateRange[1];
292+
293+
if (!isGjsWrapper) {
294+
elementStack.push(node.tag);
295+
}
272296
},
273-
'GlimmerElementNode:exit'() {
274-
elementStack.pop();
297+
'GlimmerElementNode:exit'(node) {
298+
const isGjsWrapper =
299+
templateRange &&
300+
node.tag === 'template' &&
301+
node.range[0] === templateRange[0] &&
302+
node.range[1] === templateRange[1];
303+
304+
if (!isGjsWrapper) {
305+
elementStack.pop();
306+
}
275307
},
276308

277309
GlimmerTextNode(node) {

lib/rules/template-no-curly-component-invocation.js

Lines changed: 76 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,14 @@ module.exports = {
141141
return false;
142142
}
143143

144+
function reportMustache(node, pathOriginal) {
145+
const angleBracketName = transformTagName(pathOriginal);
146+
context.report({
147+
node,
148+
message: `You are using the component {{${pathOriginal}}} with curly component syntax. You should use <${angleBracketName}> instead. If it is actually a helper you must manually add it to the 'no-curly-component-invocation' rule configuration, e.g. \`'no-curly-component-invocation': { allow: ['${pathOriginal}'] }\`.`,
149+
});
150+
}
151+
144152
return {
145153
GlimmerMustacheStatement(node) {
146154
if (!node.path || node.path.type !== 'GlimmerPathExpression') {
@@ -149,45 +157,93 @@ module.exports = {
149157

150158
const pathOriginal = node.path.original;
151159

152-
// Skip if has positional params or hash arguments (can't be converted to angle brackets)
153-
if (
154-
(node.params && node.params.length > 0) ||
155-
(node.hash && node.hash.pairs && node.hash.pairs.length > 0)
156-
) {
160+
// Special case: link-to is always reported regardless of params
161+
if (pathOriginal === 'link-to') {
162+
reportMustache(node, pathOriginal);
157163
return;
158164
}
159165

160-
if (shouldCheckComponent(pathOriginal)) {
161-
const angleBracketName = transformTagName(pathOriginal);
162-
context.report({
163-
node,
164-
message: `You are using the component {{${pathOriginal}}} with curly component syntax. You should use <${angleBracketName}> instead. If it is actually a helper you must manually add it to the 'no-curly-component-invocation' rule configuration, e.g. \`'no-curly-component-invocation': { allow: ['${pathOriginal}'] }\`.`,
165-
});
166+
// Skip if has positional params (angle bracket syntax doesn't support positional params)
167+
if (node.params && node.params.length > 0) {
168+
return;
169+
}
170+
171+
const hasNamedArguments = node.hash && node.hash.pairs && node.hash.pairs.length > 0;
172+
173+
if (hasNamedArguments) {
174+
// Always curly - don't report even with hash pairs
175+
if (ALWAYS_CURLY.has(pathOriginal)) {
176+
return;
177+
}
178+
179+
// Check if in allow list
180+
if (config.allow.includes(pathOriginal)) {
181+
return;
182+
}
183+
184+
// input/textarea with hash pairs are always reported
185+
if (['input', 'textarea'].includes(pathOriginal)) {
186+
reportMustache(node, pathOriginal);
187+
return;
188+
}
189+
190+
// requireDash: skip single-word names without a dash
191+
if (config.requireDash && !pathOriginal.includes('-')) {
192+
return;
193+
}
194+
195+
// Built-in helpers with hash pairs are not reported
196+
if (BUILT_INS.has(pathOriginal)) {
197+
return;
198+
}
199+
200+
// Report components with hash pairs (has dash, slash, or multi-part path)
201+
reportMustache(node, pathOriginal);
202+
} else {
203+
if (shouldCheckComponent(pathOriginal)) {
204+
reportMustache(node, pathOriginal);
205+
}
166206
}
167207
},
168208

169209
GlimmerBlockStatement(node) {
170-
if (!node.path || node.path.type !== 'GlimmerPathExpression') {
210+
if (node.inverse) {
211+
// {{#foo}}bar{{else}}baz{{/foo}}
171212
return;
172213
}
173214

174-
const pathOriginal = node.path.original;
175-
176-
// Skip if has positional params or hash arguments
177-
if (
178-
(node.params && node.params.length > 0) ||
179-
(node.hash && node.hash.pairs && node.hash.pairs.length > 0)
180-
) {
215+
if (!node.path || node.path.type !== 'GlimmerPathExpression') {
181216
return;
182217
}
183218

184-
if (shouldCheckComponent(pathOriginal)) {
219+
const pathOriginal = node.path.original;
220+
221+
// Special case: link-to is always reported regardless of params
222+
if (pathOriginal === 'link-to') {
185223
const angleBracketName = transformTagName(pathOriginal);
186224
context.report({
187225
node,
188226
message: `You are using the component {{#${pathOriginal}}} with curly component syntax. You should use <${angleBracketName}> instead. If it is actually a helper you must manually add it to the 'no-curly-component-invocation' rule configuration, e.g. \`'no-curly-component-invocation': { allow: ['${pathOriginal}'] }\`.`,
189227
});
228+
return;
190229
}
230+
231+
// Skip if has positional params
232+
if (node.params && node.params.length > 0) {
233+
return;
234+
}
235+
236+
// Check if in allow list
237+
if (config.allow.includes(pathOriginal)) {
238+
return;
239+
}
240+
241+
// Report block invocations (with or without hash pairs)
242+
const angleBracketName = transformTagName(pathOriginal);
243+
context.report({
244+
node,
245+
message: `You are using the component {{#${pathOriginal}}} with curly component syntax. You should use <${angleBracketName}> instead. If it is actually a helper you must manually add it to the 'no-curly-component-invocation' rule configuration, e.g. \`'no-curly-component-invocation': { allow: ['${pathOriginal}'] }\`.`,
246+
});
191247
},
192248
};
193249
},

0 commit comments

Comments
 (0)