Skip to content

Commit

Permalink
Fix module augmentation in @parcel/transformers-typescript-types (#7315)
Browse files Browse the repository at this point in the history
  • Loading branch information
astegmaier committed Nov 23, 2021
1 parent 3120998 commit 8a0c9ca
Show file tree
Hide file tree
Showing 12 changed files with 131 additions and 2 deletions.
@@ -0,0 +1,10 @@
{
"name": "augmenter",
"version": "1.0.0",
"source": "src/index.ts",
"types": "dist/index.d.ts",
"main": "dist/index.js",
"dependencies": {
"original": "1.0.0"
}
}
@@ -0,0 +1,9 @@
export const anotherThing: string;
declare module "original" {
interface Person {
greet(): string;
}
}
export const somethingElse: string;

//# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,12 @@
import { Person } from "original";
Person.prototype.greet = function() { return `Hello ${this.name}!` }

export const anotherThing: string = "hello";

declare module "original" {
interface Person {
greet(): string;
}
}

export const somethingElse: string = "goodbye";
@@ -0,0 +1,4 @@
export declare class Person {
name: string;
constructor(name: string);
}
@@ -0,0 +1,9 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Person = void 0;
class Person {
constructor(name) {
this.name = name;
}
}
exports.Person = Person;
@@ -0,0 +1,7 @@
{
"name": "original",
"version": "1.0.0",
"source": "src/index.ts",
"main": "built-src/index.js",
"types": "built-src/index.d.ts"
}
@@ -0,0 +1,3 @@
export class Person {
constructor(public name: string) {}
}
@@ -0,0 +1,7 @@
{
"private": true,
"workspaces": [
"augmenter",
"original"
]
}
@@ -0,0 +1,4 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1


38 changes: 38 additions & 0 deletions packages/core/integration-tests/test/ts-types.js
Expand Up @@ -323,4 +323,42 @@ describe('typescript types', function () {
);
assert.equal(dist, expected);
});

it('should work with module augmentation', async function () {
let fixtureDir = path.join(__dirname, 'integration/ts-types/augmentation');
await outputFS.mkdirp(path.join(fixtureDir, 'node_modules'));
await ncp(fixtureDir, fixtureDir);
await outputFS.symlink(
path.join(fixtureDir, 'original'),
path.join(fixtureDir, 'node_modules/original'),
);

let b = await bundle(path.join(fixtureDir, 'augmenter'), {
inputFS: overlayFS,
});
assertBundles(b, [
{
name: 'index.js',
type: 'js',
assets: ['index.ts'],
},
{
name: 'index.d.ts',
type: 'ts',
assets: ['index.ts'],
},
]);

let dist = (
await outputFS.readFile(
path.join(fixtureDir, 'augmenter/dist/index.d.ts'),
'utf8',
)
).replace(/\r\n/g, '\n');
let expected = await inputFS.readFile(
path.join(fixtureDir, 'augmenter/src/expected.d.ts'),
'utf8',
);
assert.equal(dist, expected);
});
});
12 changes: 11 additions & 1 deletion packages/transformers/typescript-types/src/collect.js
Expand Up @@ -11,13 +11,17 @@ export function collect(
context: any,
sourceFile: any,
): any {
// When module definitions are nested inside each other (e.g with module augmentation),
// we want to keep track of the hierarchy so we can associated nodes with the right module.
const moduleStack: Array<?TSModule> = [];
let _currentModule: ?TSModule;
let visit = (node: any): any => {
if (ts.isBundle(node)) {
return ts.updateBundle(node, ts.visitNodes(node.sourceFiles, visit));
}

if (ts.isModuleDeclaration(node)) {
moduleStack.push(_currentModule);
_currentModule = new TSModule();
moduleGraph.addModule(node.name.text, _currentModule);
}
Expand Down Expand Up @@ -105,7 +109,13 @@ export function collect(
}
}

return ts.visitEachChild(node, visit, context);
const results = ts.visitEachChild(node, visit, context);
// After we finish traversing the children of a module definition,
// we need to make sure that subsequent nodes get associated with the next-highest level module.
if (ts.isModuleDeclaration(node)) {
_currentModule = moduleStack.pop();
}
return results;
};

return ts.visitNode(sourceFile, visit);
Expand Down
18 changes: 17 additions & 1 deletion packages/transformers/typescript-types/src/shake.js
Expand Up @@ -19,6 +19,12 @@ export function shake(
// Propagate exports from the main module to determine what types should be included
let exportedNames = moduleGraph.propagate(context);

// When module definitions are nested inside each other (e.g with module augmentation),
// we want to keep track of the hierarchy so we can associated nodes with the right module.
const moduleStack: Array<?TSModule> = [];

let addedGeneratedImports = false;

let _currentModule: ?TSModule;
let visit = (node: any): any => {
if (ts.isBundle(node)) {
Expand All @@ -27,12 +33,22 @@ export function shake(

// Flatten all module declarations into the top-level scope
if (ts.isModuleDeclaration(node)) {
// Deeply nested module declarations are assumed to be module augmentations and left alone.
if (moduleStack.length >= 1) {
// Since we are hoisting them to the top-level scope, we need to add a "declare" keyword to make them ambient.
node.modifiers.unshift(ts.createModifier(ts.SyntaxKind.DeclareKeyword));
return node;
}

moduleStack.push(_currentModule);
let isFirstModule = !_currentModule;
_currentModule = moduleGraph.getModule(node.name.text);
let statements = ts.visitEachChild(node, visit, context).body.statements;
_currentModule = moduleStack.pop();

if (isFirstModule) {
if (isFirstModule && !addedGeneratedImports) {
statements.unshift(...generateImports(moduleGraph));
addedGeneratedImports = true;
}

return statements;
Expand Down

0 comments on commit 8a0c9ca

Please sign in to comment.