Skip to content

Commit 4a75afe

Browse files
Geodewd549bmish
andauthored
fix: no-runloop: catch namespace imports (#2315)
* fix: no-runloop: catch namespace imports Let's update the `no-runloop` rule to flag usages of `@ember/runloop` methods when imported via namespace import (e.g. `import * as runloop from '@ember/runloop'`) and used via member expressions (e.g. `runloop.later(), runloop.schedule(), etc.). This fixes: #2263. * Update tests/lib/rules/no-runloop.js --------- Co-authored-by: Bryan Mishkin <[email protected]>
1 parent 6106b57 commit 4a75afe

File tree

2 files changed

+52
-22
lines changed

2 files changed

+52
-22
lines changed

lib/rules/no-runloop.js

Lines changed: 39 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,11 @@ module.exports = {
7676
const allowList = context.options[0]?.allowList ?? [];
7777
// Maps local names to imported names of imports
7878
const localToImportedNameMap = new Map();
79+
let namespaceImportName;
80+
81+
const isDisallowed = function (fn) {
82+
return EMBER_RUNLOOP_FUNCTIONS.includes(fn) && !allowList.includes(fn);
83+
};
7984

8085
/**
8186
* Reports a node with usage of a disallowed runloop function
@@ -109,38 +114,50 @@ module.exports = {
109114
if (EMBER_RUNLOOP_FUNCTIONS.includes(importedName)) {
110115
localToImportedNameMap.set(spec.local.name, importedName);
111116
}
117+
} else if (spec.type === 'ImportNamespaceSpecifier') {
118+
namespaceImportName = spec.local.name;
112119
}
113120
}
114121
}
115122
},
116123

117124
CallExpression(node) {
125+
const callee = node.callee;
126+
118127
// Examples: run(...), later(...)
119-
if (node.callee.type === 'Identifier') {
120-
const name = node.callee.name;
121-
const runloopFn = localToImportedNameMap.get(name);
122-
const isNotAllowed = runloopFn && !allowList.includes(runloopFn);
123-
if (isNotAllowed) {
124-
report(node, runloopFn, name);
128+
if (callee.type === 'Identifier') {
129+
const localName = callee.name;
130+
const runloopFn = localToImportedNameMap.get(localName);
131+
132+
if (runloopFn && isDisallowed(runloopFn)) {
133+
report(node, runloopFn, localName);
125134
}
135+
136+
return;
126137
}
127138

128-
// runloop functions (aside from run itself) can chain onto `run`, so we need to check for this
129-
// Examples: run.later(...), run.schedule(...)
130-
if (node.callee.type === 'MemberExpression' && node.callee.object?.type === 'Identifier') {
131-
const objectName = node.callee.object.name;
132-
const objectRunloopFn = localToImportedNameMap.get(objectName);
133-
134-
if (objectRunloopFn === 'run' && node.callee.property?.type === 'Identifier') {
135-
const runloopFn = node.callee.property.name;
136-
137-
if (
138-
EMBER_RUNLOOP_FUNCTIONS.includes(runloopFn) &&
139-
runloopFn !== 'run' &&
140-
!allowList.includes(runloopFn)
141-
) {
142-
report(node, runloopFn, `${objectName}.${runloopFn}`);
143-
}
139+
if (
140+
callee.type === 'MemberExpression' &&
141+
callee.object.type === 'Identifier' &&
142+
callee.property.type === 'Identifier'
143+
) {
144+
const objectName = callee.object.name;
145+
const methodName = callee.property.name;
146+
147+
// runloop functions (aside from run itself) can chain onto `run`, so we need to check for this
148+
// Examples: run.later(...), run.schedule(...)
149+
if (
150+
localToImportedNameMap.get(objectName) === 'run' &&
151+
isDisallowed(methodName) &&
152+
methodName !== 'run'
153+
) {
154+
report(node, methodName, `${objectName}.${methodName}`);
155+
return;
156+
}
157+
158+
// Example: `import * as runloop from '@ember/runloop'` -> `runloop.later(...)`
159+
if (objectName === namespaceImportName && isDisallowed(methodName)) {
160+
report(node, methodName, `${objectName}.${methodName}`);
144161
}
145162
}
146163
},

tests/lib/rules/no-runloop.js

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,5 +222,18 @@ eslintTester.run('no-runloop', rule, {
222222
},
223223
],
224224
},
225+
{
226+
code: `
227+
import * as run from '@ember/runloop';
228+
run.later();
229+
`,
230+
output: null,
231+
errors: [
232+
{
233+
messageId: 'lifelineReplacement',
234+
type: 'CallExpression',
235+
},
236+
],
237+
},
225238
],
226239
});

0 commit comments

Comments
 (0)