fix(name): set or default the `name` property on functions and classes
Also, more consistent rewriting of `export default` FunctionDeclaration
and ClassDeclaration.
Closes closes and closes
MODIFY
REWRITE.md
REWRITE.md
@@ -74,10 +74,25 @@ export function fn() {
```
```js
-$h_live.fn(); // hoisted decl (no tdz)
+// Rename function and hoist proxy assignment.
+Object.defineProperty($c_fn, 'name', { value: 'fn' }); \
+$h_live.fn($c_fn); \
...
function $c_fn() {
...
-} \
-fn = $c_fn;
+}
+```
+
+## export default
+
+In order to properly assign the 'default' name to exported anonymous functions and classes, we rewrite:
+
+```js
+export default XXX;
+```
+
+to:
+
+```js
+const {default: $c_default} = {default: (XXX)}; $h_once.default($c_default);
```

MODIFY
src/babelPlugin.js
src/babelPlugin.js
@@ -260,49 +260,35 @@ function makeModulePlugins(options) {
fixedExportMap.default = ['default'];
}
if (doTransform) {
+ const id = t.identifier('default');
+ const cid = t.identifier('default');
+ soften(cid);
const callee = t.memberExpression(
hiddenIdentifier(h.HIDDEN_ONCE),
- t.identifier('default'),
+ id,
);
- let decl = path.node.declaration;
- if (decl.type === 'ClassDeclaration') {
- decl = t.classExpression(decl.id, decl.superClass, decl.body);
- if (!decl.id) {
- // We need to override the class's empty name with `default`.
- const id = t.identifier(`${h.HIDDEN_CONST_VAR_PREFIX}default`);
- allowedHiddens.add(id);
+ let expr = path.node.declaration;
+ if (expr.type === 'ClassDeclaration') {
+ expr = t.classExpression(expr.id, expr.superClass, expr.body);
+ } else if (expr.type === 'FunctionDeclaration') {
+ expr = t.functionExpression(
+ expr.id,
+ expr.params,
+ expr.body,
+ expr.generator,
+ expr.async,
+ );
+ }
+ // const {default: $c_default} = {default: (XXX)}; $h_once.default($c_default);
path.replaceWithMultiple([
t.variableDeclaration('const', [
- t.variableDeclarator(id, decl),
- ]),
- t.expressionStatement(
- t.callExpression(
- t.memberExpression(
- t.identifier('Object'),
- t.identifier('defineProperty'),
- ),
- [
- id,
- t.stringLiteral('name'),
- t.objectExpression([
- t.objectProperty(
- t.stringLiteral('value'),
- t.stringLiteral('default'),
- ),
- t.objectProperty(
- t.stringLiteral('writable'),
- t.booleanLiteral(false),
+ t.variableDeclarator(
+ t.objectPattern([t.objectProperty(id, cid)]),
+ t.objectExpression([t.objectProperty(id, expr)]),
),
]),
- ],
- ),
- ),
- t.expressionStatement(t.callExpression(callee, [id])),
+ t.expressionStatement(t.callExpression(callee, [cid])),
]);
- return;
- }
- }
- path.replaceWith(t.callExpression(callee, [decl]));
}
},
ClassDeclaration(path) {

MODIFY
src/index.js
src/index.js
@@ -118,7 +118,17 @@ const makeModuleTransformer = (babelCore, importer) => {
)
.join(',')}]), ${js(sourceOptions.exportAlls)});`;
preamble += sourceOptions.hoistedDecls
- .map(([vname, cvname]) => `${h.HIDDEN_LIVE}.${vname}(${cvname || ''});`)
+ .map(([vname, cvname]) => {
+ let src = '';
+ if (cvname) {
+ // It's a function, so set its name property.
+ src = `Object.defineProperty(${cvname}, 'name', {value: ${js(
+ vname,
+ )}});`;
+ }
+ src += `${h.HIDDEN_LIVE}.${vname}(${cvname || ''});`;
+ return src;
+ })
.join('');
// The functor captures the SES `arguments`, which is definitely

MODIFY
test/test-export-default.js
test/test-export-default.js
@@ -55,6 +55,12 @@ test('export default', async t => {
`endowed modules`,
);
+ const { default: Cls } = await evaluateModule(`\
+export default class { valueOf() { return 45; } }
+`);
+ t.equal(Cls.name, 'default', `default class export is stamped`);
+ t.equal(new Cls().valueOf(), 45, `valueOf returns properly`);
+
const ns = await evaluateModule(`\
export default arguments;`);
t.equal(typeof ns.default, 'object', 'arguments is an object');

MODIFY
test/test-export-named.js
test/test-export-named.js
@@ -149,7 +149,7 @@ export const fn3 = fn;
test(`export class`, async t => {
try {
- const importer = makeImporter(['C', 'count']);
+ const importer = makeImporter(['C', 'F', 'count']);
const transforms = [makeModuleTransformer(babelCore, importer)];
const { evaluateModule } = makeEvaluators({
transforms,
@@ -160,6 +160,7 @@ export let count = 0;
export class C {} if (C) { count += 1; }
`);
t.assert(new C(), `class exports`);
+ t.equal(C.name, 'C', `class is named C`);
t.equal(count, 1, `class C is global`);
const { default: C2 } = await evaluateModule(`\
@@ -173,6 +174,30 @@ export default class {}
`);
t.assert(new C3(), `default class constructs`);
t.equal(C3.name, 'default', `default class name`);
+ const { default: C4 } = await evaluateModule(`\
+export default (class {});
+`);
+ t.assert(new C4(), `default class expression constructs`);
+ t.equal(C4.name, 'default', `default class expression name`);
+
+ const { F: F0 } = await evaluateModule(`\
+F(123);
+export function F(arg) { return arg; }
+`);
+ t.equal(F0.name, 'F', `F function name`);
+
+ const { default: F } = await evaluateModule(`\
+export default async function F(arg) { return arg; }
+`);
+ t.equal(F.name, 'F', `F function name`);
+ const ret = F('foo');
+ t.assert(ret instanceof Promise, `F is async`);
+ t.equal(await ret, 'foo', `F returns correctly`);
+
+ const { default: F2 } = await evaluateModule(`\
+export default async function(arg) { return arg; };
+`);
+ t.equal(F2.name, 'default', `F2 function default name`);
} catch (e) {
console.log('unexpected exception', e);
t.assert(false, e);

fix(name): set or default the `name` property on functions and classes

Also, more consistent rewriting of `export default` FunctionDeclaration
and ClassDeclaration.

Closes #6, closes #7, and closes #9