Skip to content

Commit a009cd1

Browse files
Merge pull request #2664 from johanrd/night_fix/template-no-at-ember-render-modifiers
Post-merge-review: Fix `template-no-at-ember-render-modifiers`: detect GJS/GTS imports
2 parents 1bec1a2 + 82efbe4 commit a009cd1

File tree

2 files changed

+154
-8
lines changed

2 files changed

+154
-8
lines changed

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

Lines changed: 73 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,20 @@
1+
// Sub-path → canonical kebab-case modifier name (default import).
2+
const RENDER_MODIFIER_IMPORT_PATHS = {
3+
'@ember/render-modifiers/modifiers/did-insert': 'did-insert',
4+
'@ember/render-modifiers/modifiers/did-update': 'did-update',
5+
'@ember/render-modifiers/modifiers/will-destroy': 'will-destroy',
6+
};
7+
8+
// Named exports of the root package `@ember/render-modifiers` → canonical kebab name.
9+
const ROOT_NAMED_EXPORTS = {
10+
didInsert: 'did-insert',
11+
didUpdate: 'did-update',
12+
willDestroy: 'will-destroy',
13+
};
14+
15+
const KEBAB_NAMES = new Set(['did-insert', 'did-update', 'will-destroy']);
16+
const ROOT_PACKAGE = '@ember/render-modifiers';
17+
118
/** @type {import('eslint').Rule.RuleModule} */
219
module.exports = {
320
meta: {
@@ -23,24 +40,72 @@ module.exports = {
2340
},
2441

2542
create(context) {
43+
const filename = context.filename;
44+
const isStrictMode = filename.endsWith('.gjs') || filename.endsWith('.gts');
45+
46+
// local import name → canonical kebab name. Only populated in GJS/GTS.
47+
const importedModifiers = new Map();
48+
49+
function isRenderModifier(name) {
50+
if (isStrictMode) {
51+
return importedModifiers.has(name);
52+
}
53+
// HBS: resolver-resolved by canonical kebab name
54+
return KEBAB_NAMES.has(name);
55+
}
56+
57+
function canonicalName(name) {
58+
return importedModifiers.get(name) || name;
59+
}
60+
2661
return {
62+
ImportDeclaration(node) {
63+
if (!isStrictMode) {
64+
return;
65+
}
66+
const source = node.source.value;
67+
68+
if (source === ROOT_PACKAGE) {
69+
// `import { didInsert, didUpdate as x } from '@ember/render-modifiers'`
70+
for (const specifier of node.specifiers) {
71+
if (specifier.type === 'ImportSpecifier') {
72+
const exportedName = specifier.imported.name;
73+
const canonical = ROOT_NAMED_EXPORTS[exportedName];
74+
if (canonical) {
75+
importedModifiers.set(specifier.local.name, canonical);
76+
}
77+
}
78+
}
79+
return;
80+
}
81+
82+
// Sub-path: `import didInsert from '@ember/render-modifiers/modifiers/did-insert'`
83+
const canonical = RENDER_MODIFIER_IMPORT_PATHS[source];
84+
if (!canonical) {
85+
return;
86+
}
87+
for (const specifier of node.specifiers) {
88+
if (specifier.type === 'ImportDefaultSpecifier') {
89+
importedModifiers.set(specifier.local.name, canonical);
90+
}
91+
}
92+
},
93+
2794
GlimmerElementNode(node) {
2895
if (!node.modifiers) {
2996
return;
3097
}
3198

3299
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-
) {
100+
if (modifier.path?.type !== 'GlimmerPathExpression') {
101+
continue;
102+
}
103+
const name = modifier.path.original;
104+
if (isRenderModifier(name)) {
40105
context.report({
41106
node: modifier,
42107
messageId: 'noRenderModifier',
43-
data: { modifier: modifier.path.original },
108+
data: { modifier: canonicalName(name) },
44109
});
45110
}
46111
}

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

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,25 @@ 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+
},
48+
// Root-package import of an unknown named export is not a render modifier
49+
{
50+
filename: 'test.gjs',
51+
code: `import { somethingElse } from '@ember/render-modifiers';
52+
<template><div {{somethingElse this.setup}}></div></template>`,
53+
},
3554
],
3655

3756
invalid: [
@@ -87,6 +106,68 @@ ruleTester.run('template-no-at-ember-render-modifiers', rule, {
87106
output: null,
88107
errors: [{ messageId: 'noRenderModifier' }],
89108
},
109+
110+
// GJS/GTS import-based forms — local name is user-chosen
111+
{
112+
filename: 'test.gjs',
113+
code: `import didInsert from '@ember/render-modifiers/modifiers/did-insert';
114+
<template><div {{didInsert this.setup}}></div></template>`,
115+
output: null,
116+
errors: [{ messageId: 'noRenderModifier' }],
117+
},
118+
{
119+
filename: 'test.gjs',
120+
code: `import didUpdate from '@ember/render-modifiers/modifiers/did-update';
121+
<template><div {{didUpdate this.update}}></div></template>`,
122+
output: null,
123+
errors: [{ messageId: 'noRenderModifier' }],
124+
},
125+
{
126+
filename: 'test.gjs',
127+
code: `import willDestroy from '@ember/render-modifiers/modifiers/will-destroy';
128+
<template><div {{willDestroy this.cleanup}}></div></template>`,
129+
output: null,
130+
errors: [{ messageId: 'noRenderModifier' }],
131+
},
132+
// Renamed default import still flags
133+
{
134+
filename: 'test.gjs',
135+
code: `import myInsert from '@ember/render-modifiers/modifiers/did-insert';
136+
<template><div {{myInsert this.setup}}></div></template>`,
137+
output: null,
138+
errors: [{ messageId: 'noRenderModifier' }],
139+
},
140+
141+
// Root-package named imports — all three modifiers
142+
{
143+
filename: 'test.gjs',
144+
code: `import { didInsert } from '@ember/render-modifiers';
145+
<template><div {{didInsert this.setup}}></div></template>`,
146+
output: null,
147+
errors: [{ messageId: 'noRenderModifier' }],
148+
},
149+
{
150+
filename: 'test.gjs',
151+
code: `import { didUpdate } from '@ember/render-modifiers';
152+
<template><div {{didUpdate this.update}}></div></template>`,
153+
output: null,
154+
errors: [{ messageId: 'noRenderModifier' }],
155+
},
156+
{
157+
filename: 'test.gjs',
158+
code: `import { willDestroy } from '@ember/render-modifiers';
159+
<template><div {{willDestroy this.cleanup}}></div></template>`,
160+
output: null,
161+
errors: [{ messageId: 'noRenderModifier' }],
162+
},
163+
// Aliased root-package import still flags
164+
{
165+
filename: 'test.gjs',
166+
code: `import { didInsert as myModifier } from '@ember/render-modifiers';
167+
<template><div {{myModifier this.setup}}></div></template>`,
168+
output: null,
169+
errors: [{ messageId: 'noRenderModifier' }],
170+
},
90171
],
91172
});
92173

0 commit comments

Comments
 (0)