Skip to content

Commit ae2260b

Browse files
Merge pull request #2672 from johanrd/night_fix/template-require-input-label
Post-merge-review: Fix `template-require-input-label` mustache branch: apply strict-mode guard
2 parents 27f8466 + 8804d2f commit ae2260b

File tree

2 files changed

+65
-6
lines changed

2 files changed

+65
-6
lines changed

lib/rules/template-require-input-label.js

Lines changed: 40 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,10 @@ module.exports = {
8484
const isStrictMode = filename.endsWith('.gjs') || filename.endsWith('.gts');
8585
const elementStack = [];
8686

87+
// local name → original name ('Input' | 'Textarea')
88+
// Only populated in GJS/GTS files via ImportDeclaration
89+
const importedFormComponents = new Map();
90+
8791
function hasValidLabelParent() {
8892
for (let i = elementStack.length - 1; i >= 0; i--) {
8993
const entry = elementStack[i];
@@ -104,15 +108,38 @@ module.exports = {
104108
}
105109

106110
return {
107-
GlimmerElementNode(node) {
108-
elementStack.push({ tag: node.tag, node });
109-
110-
if (isStrictMode && (node.tag === 'Input' || node.tag === 'Textarea')) {
111+
ImportDeclaration(node) {
112+
if (!isStrictMode) {
111113
return;
112114
}
115+
if (node.source.value === '@ember/component') {
116+
for (const specifier of node.specifiers) {
117+
if (specifier.type === 'ImportSpecifier') {
118+
const original = specifier.imported.name;
119+
if (original === 'Input' || original === 'Textarea') {
120+
importedFormComponents.set(specifier.local.name, original);
121+
}
122+
}
123+
}
124+
}
125+
},
113126

114-
const tagName = node.tag?.toLowerCase();
115-
if (tagName !== 'input' && tagName !== 'textarea' && tagName !== 'select') {
127+
GlimmerElementNode(node) {
128+
elementStack.push({ tag: node.tag, node });
129+
130+
const tag = node.tag;
131+
// Is this tag one we should check?
132+
// - Native <input>/<textarea>/<select> always.
133+
// - <Input>/<Textarea> built-ins:
134+
// - In strict mode (.gjs/.gts): only if the tag resolves to a tracked
135+
// import from '@ember/component' (supports renames).
136+
// - In HBS: match by bare tag name.
137+
const isNativeFormElement = tag === 'input' || tag === 'textarea' || tag === 'select';
138+
const isBuiltinFormComponent = isStrictMode
139+
? importedFormComponents.has(tag)
140+
: tag === 'Input' || tag === 'Textarea';
141+
142+
if (!isNativeFormElement && !isBuiltinFormComponent) {
116143
return;
117144
}
118145

@@ -164,6 +191,13 @@ module.exports = {
164191
},
165192

166193
GlimmerMustacheStatement(node) {
194+
// Classic {{input}}/{{textarea}} curly helpers only exist in HBS.
195+
// In GJS/GTS, these identifiers are user-imported JS bindings with
196+
// no relation to the classic helpers, so skip.
197+
if (isStrictMode) {
198+
return;
199+
}
200+
167201
const name = node.path?.original;
168202
if (name !== 'input' && name !== 'textarea') {
169203
return;

tests/lib/rules/template-require-input-label.js

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,19 @@ ruleTester.run('template-require-input-label', rule, {
3838
'<template><input type="hidden" /></template>',
3939
'<template><Input type="hidden" /></template>',
4040
'<template>{{input type="hidden"}}</template>',
41+
// In GJS/GTS with no @ember/component import, <Input>/<Textarea> are
42+
// user-authored components — do not treat them as the built-in.
4143
{ filename: 'layout.gjs', code: '<template><Input /></template>' },
4244
{ filename: 'layout.gts', code: '<template><Textarea /></template>' },
45+
// In GJS/GTS, {{input}} / {{textarea}} are user-imported bindings, not
46+
// the classic Ember helpers — skip the mustache-form check.
47+
{ filename: 'layout.gjs', code: '<template>{{input}}</template>' },
48+
{ filename: 'layout.gts', code: '<template>{{textarea}}</template>' },
49+
// Built-in <Input> imported from @ember/component, wrapped in a label.
50+
{
51+
filename: 'layout.gjs',
52+
code: "import { Input } from '@ember/component';\n<template><label>Name <Input /></label></template>",
53+
},
4354
{
4455
code: '<template><CustomLabel><input /></CustomLabel></template>',
4556
options: [{ labelTags: ['CustomLabel'] }],
@@ -125,6 +136,20 @@ ruleTester.run('template-require-input-label', rule, {
125136
output: null,
126137
errors: [{ message: MULTIPLE_LABELS }],
127138
},
139+
// Built-in <Input> imported from @ember/component in GJS → flagged.
140+
{
141+
filename: 'layout.gjs',
142+
code: "import { Input } from '@ember/component';\n<template><Input /></template>",
143+
output: null,
144+
errors: [{ message: NO_LABEL }],
145+
},
146+
// Renamed import of <Textarea> from @ember/component in GTS → flagged.
147+
{
148+
filename: 'layout.gts',
149+
code: "import { Textarea as TA } from '@ember/component';\n<template><TA /></template>",
150+
output: null,
151+
errors: [{ message: NO_LABEL }],
152+
},
128153
],
129154
});
130155

0 commit comments

Comments
 (0)