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

Fix module augmentation in @parcel/transformers-typescript-types #7315

Merged
@@ -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