Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for arbitrary module namespace identifiers #4770

Merged
merged 2 commits into from Dec 28, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
46 changes: 26 additions & 20 deletions src/Module.ts
Expand Up @@ -9,12 +9,13 @@ import { nodeConstructors } from './ast/nodes';
import ExportAllDeclaration from './ast/nodes/ExportAllDeclaration';
import ExportDefaultDeclaration from './ast/nodes/ExportDefaultDeclaration';
import type ExportNamedDeclaration from './ast/nodes/ExportNamedDeclaration';
import type Identifier from './ast/nodes/Identifier';
import Identifier from './ast/nodes/Identifier';
import type ImportDeclaration from './ast/nodes/ImportDeclaration';
import ImportDefaultSpecifier from './ast/nodes/ImportDefaultSpecifier';
import type ImportExpression from './ast/nodes/ImportExpression';
import ImportNamespaceSpecifier from './ast/nodes/ImportNamespaceSpecifier';
import Literal from './ast/nodes/Literal';
import type MetaProperty from './ast/nodes/MetaProperty';
import * as NodeType from './ast/nodes/NodeType';
import Program from './ast/nodes/Program';
import TemplateLiteral from './ast/nodes/TemplateLiteral';
import VariableDeclaration from './ast/nodes/VariableDeclaration';
Expand Down Expand Up @@ -880,26 +881,26 @@ export default class Module {
return localVariable;
}

const importDeclaration = this.importDescriptions.get(name);
if (importDeclaration) {
const otherModule = importDeclaration.module;
const importDescription = this.importDescriptions.get(name);
if (importDescription) {
const otherModule = importDescription.module;

if (otherModule instanceof Module && importDeclaration.name === '*') {
if (otherModule instanceof Module && importDescription.name === '*') {
return otherModule.namespace;
}

const [declaration] = getVariableForExportNameRecursive(
otherModule,
importDeclaration.name,
importDescription.name,
importerForSideEffects || this,
isExportAllSearch,
searchedNamesAndModules
);

if (!declaration) {
return this.error(
errorMissingExport(importDeclaration.name, this.id, otherModule.id),
importDeclaration.start
errorMissingExport(importDescription.name, this.id, otherModule.id),
importDescription.start
);
}

Expand Down Expand Up @@ -983,13 +984,13 @@ export default class Module {

const source = node.source.value;
this.addSource(source, node);
for (const specifier of node.specifiers) {
const name = specifier.exported.name;
for (const { exported, local, start } of node.specifiers) {
const name = exported instanceof Literal ? exported.value : exported.name;
this.reexportDescriptions.set(name, {
localName: specifier.local.name,
localName: local instanceof Literal ? local.value : local.name,
module: null as never, // filled in later,
source,
start: specifier.start
start
});
}
} else if (node.declaration) {
Expand All @@ -1012,9 +1013,10 @@ export default class Module {
} else {
// export { foo, bar, baz }

for (const specifier of node.specifiers) {
const localName = specifier.local.name;
const exportedName = specifier.exported.name;
for (const { local, exported } of node.specifiers) {
// except for reexports, local must be an Identifier
const localName = (local as Identifier).name;
const exportedName = exported instanceof Identifier ? exported.name : exported.value;
this.exports.set(exportedName, { identifier: null, localName });
}
}
Expand All @@ -1024,10 +1026,14 @@ export default class Module {
const source = node.source.value;
this.addSource(source, node);
for (const specifier of node.specifiers) {
const isDefault = specifier.type === NodeType.ImportDefaultSpecifier;
const isNamespace = specifier.type === NodeType.ImportNamespaceSpecifier;

const name = isDefault ? 'default' : isNamespace ? '*' : specifier.imported.name;
const name =
specifier instanceof ImportDefaultSpecifier
? 'default'
: specifier instanceof ImportNamespaceSpecifier
? '*'
: specifier.imported instanceof Identifier
? specifier.imported.name
: specifier.imported.value;
this.importDescriptions.set(specifier.local.name, {
module: null as never, // filled in later
name,
Expand Down
5 changes: 3 additions & 2 deletions src/ast/nodes/ExportSpecifier.ts
@@ -1,10 +1,11 @@
import type Identifier from './Identifier';
import type Literal from './Literal';
import type * as NodeType from './NodeType';
import { NodeBase } from './shared/Node';

export default class ExportSpecifier extends NodeBase {
declare exported: Identifier;
declare local: Identifier;
declare exported: Identifier | Literal<string>;
declare local: Identifier | Literal<string>;
declare type: NodeType.tExportSpecifier;

protected applyDeoptimizations() {}
Expand Down
3 changes: 2 additions & 1 deletion src/ast/nodes/ImportSpecifier.ts
@@ -1,9 +1,10 @@
import type Identifier from './Identifier';
import type Literal from './Literal';
import type * as NodeType from './NodeType';
import { NodeBase } from './shared/Node';

export default class ImportSpecifier extends NodeBase {
declare imported: Identifier;
declare imported: Identifier | Literal<string>;
declare local: Identifier;
declare type: NodeType.tImportSpecifier;

Expand Down
@@ -0,0 +1,3 @@
module.exports = {
description: 'supports arbitrary module namespace identifiers'
};
@@ -0,0 +1,2 @@
const foo = 42;
export { foo as ' 😆 ' };
@@ -0,0 +1,3 @@
import { ' 🙄 ' as foo } from './reexport.js';

assert.strictEqual(foo, 42);
@@ -0,0 +1 @@
export { ' 😆 ' as ' 🙄 ' } from './foo.js';