Skip to content

Commit

Permalink
[api-extractor] Fix 'export * as __' part of #3593
Browse files Browse the repository at this point in the history
"Incorrect canonical reference to aliased class in .api.json"
#3593

This fixes the other aliasing, namely through (nested)
namespaces.
Added a test scenario "docReferencesNamespaceAlias", which
consists of nested namespaces that have their own index.d.ts,
re-exporting the default export of each sibling file and
their respective sub-namespace (top-level -> 'renamed'
-> 'sub').
The *.api.json canonicalReferences now correctly use the
nested namespace "fully-qualified name".

Again, some other test result changed unexpectedly, maybe
revealing an existing bug / wrong expected result.
The scenarios are "exportImportStarAs2", "importEquals"
and "includeForgottenExports". The pattern is that
before, namespace and class were separate tokens, but
are now aggregated to one dot-separated expression.
I hope that these are improvements, but I am not sure,
so this should be reviewed.

Implementation:
---------------
When DeclarationReferenceGenerator#_getParentReference()
looks for a parent symbol, it must also resolve
namespace parent symbols the Collector derived from
'import * as ___'.
To that end, Collector now has a method
getExportingNamespace() that pulls namespace imports from
the CollectorEntities of a namespace symbol (if present).
For that, namespace imports are now also registered in the
_entitiesBySymbol Map.
In DeclarationReferenceGenerator, the handling of
namespaces had to be improved. _getNavigationToSymbol()
now detects namespaces, even behind an alias, and
then always returns "." as separator. To find the
parent, it also considers namespaces now.
_symbolToDeclarationReference() takes care not to
follow an alias that leads to a *-imported namespace,
because the target is the imported source file that
no longer allows to retrieve the namespace name.
  • Loading branch information
fwienber committed Sep 2, 2022
1 parent b11cc24 commit ef387a7
Show file tree
Hide file tree
Showing 16 changed files with 574 additions and 19 deletions.
21 changes: 21 additions & 0 deletions apps/api-extractor/src/collector/Collector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -314,6 +314,25 @@ export class Collector {
return collectorEntity?.nameForEmit ?? symbol.name;
}

/**
* Returns (the symbol of) the namespace that exports the given `symbol`, if applicable.
* @param symbol the symbol representing the namespace that exports the given `symbol`,
* or undefined if there is no such namespace
*/
public getExportingNamespace(symbol: ts.Symbol): ts.Symbol | undefined {
const collectorEntity: CollectorEntity | undefined = this._entitiesBySymbol.get(symbol);
if (collectorEntity) {
const exportingNamespace: AstNamespaceImport | undefined = collectorEntity.getExportingNamespace();
if (exportingNamespace) {
return TypeScriptInternals.tryGetSymbolForDeclaration(
exportingNamespace.declaration,
this.typeChecker
);
}
}
return undefined;
}

