Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 15 additions & 6 deletions apps/api-extractor/src/analyzer/ExportAnalyzer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -977,12 +977,21 @@ export class ExportAnalyzer {
if (importSymbol) {
const followedSymbol: ts.Symbol = TypeScriptHelpers.followAliases(importSymbol, this._typeChecker);

astImport.astSymbol = this._astSymbolTable.fetchAstSymbol({
followedSymbol: followedSymbol,
isExternal: true,
includeNominalAnalysis: false,
addIfMissing: true
});
// If the import resolves to an entire module rather than a declaration within a module, then there
// is no AstSymbol to associate with it. This happens when a dependency re-exports a module as a
// namespace (e.g. `export * as ns from './module'`) and we import that namespace by name. Following
// the alias chain lands on the module's source file symbol, which API Extractor cannot represent as
// an AstSymbol (a SourceFile is not a supported declaration kind). This mirrors the handling of
// `import * as ns from '...'` star imports, where `importSymbol` is undefined.
// See https://github.com/microsoft/rushstack/issues/4963
if (!TypeScriptHelpers.isExternalModuleSymbol(followedSymbol)) {
astImport.astSymbol = this._astSymbolTable.fetchAstSymbol({
followedSymbol: followedSymbol,
isExternal: true,
includeNominalAnalysis: false,
addIfMissing: true
});
}
}
} else {
// If we encounter at least one import that does not use the type-only form,
Expand Down
12 changes: 12 additions & 0 deletions apps/api-extractor/src/analyzer/TypeScriptHelpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,18 @@ export class TypeScriptHelpers {
return undefined;
}

/**
* Returns true if the symbol represents an entire module (i.e. its declaration is a source file),
* as opposed to a declaration appearing within a module.
*/
public static isExternalModuleSymbol(symbol: ts.Symbol): boolean {
return (
!!(symbol.flags & ts.SymbolFlags.ValueModule) &&
symbol.valueDeclaration !== undefined &&
ts.isSourceFile(symbol.valueDeclaration)
);
}

/**
* Returns true if the specified symbol is an ambient declaration.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,14 +71,6 @@ export class DeclarationReferenceGenerator {
}
}

private static _isExternalModuleSymbol(symbol: ts.Symbol): boolean {
return (
!!(symbol.flags & ts.SymbolFlags.ValueModule) &&
symbol.valueDeclaration !== undefined &&
ts.isSourceFile(symbol.valueDeclaration)
);
}

private static _isSameSymbol(left: ts.Symbol | undefined, right: ts.Symbol): boolean {
return (
left === right ||
Expand Down Expand Up @@ -125,7 +117,7 @@ export class DeclarationReferenceGenerator {
// If its parent symbol is not a source file, then use either Exports or Members. If the parent symbol
// is a source file, but it wasn't exported from the package entry point (in the check above), then the
// symbol is a local, so fall through below.
if (parent && !DeclarationReferenceGenerator._isExternalModuleSymbol(parent)) {
if (parent && !TypeScriptHelpers.isExternalModuleSymbol(parent)) {
if (
parent.members &&
DeclarationReferenceGenerator._isSameSymbol(parent.members.get(symbol.escapedName), symbol)
Expand Down Expand Up @@ -214,7 +206,7 @@ export class DeclarationReferenceGenerator {
}
}

if (DeclarationReferenceGenerator._isExternalModuleSymbol(followedSymbol)) {
if (TypeScriptHelpers.isExternalModuleSymbol(followedSymbol)) {
if (!includeModuleSymbols) {
return undefined;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
// See LICENSE in the project root for license information.

// Re-exports an entire module as a namespace. When a downstream package imports this namespace by
// name and references a type within it, following the alias chain lands on this module's source file
// symbol. See https://github.com/microsoft/rushstack/issues/4963
export * as Lib2ReexportedNamespace from './Lib2ReexportedNamespaceTarget';
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
// See LICENSE in the project root for license information.

/** @public */
export interface Lib2ReexportedInterface {
prop: number;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,223 @@
{
"metadata": {
"toolPackage": "@microsoft/api-extractor",
"toolVersion": "[test mode]",
"schemaVersion": 1011,
"oldestForwardsCompatibleVersion": 1001,
"tsdocConfig": {
"$schema": "https://developer.microsoft.com/json-schemas/tsdoc/v0/tsdoc.schema.json",
"noStandardTags": true,
"tagDefinitions": [
{
"tagName": "@alpha",
"syntaxKind": "modifier"
},
{
"tagName": "@beta",
"syntaxKind": "modifier"
},
{
"tagName": "@defaultValue",
"syntaxKind": "block"
},
{
"tagName": "@decorator",
"syntaxKind": "block",
"allowMultiple": true
},
{
"tagName": "@deprecated",
"syntaxKind": "block"
},
{
"tagName": "@eventProperty",
"syntaxKind": "modifier"
},
{
"tagName": "@example",
"syntaxKind": "block",
"allowMultiple": true
},
{
"tagName": "@experimental",
"syntaxKind": "modifier"
},
{
"tagName": "@inheritDoc",
"syntaxKind": "inline"
},
{
"tagName": "@internal",
"syntaxKind": "modifier"
},
{
"tagName": "@label",
"syntaxKind": "inline"
},
{
"tagName": "@link",
"syntaxKind": "inline",
"allowMultiple": true
},
{
"tagName": "@override",
"syntaxKind": "modifier"
},
{
"tagName": "@packageDocumentation",
"syntaxKind": "modifier"
},
{
"tagName": "@param",
"syntaxKind": "block",
"allowMultiple": true
},
{
"tagName": "@privateRemarks",
"syntaxKind": "block"
},
{
"tagName": "@public",
"syntaxKind": "modifier"
},
{
"tagName": "@readonly",
"syntaxKind": "modifier"
},
{
"tagName": "@remarks",
"syntaxKind": "block"
},
{
"tagName": "@returns",
"syntaxKind": "block"
},
{
"tagName": "@sealed",
"syntaxKind": "modifier"
},
{
"tagName": "@see",
"syntaxKind": "block"
},
{
"tagName": "@throws",
"syntaxKind": "block",
"allowMultiple": true
},
{
"tagName": "@typeParam",
"syntaxKind": "block",
"allowMultiple": true
},
{
"tagName": "@virtual",
"syntaxKind": "modifier"
},
{
"tagName": "@jsx",
"syntaxKind": "block"
},
{
"tagName": "@jsxRuntime",
"syntaxKind": "block"
},
{
"tagName": "@jsxFrag",
"syntaxKind": "block"
},
{
"tagName": "@jsxImportSource",
"syntaxKind": "block"
},
{
"tagName": "@betaDocumentation",
"syntaxKind": "modifier"
},
{
"tagName": "@internalRemarks",
"syntaxKind": "block"
},
{
"tagName": "@preapproved",
"syntaxKind": "modifier"
}
],
"supportForTags": {
"@alpha": true,
"@beta": true,
"@defaultValue": true,
"@decorator": true,
"@deprecated": true,
"@eventProperty": true,
"@example": true,
"@experimental": true,
"@inheritDoc": true,
"@internal": true,
"@label": true,
"@link": true,
"@override": true,
"@packageDocumentation": true,
"@param": true,
"@privateRemarks": true,
"@public": true,
"@readonly": true,
"@remarks": true,
"@returns": true,
"@sealed": true,
"@see": true,
"@throws": true,
"@typeParam": true,
"@virtual": true,
"@betaDocumentation": true,
"@internalRemarks": true,
"@preapproved": true
},
"reportUnsupportedHtmlElements": false
}
},
"kind": "Package",
"canonicalReference": "api-extractor-scenarios!",
"docComment": "",
"name": "api-extractor-scenarios",
"preserveMemberOrder": false,
"members": [
{
"kind": "EntryPoint",
"canonicalReference": "api-extractor-scenarios!",
"name": "",
"preserveMemberOrder": false,
"members": [
{
"kind": "Function",
"canonicalReference": "api-extractor-scenarios!useReexportedNamespace:function(1)",
"docComment": "/**\n * @public\n */\n",
"excerptTokens": [
{
"kind": "Content",
"text": "export declare function useReexportedNamespace(): "
},
{
"kind": "Reference",
"text": "Lib2ReexportedNamespace.Lib2ReexportedInterface",
"canonicalReference": "api-extractor-lib2-test!Lib2ReexportedInterface:interface"
},
{
"kind": "Content",
"text": ";"
}
],
"fileUrlPath": "src/importExternalReexportedNamespace/index.ts",
"returnTypeTokenRange": {
"startIndex": 1,
"endIndex": 2
},
"releaseTag": "Public",
"overloadIndex": 1,
"parameters": [],
"name": "useReexportedNamespace"
}
]
}
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
## API Report File for "api-extractor-scenarios"

> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).

```ts

import { Lib2ReexportedNamespace } from 'api-extractor-lib2-test/lib/Lib2ReexportedNamespace';

// @public (undocumented)
export function useReexportedNamespace(): Lib2ReexportedNamespace.Lib2ReexportedInterface;

// (No @packageDocumentation comment for this package)

```
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { Lib2ReexportedNamespace } from 'api-extractor-lib2-test/lib/Lib2ReexportedNamespace';

/** @public */
export declare function useReexportedNamespace(): Lib2ReexportedNamespace.Lib2ReexportedInterface;

export { }
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
// See LICENSE in the project root for license information.

// Regression test for https://github.com/microsoft/rushstack/issues/4963
//
// `Lib2ReexportedNamespace` is imported by name from an external package, but in that package it was
// produced by re-exporting an entire module as a namespace (`export * as Lib2ReexportedNamespace from
// './...'`). Referencing a type within it used to crash API Extractor with an internal error, because
// following the alias chain lands on the module's source file symbol.
import { Lib2ReexportedNamespace } from 'api-extractor-lib2-test/lib/Lib2ReexportedNamespace';

/** @public */
export function useReexportedNamespace(): Lib2ReexportedNamespace.Lib2ReexportedInterface {
return { prop: 1 };
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"changes": [
{
"comment": "Fix an internal error (\"... symbol has a ts.SyntaxKind.SourceFile declaration which is not (yet?) supported by API Extractor\") that occurred when importing by name a namespace that a dependency had produced by re-exporting an entire module (e.g. `export * as ns from './module'`) and then referencing a type within it.",
"type": "patch",
"packageName": "@microsoft/api-extractor"
}
],
"packageName": "@microsoft/api-extractor",
"email": "lukas@livekit.io"
}
Loading