Skip to content

Commit

Permalink
fix(tree-shaking): Correctly deconflict ExportDeclaration inside modu…
Browse files Browse the repository at this point in the history
…le wrappers and add support for deconflicting QualifiedNames. Closes #136
  • Loading branch information
wessberg committed Apr 13, 2021
1 parent f5af3d3 commit 6623ca2
Show file tree
Hide file tree
Showing 4 changed files with 86 additions and 6 deletions.
Expand Up @@ -37,13 +37,16 @@ import {deconflictFunctionTypeNode} from "./visitor/deconflict-function-type";
import {deconflictImportTypeNode} from "./visitor/deconflict-import-type-node";
import {deconflictConstructorDeclaration} from "./visitor/deconflict-constructor-declaration";
import {deconflictCallSignatureDeclaration} from "./visitor/deconflict-call-signature-declaration";
import {deconflictQualifiedName} from "./visitor/deconflict-qualified-name";

/**
* Deconflicts the given Node. Everything but LValues will be updated here
*/
function deconflictNode({node, ...options}: DeconflicterVisitorOptions<TS.Node>): TS.Node | undefined {
if (options.typescript.isBindingElement(node)) {
return deconflictBindingElement({node, ...options});
} else if (options.typescript.isQualifiedName(node)) {
return deconflictQualifiedName({node, ...options});
} else if (options.typescript.isClassDeclaration(node)) {
return deconflictClassDeclaration({node, ...options});
} else if (options.typescript.isClassExpression(node)) {
Expand Down
Expand Up @@ -19,18 +19,24 @@ export function deconflictModuleDeclaration(options: DeconflicterVisitorOptions<
const id = getIdForNode(options);
const originalSourceFile = getOriginalSourceFile(node, sourceFile, typescript);

// Check if it is a namespace ModuleDeclaration. If it is, it can be deconflicted. If it isn't, it should augment and merge with any existing declarations for it
// Check if it is a namespace ModuleDeclaration. If it is, its name can be deconflicted. If it isn't, it should augment and merge with any existing declarations for it
const isNamespace = (node.flags & typescript.NodeFlags.Namespace) !== 0;
if (!isNamespace) {
const binding = getBindingFromLexicalEnvironment(lexicalEnvironment, node.name.text) ?? node.name.text;

// If the binding has changed, update the ModuleDeclaration
if (binding !== node.name.text) {
return preserveMeta(compatFactory.updateModuleDeclaration(node, node.decorators, node.modifiers, compatFactory.createIdentifier(binding), node.body), node, options);
// The body has its own lexical environment
const nextContinuationOptions: ContinuationOptions = {lexicalEnvironment: cloneLexicalEnvironment(lexicalEnvironment)};
const bodyContResult = node.body == null ? undefined : continuation(node.body, nextContinuationOptions);

const isIdentical = binding === node.name.text && bodyContResult === node.body;

if (isIdentical) {
return node;
}

// Otherwise, preserve the node as it is
return node;
else {
return preserveMeta(compatFactory.updateModuleDeclaration(node, node.decorators, node.modifiers, compatFactory.createIdentifier(binding), bodyContResult), node, options);
}
}

if (isIdentifierFree(lexicalEnvironment, node.name.text, originalSourceFile.fileName)) {
Expand Down
@@ -0,0 +1,19 @@
import {DeconflicterVisitorOptions} from "../deconflicter-visitor-options";
import {TS} from "../../../../../../type/ts";
import {preserveMeta} from "../../../util/clone-node-with-meta";

/**
* Deconflicts the given QualifiedName.
*/
export function deconflictQualifiedName(options: DeconflicterVisitorOptions<TS.QualifiedName>): TS.QualifiedName | undefined {
const {node, continuation, lexicalEnvironment, compatFactory} = options;
const leftContResult = continuation(node.left, {lexicalEnvironment});

const isIdentical = leftContResult === node.left;

if (isIdentical) {
return node;
}

return preserveMeta(compatFactory.updateQualifiedName(node, leftContResult, node.right), node, options);
}
52 changes: 52 additions & 0 deletions test/deconflict.test.ts
Expand Up @@ -1241,6 +1241,58 @@ test("Deconflicts symbols. #22", withTypeScript, async (t, {typescript}) => {
);
});

test("Deconflicts symbols. #23", withTypeScript, async (t, {typescript}) => {
const bundle = await generateRollupBundle(
[
{
entry: true,
fileName: "virtual-src/index.ts",
text: `\
import { B } from "./b";
import { B as B2 } from "./b2";
export { B, B2 };
`
},
{
entry: false,
fileName: "virtual-src/b.ts",
text: `\
export class B { }
`
},
{
entry: false,
fileName: "virtual-src/b2.ts",
text: `\
export class B { }
`
}
],
{
typescript,
debug: false
}
);
const {
declarations: [file]
} = bundle;

t.deepEqual(
formatCode(file.code),
formatCode(`\
declare class B {
}
declare class B$0 {
}
declare module BWrapper {
export { B$0 as B };
}
import B2 = BWrapper.B;
export { B, B2 };
`)
);
});

test("Will merge declarations declared in same SourceFile rather than deconflict. #1", withTypeScript, async (t, {typescript}) => {
const bundle = await generateRollupBundle(
[
Expand Down

0 comments on commit 6623ca2

Please sign in to comment.