public fetchSymbolMetadata(astSymbol: AstSymbol): SymbolMetadata {
if (astSymbol.symbolMetadata === undefined) {
this._fetchSymbolMetadata(astSymbol);
Expand Down Expand Up @@ -435,6 +454,8 @@ export class Collector {
this._entitiesByAstEntity.set(astEntity, entity);
if (astEntity instanceof AstSymbol) {
this._entitiesBySymbol.set(astEntity.followedSymbol, entity);
} else if (astEntity instanceof AstNamespaceImport) {
this._entitiesBySymbol.set((astEntity.declaration as unknown as ts.Type).symbol, entity);
}
this._entities.push(entity);
this._collectReferenceDirectives(astEntity);
Expand Down
13 changes: 13 additions & 0 deletions apps/api-extractor/src/collector/CollectorEntity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { AstSymbol } from '../analyzer/AstSymbol';
import { Collector } from './Collector';
import { Sort } from '@rushstack/node-core-library';
import { AstEntity } from '../analyzer/AstEntity';
import { AstNamespaceImport } from '../analyzer/AstNamespaceImport';

/**
* This is a data structure used by the Collector to track an AstEntity that may be emitted in the *.d.ts file.
Expand Down Expand Up @@ -189,6 +190,18 @@ export class CollectorEntity {
return this._localExportNamesByParent.size > 0;
}

/**
* Return the first namespace that exports this entity.
*/
public getExportingNamespace(): AstNamespaceImport | undefined {
for (const [parent, localExportNames] of this._localExportNamesByParent) {
if (parent.consumable && localExportNames.size > 0 && parent.astEntity instanceof AstNamespaceImport) {
return parent.astEntity;
}
}
return undefined;
}

/**
* Adds a new export name to the entity.
*/
Expand Down
26 changes: 22 additions & 4 deletions apps/api-extractor/src/generators/DeclarationReferenceGenerator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,9 +88,18 @@ export class DeclarationReferenceGenerator {
}

private _getNavigationToSymbol(symbol: ts.Symbol): Navigation {
// resolve alias first:
if (symbol.flags & ts.SymbolFlags.Alias) {
symbol = this._collector.typeChecker.getAliasedSymbol(symbol);
}
// namespace always uses ".":
if (symbol.flags & ts.SymbolFlags.Namespace) {
return Navigation.Exports;
}
const declaration: ts.Declaration | undefined = TypeScriptHelpers.tryGetADeclaration(symbol);
const sourceFile: ts.SourceFile | undefined = declaration?.getSourceFile();
const parent: ts.Symbol | undefined = TypeScriptInternals.getSymbolParent(symbol);
const parent: ts.Symbol | undefined =
TypeScriptInternals.getSymbolParent(symbol) ?? this._collector.getExportingNamespace(symbol);

// If it's global or from an external library, then use either Members or Exports. It's not possible for
// global symbols or external library symbols to be Locals.
Expand Down Expand Up @@ -208,7 +217,11 @@ export class DeclarationReferenceGenerator {
followedSymbol = this._collector.typeChecker.getExportSymbolOfSymbol(followedSymbol);
}
if (followedSymbol.flags & ts.SymbolFlags.Alias) {
followedSymbol = this._collector.typeChecker.getAliasedSymbol(followedSymbol);
const nextFollowedSymbol: ts.Symbol = this._collector.typeChecker.getAliasedSymbol(followedSymbol);
// do not follow alias to namespace, as it results in a source file that no longer knows its short name:
if (!(nextFollowedSymbol.flags & ts.SymbolFlags.Namespace)) {
followedSymbol = nextFollowedSymbol;
}
}

if (DeclarationReferenceGenerator._isExternalModuleSymbol(followedSymbol)) {
Expand Down Expand Up @@ -266,9 +279,13 @@ export class DeclarationReferenceGenerator {
}

private _getParentReference(symbol: ts.Symbol): DeclarationReference | undefined {
const declaration: ts.Node | undefined = TypeScriptHelpers.tryGetADeclaration(symbol);
// First case: the symbol is exported via a namespace
const exportingNamespace: ts.Symbol | undefined = this._collector.getExportingNamespace(symbol);
if (exportingNamespace) {
return this._symbolToDeclarationReference(exportingNamespace, exportingNamespace.flags, true);
}

// First, try to find a parent symbol via the symbol tree.
// Next, try to find a parent symbol via the symbol tree.
const parentSymbol: ts.Symbol | undefined = TypeScriptInternals.getSymbolParent(symbol);
if (parentSymbol) {
return this._symbolToDeclarationReference(
Expand All @@ -290,6 +307,7 @@ export class DeclarationReferenceGenerator {
//
// In the example above, `SomeType` doesn't have a parent symbol per the TS internal API above,
// but its reference still needs to be qualified with the parent reference for `n`.
const declaration: ts.Node | undefined = TypeScriptHelpers.tryGetADeclaration(symbol);
const grandParent: ts.Node | undefined = declaration?.parent?.parent;
if (grandParent && ts.isModuleDeclaration(grandParent)) {
const grandParentSymbol: ts.Symbol | undefined = TypeScriptInternals.tryGetSymbolForDeclaration(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
"docReferences2",
"docReferences3",
"docReferencesAlias",
"docReferencesNamespaceAlias",
"dynamicImportType",
"dynamicImportType2",
"dynamicImportType3",
Expand Down
Loading

0 comments on commit ef387a7

Please sign in to comment.