Skip to content

Commit 679c190

Browse files
author
Robert Jackson
committed
Ensure decorators provided by Ember are handled when the identifier is aliased.
tldr; decorator paths are not always included in `path.scope.getBinding(local.name)` In some circumstances, decorators are not included in the reference paths for a local binding when the decorator identifier name is also defined _within_ the method being decorated. This is likely a bug in Babel, that should be reported and fixed.
1 parent 3e07785 commit 679c190

2 files changed

Lines changed: 50 additions & 6 deletions

File tree

__tests__/index-test.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -444,16 +444,16 @@ export default class MyController extends Controller {
444444
['@babel/plugin-proposal-decorators', { legacy: true }],
445445
]);
446446

447-
expect(actual)
448-
.toEqual(`var _class;
447+
expect(actual).toEqual(`var _dec, _class;
449448
450449
function _applyDecoratedDescriptor(target, property, decorators, descriptor, context) { var desc = {}; Object.keys(descriptor).forEach(function (key) { desc[key] = descriptor[key]; }); desc.enumerable = !!desc.enumerable; desc.configurable = !!desc.configurable; if ('value' in desc || desc.initializer) { desc.writable = true; } desc = decorators.slice().reverse().reduce(function (desc, decorator) { return decorator(target, property, desc) || desc; }, desc); if (context && desc.initializer !== void 0) { desc.value = desc.initializer ? desc.initializer.call(context) : void 0; desc.initializer = undefined; } if (desc.initializer === void 0) { Object.defineProperty(target, property, desc); desc = null; } return desc; }
451450
452-
let MyController = (_class = class MyController extends Ember.Controller {
451+
let MyController = (_dec = Ember._action, (_class = class MyController extends Ember.Controller {
453452
addAction(action) {
454453
this.actions.pushObject(action);
455454
}
456-
}, (_applyDecoratedDescriptor(_class.prototype, "addAction", [Ember._action], Object.getOwnPropertyDescriptor(_class.prototype, "addAction"), _class.prototype)), _class);
455+
456+
}, (_applyDecoratedDescriptor(_class.prototype, "addAction", [_dec], Object.getOwnPropertyDescriptor(_class.prototype, "addAction"), _class.prototype)), _class));
457457
export { MyController as default };`);
458458
});
459459
});

src/index.js

Lines changed: 46 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,24 @@ function isIgnored(ignore, importPath, exportName) {
1313
}
1414
}
1515

16+
function isDecorator(moduleName, importName) {
17+
switch (moduleName) {
18+
case '@ember/service':
19+
return importName === 'inject';
20+
case '@ember/controller':
21+
return importName === 'inject';
22+
case '@glimmer/tracking':
23+
return importName === 'tracked';
24+
case '@ember/object/compat':
25+
return importName === 'dependentKeyCompat';
26+
case '@ember/object':
27+
return ['action', 'computed'].includes(importName);
28+
case '@ember/object/computed':
29+
// only the default import of this module is not a decorator
30+
return importName !== 'default';
31+
}
32+
}
33+
1634
module.exports = function (babel) {
1735
const t = babel.types;
1836

@@ -165,10 +183,36 @@ module.exports = function (babel) {
165183
])
166184
);
167185
} else {
168-
// Replace the occurences of the imported name with the global name.
169186
let binding = path.scope.getBinding(local.name);
187+
let referencePaths = binding.referencePaths;
188+
189+
if (isDecorator(importPath, importName)) {
190+
// tldr; decorator paths are not always included in `path.scope.getBinding(local.name)`
191+
//
192+
// In some circumstances, decorators are not included in the
193+
// reference paths for a local binding when the decorator
194+
// identifier name is also defined _within_ the method being
195+
// decorated. This is likely a bug in Babel, that should be
196+
// reported and fixed.
197+
//
198+
// in order to fix that, we have to manually traverse to gather
199+
// the decorator references **before** the
200+
// @babel/plugin-proposal-decorators runs (because it removes
201+
// them)
202+
path.parentPath.traverse({
203+
Decorator(decoratorPath) {
204+
if (
205+
decoratorPath.node.expression.type === 'Identifier' &&
206+
decoratorPath.node.expression.name === local.name
207+
) {
208+
referencePaths.push(decoratorPath.get('expression'));
209+
}
210+
},
211+
});
212+
}
170213

171-
binding.referencePaths.forEach((referencePath) => {
214+
// Replace the occurences of the imported name with the global name.
215+
referencePaths.forEach((referencePath) => {
172216
if (!isTypescriptNode(referencePath.parentPath)) {
173217
referencePath.replaceWith(getMemberExpressionFor(global));
174218
}

0 commit comments

Comments
 (0)