Skip to content

Commit b387b01

Browse files
authored
Merge pull request #81 from danwenzel/multi-level-destructuring
Support multi-statement destructuring of Ember globals
2 parents 0262e3e + 0009c5c commit b387b01

3 files changed

Lines changed: 105 additions & 5 deletions

File tree

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import Ember from 'ember';
2+
3+
const { computed, inject, String } = Ember;
4+
const { oneWay } = computed;
5+
const { service } = inject;
6+
const { camelize } = String;
7+
8+
export default Ember.Component.extend({
9+
barService: service('bar'),
10+
name: oneWay('userName'),
11+
foo() {
12+
camelize('bar');
13+
}
14+
});
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import Component from '@ember/component';
2+
import { camelize } from '@ember/string';
3+
import { inject as service } from '@ember/service';
4+
import { oneWay } from '@ember/object/computed';
5+
6+
export default Component.extend({
7+
barService: service('bar'),
8+
name: oneWay('userName'),
9+
foo() {
10+
camelize('bar');
11+
}
12+
});

transform.js

Lines changed: 79 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ function transform(file, api/*, options*/) {
3434
// use this at the end to generate a report.
3535
let warnings = [];
3636

37+
let pendingGlobals = {};
38+
3739
try {
3840
// Discover existing module imports, if any, in the file. If the user has
3941
// already imported one or more exports that we rewrite a global with, we
@@ -62,6 +64,10 @@ function transform(file, api/*, options*/) {
6264
// e.g. `const { String: { underscore } } = Ember;`.
6365
let globalAliases = findGlobalEmberAliases(root, globalEmber, mappings);
6466

67+
// Go through all of the tracked pending Ember globals. The ones that have
68+
// been marked as missing should be added to the warnings.
69+
resolvePendingGlobals();
70+
6571
// Resolve the discovered aliases against the module registry. We intentionally do
6672
// this ahead of finding replacements for e.g. `Ember.String.underscore` usage in
6773
// order to reuse custom names for any fields referenced both ways.
@@ -72,7 +78,6 @@ function transform(file, api/*, options*/) {
7278
// mappings, save it off for replacement later.
7379
let replacements = findUsageOfEmberGlobal(root, globalEmber)
7480
.map(findReplacement(mappings));
75-
7681
// add the already found namespace replacements to our replacement array
7782
for (let ns of namespaceUsages) {
7883
let namespaceReplacements = ns.usages
@@ -188,9 +193,20 @@ function transform(file, api/*, options*/) {
188193
}
189194

190195
function findUsageOfDestructuredEmber(root, globalEmber) {
196+
// Keep track of the nested properties off of the Ember namespace,
197+
// to support multi-statement destructuring, i.e.:
198+
// const { computed } = Ember;
199+
// const { oneWay } = computed;
200+
let globalEmberWithNestedProperties = [globalEmber];
191201
let uses = root.find(j.VariableDeclarator, (node) => {
192202
if (j.Identifier.check(node.init)) {
193-
return node.init.name === globalEmber;
203+
if (includes(globalEmberWithNestedProperties, node.init.name)) {
204+
// We've found an Ember global, or one of its nested properties.
205+
// Add it to the uses, and add its properties to the list of nested properties
206+
const identifierProperties = getIdentifierProperties(node);
207+
globalEmberWithNestedProperties = globalEmberWithNestedProperties.concat(identifierProperties);
208+
return true;
209+
}
194210
} else if (j.MemberExpression.check(node.init)) {
195211
return node.init.object.name === globalEmber;
196212
}
@@ -199,6 +215,29 @@ function transform(file, api/*, options*/) {
199215
return uses.paths();
200216
}
201217

218+
function resolvePendingGlobals() {
219+
Object.keys(pendingGlobals).forEach((key) => {
220+
let pendingGlobal = pendingGlobals[key];
221+
const parentPath = pendingGlobal.pattern.parentPath;
222+
if (!pendingGlobal.hasMissingGlobal) {
223+
parentPath.prune();
224+
} else {
225+
warnMissingGlobal(parentPath, pendingGlobal.emberPath);
226+
}
227+
})
228+
}
229+
230+
function getIdentifierProperties(node) {
231+
let identifierProperties = [];
232+
node.id.properties.forEach((property) => {
233+
if (j.Identifier.check(property.value)) {
234+
identifierProperties.push(property.key.name);
235+
}
236+
});
237+
238+
return identifierProperties;
239+
}
240+
202241
function joinEmberPath(nodePath, globalEmber) {
203242
if (j.Identifier.check(nodePath.node)) {
204243
if (nodePath.node.name !== globalEmber) {
@@ -217,16 +256,38 @@ function transform(file, api/*, options*/) {
217256

218257
// Determine aliases introduced by the given destructuring pattern, removing
219258
// items from the pattern when they're available via a module import instead.
259+
// Also tracks and flags pending globals for future patterns,
260+
// in case we have multi-statement destructuring, i.e:
261+
// const { computed } = Ember;
262+
// const { oneWay } = computed;
220263
function extractAliases(mappings, pattern, emberPath) {
221264
if (j.Identifier.check(pattern.node)) {
222265
if (emberPath in mappings) {
223266
pattern.parentPath.prune();
267+
const pendingGlobalParent = findPendingGlobal(emberPath);
268+
if (pendingGlobalParent) {
269+
// A parent has been found. Mark it as no longer being missing.
270+
pendingGlobalParent.hasMissingGlobal = false;
271+
}
272+
224273
return [new GlobalAlias(pattern, emberPath)];
225274
} else {
226-
// skip warnings for destructured namespaces, these will be handled elsewhere
227-
if (!includes(EMBER_NAMESPACES, emberPath)) {
228-
warnMissingGlobal(pattern.parentPath, emberPath);
275+
let thisPatternHasMissingGlobal = false;
276+
const pendingGlobalParent = findPendingGlobal(emberPath);
277+
if (pendingGlobalParent) {
278+
// A parent has been found. Mark it as a missing global.
279+
pendingGlobalParent.hasMissingGlobal = true;
280+
} else {
281+
// Otherwise, mark this pattern as a missing global.
282+
thisPatternHasMissingGlobal = true;
229283
}
284+
285+
// Add this pattern to pendingGlobals
286+
pendingGlobals[pattern.node.name] = {
287+
pattern,
288+
emberPath,
289+
hasMissingGlobal: thisPatternHasMissingGlobal
290+
};
230291
}
231292
} else if (j.ObjectPattern.check(pattern.node)) {
232293
let aliases = findObjectPatternAliases(mappings, pattern, emberPath);
@@ -239,6 +300,19 @@ function transform(file, api/*, options*/) {
239300
return [];
240301
}
241302

303+
function findPendingGlobal(emberPath) {
304+
if (!emberPath) {
305+
return;
306+
}
307+
const paths = emberPath.split('.');
308+
for (let idx = 0; idx < paths.length; idx++) {
309+
const path = paths[idx];
310+
if (pendingGlobals[path]) {
311+
return pendingGlobals[path];
312+
}
313+
}
314+
}
315+
242316
function findObjectPatternAliases(mappings, objectPattern, basePath) {
243317
let aliases = [];
244318
for (let i = objectPattern.node.properties.length - 1; i >= 0; i--) {

0 commit comments

Comments
 (0)