-
Notifications
You must be signed in to change notification settings - Fork 245
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(jsii): dependency submodules are not tagged #1663
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -112,7 +112,7 @@ export class Assembler implements Emitter { | |
const symbol = this._typeChecker.getSymbolAtLocation(sourceFile); | ||
if (symbol) { | ||
const moduleExports = this._typeChecker.getExportsOfModule(symbol); | ||
await Promise.all(moduleExports.map(this._registerNamespaces.bind(this))); | ||
await Promise.all(moduleExports.map(item => this._registerNamespaces(item, this.projectInfo.projectRoot))); | ||
for (const node of moduleExports) { | ||
visitPromises.push(this._visitNode(node.declarations[0], new EmitContext([], this.projectInfo.stability))); | ||
} | ||
|
@@ -377,14 +377,24 @@ export class Assembler implements Emitter { | |
// It's unlikely, but if we can't get the SourceFile here, ignore it (TypeScript compilation probably failed) | ||
if (depMod == null) { continue; } | ||
|
||
const depRoot = packageRoot(resolved.resolvedModule.resolvedFileName); | ||
|
||
for (const symbol of this._typeChecker.getExportsOfModule(depMod)) { | ||
// eslint-disable-next-line no-await-in-loop | ||
await this._registerNamespaces(symbol); | ||
await this._registerNamespaces(symbol, depRoot); | ||
} | ||
} | ||
|
||
function packageRoot(file: string): string { | ||
const parent = path.dirname(file); | ||
if (path.basename(parent) === 'node_modules' || parent === file) { | ||
return file; | ||
} | ||
return packageRoot(parent); | ||
} | ||
} | ||
|
||
private async _registerNamespaces(symbol: ts.Symbol): Promise<void> { | ||
private async _registerNamespaces(symbol: ts.Symbol, packageRoot: string): Promise<void> { | ||
const declaration = symbol.valueDeclaration ?? symbol.declarations[0]; | ||
if (declaration == null) { | ||
// Nothing to do here... | ||
|
@@ -394,7 +404,7 @@ export class Assembler implements Emitter { | |
const { fqn, fqnResolutionPrefix } = await qualifiedNameOf.call(this, symbol, true); | ||
|
||
this._submodules.set(symbol, { fqn, fqnResolutionPrefix, locationInModule: this.declarationLocation(declaration) }); | ||
await this._addToSubmodule(symbol, symbol); | ||
await this._addToSubmodule(symbol, symbol, packageRoot); | ||
return; | ||
} | ||
if (!ts.isNamespaceExport(declaration)) { | ||
|
@@ -417,12 +427,21 @@ export class Assembler implements Emitter { | |
// Unresolvable module... We'll let tsc report this for us. | ||
return; | ||
} | ||
if (resolution.resolvedModule.isExternalLibraryImport) { | ||
if ( | ||
// We're not looking into a dependency's namespace exports, and the resolution says it's external | ||
(packageRoot === this.projectInfo.projectRoot && resolution.resolvedModule.isExternalLibraryImport) | ||
// Or the module resolves outside of the current dependency's tree entirely | ||
|| !resolution.resolvedModule.resolvedFileName.startsWith(packageRoot) | ||
// Or the module is under one the current dependency's node_modules subtree | ||
|| resolution.resolvedModule.resolvedFileName.split(path.sep).filter(entry => entry === 'node_modules').length | ||
!== packageRoot.split(path.sep).filter(entry => entry === 'node_modules').length | ||
) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Help me out here, why does a different number of node_modules folder in a path mean we don't want to register it? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. When we reach here, we've already established that the file resolved within the same root ( |
||
// External re-exports are "pure-javascript" sugar; they need not be | ||
// represented in the jsii Assembly since the types in there will be | ||
// resolved through dependencies. | ||
return; | ||
} | ||
Comment on lines
+431
to
443
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. To give me more context, is this There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We are preventing creating a second name for the same actual type, because while TypeScript will totally follow the re-export and understand the type is just being aliased; other languages that do not feature structural type-checking will consider the two instances of the type as entirely distinct entities - that is no bueno. |
||
|
||
const sourceFile = this.program.getSourceFile(resolution.resolvedModule.resolvedFileName)!; | ||
const sourceModule = this._typeChecker.getSymbolAtLocation(sourceFile); | ||
// If there's no module, it's a syntax error, and tsc will have reported it for us. | ||
|
@@ -436,7 +455,7 @@ export class Assembler implements Emitter { | |
const targets = undefined; // This will be configurable in the future. | ||
|
||
this._submodules.set(symbol, { fqn, fqnResolutionPrefix, targets, locationInModule: this.declarationLocation(declaration) }); | ||
await this._addToSubmodule(symbol, sourceModule); | ||
await this._addToSubmodule(symbol, sourceModule, packageRoot); | ||
} | ||
|
||
async function qualifiedNameOf(this: Assembler, sym: ts.Symbol, inlineNamespace = false): Promise<{ fqn: string, fqnResolutionPrefix: string }> { | ||
|
@@ -464,10 +483,11 @@ export class Assembler implements Emitter { | |
* declarations exported by an `export * as ns from 'moduleLike';` statement | ||
* so that they can subsequently be correctly namespaced. | ||
* | ||
* @param ns the symbol that identifies the submodule. | ||
* @param moduleLike the module-like symbol bound to the submodule. | ||
* @param ns the symbol that identifies the submodule. | ||
* @param moduleLike the module-like symbol bound to the submodule. | ||
* @param packageRoot the root of the package being traversed. | ||
*/ | ||
private async _addToSubmodule(ns: ts.Symbol, moduleLike: ts.Symbol) { | ||
private async _addToSubmodule(ns: ts.Symbol, moduleLike: ts.Symbol, packageRoot: string) { | ||
// For each symbol exported by the moduleLike, map it to the ns submodule. | ||
for (const symbol of this._typeChecker.getExportsOfModule(moduleLike)) { | ||
if (this._submoduleMap.has(symbol)) { | ||
|
@@ -522,14 +542,14 @@ export class Assembler implements Emitter { | |
} | ||
if (type.symbol.exports) { | ||
// eslint-disable-next-line no-await-in-loop | ||
await this._addToSubmodule(ns, symbol); | ||
await this._addToSubmodule(ns, symbol, packageRoot); | ||
} | ||
} else if (ts.isModuleDeclaration(decl)) { | ||
// eslint-disable-next-line no-await-in-loop | ||
await this._registerNamespaces(symbol); | ||
await this._registerNamespaces(symbol, packageRoot); | ||
} else if (ts.isNamespaceExport(decl)) { | ||
// eslint-disable-next-line no-await-in-loop | ||
await this._registerNamespaces(symbol); | ||
await this._registerNamespaces(symbol, packageRoot); | ||
} | ||
} | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can this two conditions ever happen together?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes. They'll do in the phase where we're actually compiling our project (as opposed to crawling dependencies).