Skip to content

Commit 87cfc83

Browse files
committed
Fix template-no-at-ember-render-modifiers: detect GJS/GTS imports
In GJS/GTS, @ember/render-modifiers is consumed via per-modifier default imports (e.g. 'import didInsert from "@ember/render-modifiers/modifiers/did-insert"'). The local name is user-chosen, so the rule can't match on a hardcoded kebab-case list. Track ImportDeclaration nodes in strict-mode files and flag modifier usages based on the local import name. HBS behavior is unchanged: the canonical kebab-case names (did-insert, did-update, will-destroy) are still flagged via resolver-style matching, matching upstream ember-template-lint.
1 parent b705850 commit 87cfc83

File tree

2 files changed

+94
-8
lines changed

2 files changed

+94
-8
lines changed

lib/rules/template-no-at-ember-render-modifiers.js

Lines changed: 50 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,13 @@
1+
// Map of `@ember/render-modifiers` sub-path → canonical kebab-case modifier name.
2+
// Default-imported names are user-chosen, so we track local names to canonical.
3+
const RENDER_MODIFIER_IMPORT_PATHS = {
4+
'@ember/render-modifiers/modifiers/did-insert': 'did-insert',
5+
'@ember/render-modifiers/modifiers/did-update': 'did-update',
6+
'@ember/render-modifiers/modifiers/will-destroy': 'will-destroy',
7+
};
8+
9+
const KEBAB_NAMES = new Set(['did-insert', 'did-update', 'will-destroy']);
10+
111
/** @type {import('eslint').Rule.RuleModule} */
212
module.exports = {
313
meta: {
@@ -23,24 +33,56 @@ module.exports = {
2333
},
2434

2535
create(context) {
36+
const filename = context.filename;
37+
const isStrictMode = filename.endsWith('.gjs') || filename.endsWith('.gts');
38+
39+
// local import name → canonical kebab name. Only populated in GJS/GTS.
40+
const importedModifiers = new Map();
41+
42+
function isRenderModifier(name) {
43+
if (isStrictMode) {
44+
return importedModifiers.has(name);
45+
}
46+
// HBS: resolver-resolved by canonical kebab name
47+
return KEBAB_NAMES.has(name);
48+
}
49+
50+
function canonicalName(name) {
51+
return importedModifiers.get(name) || name;
52+
}
53+
2654
return {
55+
ImportDeclaration(node) {
56+
if (!isStrictMode) {
57+
return;
58+
}
59+
const canonical = RENDER_MODIFIER_IMPORT_PATHS[node.source.value];
60+
if (!canonical) {
61+
return;
62+
}
63+
// Default import (`import didInsert from '...'`) is the supported form
64+
for (const specifier of node.specifiers) {
65+
if (specifier.type === 'ImportDefaultSpecifier') {
66+
importedModifiers.set(specifier.local.name, canonical);
67+
}
68+
}
69+
},
70+
2771
GlimmerElementNode(node) {
2872
if (!node.modifiers) {
2973
return;
3074
}
3175

3276
for (const modifier of node.modifiers) {
33-
if (
34-
modifier.path &&
35-
modifier.path.type === 'GlimmerPathExpression' &&
36-
(modifier.path.original === 'did-insert' ||
37-
modifier.path.original === 'did-update' ||
38-
modifier.path.original === 'will-destroy')
39-
) {
77+
if (modifier.path?.type !== 'GlimmerPathExpression') {
78+
continue;
79+
}
80+
const name = modifier.path.original;
81+
if (isRenderModifier(name)) {
4082
context.report({
4183
node: modifier,
4284
messageId: 'noRenderModifier',
43-
data: { modifier: modifier.path.original },
85+
data: { modifier: canonicalName(name) },
4486
});
4587
}
4688
}

tests/lib/rules/template-no-at-ember-render-modifiers.js

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,19 @@ ruleTester.run('template-no-at-ember-render-modifiers', rule, {
3232
'<template>{{did-insert}}</template>',
3333
'<template>{{did-update}}</template>',
3434
'<template>{{will-destroy}}</template>',
35+
36+
// In GJS/GTS, kebab identifiers cannot be imports; these are bare paths
37+
// that happen to share the canonical name but are not the render modifier.
38+
{
39+
filename: 'test.gjs',
40+
code: '<template><div {{did-insert this.setup}}></div></template>',
41+
},
42+
// Unrelated imports with matching local names should not match
43+
{
44+
filename: 'test.gjs',
45+
code: `import didInsert from './my-lib';
46+
<template><div {{didInsert this.setup}}></div></template>`,
47+
},
3548
],
3649

3750
invalid: [
@@ -87,6 +100,37 @@ ruleTester.run('template-no-at-ember-render-modifiers', rule, {
87100
output: null,
88101
errors: [{ messageId: 'noRenderModifier' }],
89102
},
103+
104+
// GJS/GTS import-based forms — local name is user-chosen
105+
{
106+
filename: 'test.gjs',
107+
code: `import didInsert from '@ember/render-modifiers/modifiers/did-insert';
108+
<template><div {{didInsert this.setup}}></div></template>`,
109+
output: null,
110+
errors: [{ messageId: 'noRenderModifier' }],
111+
},
112+
{
113+
filename: 'test.gjs',
114+
code: `import didUpdate from '@ember/render-modifiers/modifiers/did-update';
115+
<template><div {{didUpdate this.update}}></div></template>`,
116+
output: null,
117+
errors: [{ messageId: 'noRenderModifier' }],
118+
},
119+
{
120+
filename: 'test.gjs',
121+
code: `import willDestroy from '@ember/render-modifiers/modifiers/will-destroy';
122+
<template><div {{willDestroy this.cleanup}}></div></template>`,
123+
output: null,
124+
errors: [{ messageId: 'noRenderModifier' }],
125+
},
126+
// Renamed default import still flags
127+
{
128+
filename: 'test.gjs',
129+
code: `import myInsert from '@ember/render-modifiers/modifiers/did-insert';
130+
<template><div {{myInsert this.setup}}></div></template>`,
131+
output: null,
132+
errors: [{ messageId: 'noRenderModifier' }],
133+
},
90134
],
91135
});
92136

0 commit comments

Comments
 (0)