From 97e4593826a5d36ab3bd8db96259eaab134e8374 Mon Sep 17 00:00:00 2001 From: Zack Elliott Date: Wed, 7 Sep 2022 17:23:12 -0700 Subject: [PATCH 1/2] Changes to #3602 --- .../src/analyzer/AstNamespaceImport.ts | 7 + .../src/analyzer/ExportAnalyzer.ts | 3 +- apps/api-extractor/src/collector/Collector.ts | 39 +-- .../src/collector/CollectorEntity.ts | 21 +- .../DeclarationReferenceGenerator.ts | 105 ++++--- .../config/build-config.json | 2 + .../api-extractor-scenarios.api.json | 2 +- .../api-extractor-scenarios.api.json | 282 ++++++++++++++++++ .../api-extractor-scenarios.api.md | 36 +++ .../etc/namespaceImports/rollup.d.ts | 28 ++ .../api-extractor-scenarios.api.json | 233 +++++++++++++++ .../api-extractor-scenarios.api.md | 23 ++ .../etc/namespaceImports2/rollup.d.ts | 15 + .../src/namespaceImports/index.ts | 12 + .../src/namespaceImports/intermediate1.ts | 5 + .../src/namespaceImports/intermediate2.ts | 5 + .../src/namespaceImports/internal.ts | 5 + .../src/namespaceImports2/index.ts | 12 + .../src/namespaceImports2/internal.ts | 5 + 19 files changed, 753 insertions(+), 87 deletions(-) create mode 100644 build-tests/api-extractor-scenarios/etc/namespaceImports/api-extractor-scenarios.api.json create mode 100644 build-tests/api-extractor-scenarios/etc/namespaceImports/api-extractor-scenarios.api.md create mode 100644 build-tests/api-extractor-scenarios/etc/namespaceImports/rollup.d.ts create mode 100644 build-tests/api-extractor-scenarios/etc/namespaceImports2/api-extractor-scenarios.api.json create mode 100644 build-tests/api-extractor-scenarios/etc/namespaceImports2/api-extractor-scenarios.api.md create mode 100644 build-tests/api-extractor-scenarios/etc/namespaceImports2/rollup.d.ts create mode 100644 build-tests/api-extractor-scenarios/src/namespaceImports/index.ts create mode 100644 build-tests/api-extractor-scenarios/src/namespaceImports/intermediate1.ts create mode 100644 build-tests/api-extractor-scenarios/src/namespaceImports/intermediate2.ts create mode 100644 build-tests/api-extractor-scenarios/src/namespaceImports/internal.ts create mode 100644 build-tests/api-extractor-scenarios/src/namespaceImports2/index.ts create mode 100644 build-tests/api-extractor-scenarios/src/namespaceImports2/internal.ts diff --git a/apps/api-extractor/src/analyzer/AstNamespaceImport.ts b/apps/api-extractor/src/analyzer/AstNamespaceImport.ts index 955ef903d27..acb4bd4c578 100644 --- a/apps/api-extractor/src/analyzer/AstNamespaceImport.ts +++ b/apps/api-extractor/src/analyzer/AstNamespaceImport.ts @@ -11,6 +11,7 @@ export interface IAstNamespaceImportOptions { readonly astModule: AstModule; readonly namespaceName: string; readonly declaration: ts.Declaration; + readonly symbol: ts.Symbol; } /** @@ -67,11 +68,17 @@ export class AstNamespaceImport extends AstSyntheticEntity { */ public readonly declaration: ts.Declaration; + /** + * The original `ts.SymbolFlags.Namespace` symbol. + */ + public readonly symbol: ts.Symbol; + public constructor(options: IAstNamespaceImportOptions) { super(); this.astModule = options.astModule; this.namespaceName = options.namespaceName; this.declaration = options.declaration; + this.symbol = options.symbol; } /** {@inheritdoc} */ diff --git a/apps/api-extractor/src/analyzer/ExportAnalyzer.ts b/apps/api-extractor/src/analyzer/ExportAnalyzer.ts index 33867923bb5..3610f828c07 100644 --- a/apps/api-extractor/src/analyzer/ExportAnalyzer.ts +++ b/apps/api-extractor/src/analyzer/ExportAnalyzer.ts @@ -626,7 +626,8 @@ export class ExportAnalyzer { namespaceImport = new AstNamespaceImport({ namespaceName: declarationSymbol.name, astModule: astModule, - declaration: declaration + declaration: declaration, + symbol: declarationSymbol }); this._astNamespaceImportByModule.set(astModule, namespaceImport); } diff --git a/apps/api-extractor/src/collector/Collector.ts b/apps/api-extractor/src/collector/Collector.ts index 9fa64ee8177..ea2a2dba145 100644 --- a/apps/api-extractor/src/collector/Collector.ts +++ b/apps/api-extractor/src/collector/Collector.ts @@ -296,41 +296,18 @@ export class Collector { } /** - * Returns the associated `CollectorEntity` for the given `astEntity`, if one was created during analysis. - */ - public tryGetCollectorEntity(astEntity: AstEntity): CollectorEntity | undefined { - return this._entitiesByAstEntity.get(astEntity); - } - - /** - * Returns the name to emit for the given `symbol`. - * This is usually the `name` of that `symbol`. - * Only if this `symbol` has a `CollectorEntity`, the `nameForEmit` of that entity is used. - * @param symbol the symbol to compute the name to emit for - * @returns the name to emit for the given `symbol` + * For a given analyzed ts.Symbol, return the CollectorEntity that it refers to. Returns undefined if it + * doesn't refer to anything interesting. */ - public getEmitName(symbol: ts.Symbol): string { - const collectorEntity: CollectorEntity | undefined = this._entitiesBySymbol.get(symbol); - return collectorEntity?.nameForEmit ?? symbol.name; + public tryGetEntityForSymbol(symbol: ts.Symbol): CollectorEntity | undefined { + return this._entitiesBySymbol.get(symbol); } /** - * 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 + * Returns the associated `CollectorEntity` for the given `astEntity`, if one was created during analysis. */ - 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 tryGetCollectorEntity(astEntity: AstEntity): CollectorEntity | undefined { + return this._entitiesByAstEntity.get(astEntity); } public fetchSymbolMetadata(astSymbol: AstSymbol): SymbolMetadata { @@ -455,7 +432,7 @@ export class Collector { 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._entitiesBySymbol.set(astEntity.symbol, entity); } this._entities.push(entity); this._collectReferenceDirectives(astEntity); diff --git a/apps/api-extractor/src/collector/CollectorEntity.ts b/apps/api-extractor/src/collector/CollectorEntity.ts index bb52da90656..46f6af15357 100644 --- a/apps/api-extractor/src/collector/CollectorEntity.ts +++ b/apps/api-extractor/src/collector/CollectorEntity.ts @@ -7,7 +7,6 @@ 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. @@ -96,6 +95,13 @@ export class CollectorEntity { return false; } + /** + * Indicates that this entity is exported from the package entry point. Compare to `CollectorEntity.exported`. + */ + public get exportedFromEntryPoint(): boolean { + return this.exportNames.size > 0; + } + /** * Indicates that this entity is exported from its parent module (i.e. either the package entry point or * a local namespace). Compare to `CollectorEntity.consumable`. @@ -117,7 +123,7 @@ export class CollectorEntity { */ public get exported(): boolean { // Exported from top-level? - if (this.exportNames.size > 0) return true; + if (this.exportedFromEntryPoint) return true; // Exported from parent? for (const localExportNames of this._localExportNamesByParent.values()) { @@ -157,7 +163,7 @@ export class CollectorEntity { */ public get consumable(): boolean { // Exported from top-level? - if (this.exportNames.size > 0) return true; + if (this.exportedFromEntryPoint) return true; // Exported from consumable parent? for (const [parent, localExportNames] of this._localExportNamesByParent) { @@ -191,12 +197,13 @@ export class CollectorEntity { } /** - * Return the first namespace that exports this entity. + * Return the first consumable parent that exports this entity. If there is none, returns + * `undefined`. */ - public getExportingNamespace(): AstNamespaceImport | undefined { + public get firstExportingConsumableParent(): CollectorEntity | undefined { for (const [parent, localExportNames] of this._localExportNamesByParent) { - if (parent.consumable && localExportNames.size > 0 && parent.astEntity instanceof AstNamespaceImport) { - return parent.astEntity; + if (parent.consumable && localExportNames.size > 0) { + return parent; } } return undefined; diff --git a/apps/api-extractor/src/generators/DeclarationReferenceGenerator.ts b/apps/api-extractor/src/generators/DeclarationReferenceGenerator.ts index 2aec240f83a..8b16a787ba5 100644 --- a/apps/api-extractor/src/generators/DeclarationReferenceGenerator.ts +++ b/apps/api-extractor/src/generators/DeclarationReferenceGenerator.ts @@ -15,6 +15,7 @@ import { TypeScriptHelpers } from '../analyzer/TypeScriptHelpers'; import { TypeScriptInternals } from '../analyzer/TypeScriptInternals'; import { Collector } from '../collector/Collector'; import { CollectorEntity } from '../collector/CollectorEntity'; +import { AstNamespaceImport } from '../analyzer/AstNamespaceImport'; export class DeclarationReferenceGenerator { public static readonly unknownReference: string = '?'; @@ -88,18 +89,9 @@ 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) ?? this._collector.getExportingNamespace(symbol); + const parent: ts.Symbol | undefined = TypeScriptInternals.getSymbolParent(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. @@ -118,34 +110,25 @@ export class DeclarationReferenceGenerator { return Navigation.Exports; } - // Otherwise, this symbol is from the current package. - if (parent) { - // If we've found an exported CollectorEntity, then it's exported from the package entry point, so - // use Exports. - const namedDeclaration: ts.DeclarationName | undefined = ( - declaration as ts.NamedDeclaration | undefined - )?.name; - if (namedDeclaration && ts.isIdentifier(namedDeclaration)) { - const collectorEntity: CollectorEntity | undefined = - this._collector.tryGetEntityForNode(namedDeclaration); - if (collectorEntity && collectorEntity.exported) { - return Navigation.Exports; - } - } - - // 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 (!DeclarationReferenceGenerator._isExternalModuleSymbol(parent)) { - if ( - parent.members && - DeclarationReferenceGenerator._isSameSymbol(parent.members.get(symbol.escapedName), symbol) - ) { - return Navigation.Members; - } + // Otherwise, this symbol is from the current package. If we've found an exported CollectorEntity, then use + // Exports. + const entity: CollectorEntity | undefined = this._collector.tryGetEntityForSymbol(symbol); + if (entity && entity.exported) { + return Navigation.Exports; + } - return Navigation.Exports; + // 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.members && + DeclarationReferenceGenerator._isSameSymbol(parent.members.get(symbol.escapedName), symbol) + ) { + return Navigation.Members; } + + return Navigation.Exports; } // Otherwise, we have a local symbol, so use a Locals navigation. These are either: @@ -217,10 +200,12 @@ export class DeclarationReferenceGenerator { followedSymbol = this._collector.typeChecker.getExportSymbolOfSymbol(followedSymbol); } if (followedSymbol.flags & ts.SymbolFlags.Alias) { - 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; + followedSymbol = this._collector.typeChecker.getAliasedSymbol(followedSymbol); + + // Without this logic, we end up following the symbol `ns` in `import * as ns from './file'` to + // the actual file `file.ts`. We don't want to do this, so revert to the original symbol. + if (followedSymbol.flags & ts.SymbolFlags.ValueModule) { + followedSymbol = symbol; } } @@ -241,7 +226,12 @@ export class DeclarationReferenceGenerator { return undefined; } - let localName: string = this._collector.getEmitName(followedSymbol); + let localName: string = followedSymbol.name; + const entity: CollectorEntity | undefined = this._collector.tryGetEntityForSymbol(followedSymbol); + if (entity?.nameForEmit) { + localName = entity.nameForEmit; + } + if (followedSymbol.escapedName === ts.InternalSymbolName.Constructor) { localName = 'constructor'; } else { @@ -279,10 +269,33 @@ export class DeclarationReferenceGenerator { } private _getParentReference(symbol: ts.Symbol): DeclarationReference | undefined { - // 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); + const declaration: ts.Node | undefined = TypeScriptHelpers.tryGetADeclaration(symbol); + const sourceFile: ts.SourceFile | undefined = declaration?.getSourceFile(); + + const entity: CollectorEntity | undefined = this._collector.tryGetEntityForSymbol(symbol); + if (entity) { + if (entity.exportedFromEntryPoint) { + return new DeclarationReference(this._sourceFileToModuleSource(sourceFile)); + } + + const firstExportingConsumableParent: CollectorEntity | undefined = + entity.firstExportingConsumableParent; + if ( + firstExportingConsumableParent && + firstExportingConsumableParent.astEntity instanceof AstNamespaceImport + ) { + const parentSymbol: ts.Symbol | undefined = TypeScriptInternals.tryGetSymbolForDeclaration( + firstExportingConsumableParent.astEntity.declaration, + this._collector.typeChecker + ); + if (parentSymbol) { + return this._symbolToDeclarationReference( + parentSymbol, + parentSymbol.flags, + /*includeModuleSymbols*/ true + ); + } + } } // Next, try to find a parent symbol via the symbol tree. @@ -307,7 +320,6 @@ 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( @@ -324,7 +336,6 @@ export class DeclarationReferenceGenerator { } // At this point, we have a local symbol in a module. - const sourceFile: ts.SourceFile | undefined = declaration?.getSourceFile(); if (sourceFile && ts.isExternalModule(sourceFile)) { return new DeclarationReference(this._sourceFileToModuleSource(sourceFile)); } else { diff --git a/build-tests/api-extractor-scenarios/config/build-config.json b/build-tests/api-extractor-scenarios/config/build-config.json index c3f851e1fe1..3c41f30a625 100644 --- a/build-tests/api-extractor-scenarios/config/build-config.json +++ b/build-tests/api-extractor-scenarios/config/build-config.json @@ -41,6 +41,8 @@ "mergedDeclarations", "mixinPattern", "namedDefaultImport", + "namespaceImports", + "namespaceImports2", "preapproved", "readonlyDeclarations", "referenceTokens", diff --git a/build-tests/api-extractor-scenarios/etc/includeForgottenExports/api-extractor-scenarios.api.json b/build-tests/api-extractor-scenarios/etc/includeForgottenExports/api-extractor-scenarios.api.json index 2197aad8ba0..0e0bf04f003 100644 --- a/build-tests/api-extractor-scenarios/etc/includeForgottenExports/api-extractor-scenarios.api.json +++ b/build-tests/api-extractor-scenarios/etc/includeForgottenExports/api-extractor-scenarios.api.json @@ -468,7 +468,7 @@ { "kind": "Reference", "text": "ForgottenExport4.ForgottenExport5", - "canonicalReference": "api-extractor-scenarios!ForgottenExport4.ForgottenExport5:class" + "canonicalReference": "api-extractor-scenarios!~ForgottenExport4.ForgottenExport5:class" }, { "kind": "Content", diff --git a/build-tests/api-extractor-scenarios/etc/namespaceImports/api-extractor-scenarios.api.json b/build-tests/api-extractor-scenarios/etc/namespaceImports/api-extractor-scenarios.api.json new file mode 100644 index 00000000000..4f050369a44 --- /dev/null +++ b/build-tests/api-extractor-scenarios/etc/namespaceImports/api-extractor-scenarios.api.json @@ -0,0 +1,282 @@ +{ + "metadata": { + "toolPackage": "@microsoft/api-extractor", + "toolVersion": "[test mode]", + "schemaVersion": 1009, + "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": "@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": "Namespace", + "canonicalReference": "api-extractor-scenarios!i1:namespace", + "docComment": "", + "excerptTokens": [], + "releaseTag": "None", + "name": "i1", + "preserveMemberOrder": false, + "members": [ + { + "kind": "Namespace", + "canonicalReference": "api-extractor-scenarios!i1.internal:namespace", + "docComment": "", + "excerptTokens": [], + "releaseTag": "None", + "name": "internal", + "preserveMemberOrder": false, + "members": [ + { + "kind": "Class", + "canonicalReference": "api-extractor-scenarios!i1.internal.SomeClass:class", + "docComment": "/**\n * @public\n */\n", + "excerptTokens": [ + { + "kind": "Content", + "text": "export declare class SomeClass " + } + ], + "releaseTag": "Public", + "name": "SomeClass", + "preserveMemberOrder": false, + "members": [], + "implementsTokenRanges": [] + } + ] + } + ] + }, + { + "kind": "Namespace", + "canonicalReference": "api-extractor-scenarios!i2:namespace", + "docComment": "", + "excerptTokens": [], + "releaseTag": "None", + "name": "i2", + "preserveMemberOrder": false, + "members": [ + { + "kind": "Namespace", + "canonicalReference": "api-extractor-scenarios!i2.internal:namespace", + "docComment": "", + "excerptTokens": [], + "releaseTag": "None", + "name": "internal", + "preserveMemberOrder": false, + "members": [ + { + "kind": "Class", + "canonicalReference": "api-extractor-scenarios!i2.internal.SomeClass:class", + "docComment": "/**\n * @public\n */\n", + "excerptTokens": [ + { + "kind": "Content", + "text": "export declare class SomeClass " + } + ], + "releaseTag": "Public", + "name": "SomeClass", + "preserveMemberOrder": false, + "members": [], + "implementsTokenRanges": [] + } + ] + } + ] + }, + { + "kind": "Function", + "canonicalReference": "api-extractor-scenarios!someFunction:function(1)", + "docComment": "/**\n * @public\n */\n", + "excerptTokens": [ + { + "kind": "Content", + "text": "export declare function someFunction(): " + }, + { + "kind": "Reference", + "text": "i1.internal.SomeClass", + "canonicalReference": "api-extractor-scenarios!i1.internal.SomeClass:class" + }, + { + "kind": "Content", + "text": ";" + } + ], + "returnTypeTokenRange": { + "startIndex": 1, + "endIndex": 2 + }, + "releaseTag": "Public", + "overloadIndex": 1, + "parameters": [], + "name": "someFunction" + } + ] + } + ] +} diff --git a/build-tests/api-extractor-scenarios/etc/namespaceImports/api-extractor-scenarios.api.md b/build-tests/api-extractor-scenarios/etc/namespaceImports/api-extractor-scenarios.api.md new file mode 100644 index 00000000000..60f137ad0d4 --- /dev/null +++ b/build-tests/api-extractor-scenarios/etc/namespaceImports/api-extractor-scenarios.api.md @@ -0,0 +1,36 @@ +## 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 + +declare namespace i1 { + export { + internal + } +} +export { i1 } + +declare namespace i2 { + export { + internal + } +} +export { i2 } + +declare namespace internal { + export { + SomeClass + } +} + +// @public (undocumented) +class SomeClass { +} + +// @public (undocumented) +export function someFunction(): i1.internal.SomeClass; + +// (No @packageDocumentation comment for this package) + +``` diff --git a/build-tests/api-extractor-scenarios/etc/namespaceImports/rollup.d.ts b/build-tests/api-extractor-scenarios/etc/namespaceImports/rollup.d.ts new file mode 100644 index 00000000000..e19612205d5 --- /dev/null +++ b/build-tests/api-extractor-scenarios/etc/namespaceImports/rollup.d.ts @@ -0,0 +1,28 @@ +declare namespace i1 { + export { + internal + } +} +export { i1 } + +declare namespace i2 { + export { + internal + } +} +export { i2 } + +declare namespace internal { + export { + SomeClass + } +} + +/** @public */ +declare class SomeClass { +} + +/** @public */ +export declare function someFunction(): i1.internal.SomeClass; + +export { } diff --git a/build-tests/api-extractor-scenarios/etc/namespaceImports2/api-extractor-scenarios.api.json b/build-tests/api-extractor-scenarios/etc/namespaceImports2/api-extractor-scenarios.api.json new file mode 100644 index 00000000000..8e0625d6aab --- /dev/null +++ b/build-tests/api-extractor-scenarios/etc/namespaceImports2/api-extractor-scenarios.api.json @@ -0,0 +1,233 @@ +{ + "metadata": { + "toolPackage": "@microsoft/api-extractor", + "toolVersion": "[test mode]", + "schemaVersion": 1009, + "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": "@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": "Namespace", + "canonicalReference": "api-extractor-scenarios!internal:namespace", + "docComment": "", + "excerptTokens": [], + "releaseTag": "None", + "name": "internal", + "preserveMemberOrder": false, + "members": [ + { + "kind": "Class", + "canonicalReference": "api-extractor-scenarios!internal.SomeClass:class", + "docComment": "/**\n * @public\n */\n", + "excerptTokens": [ + { + "kind": "Content", + "text": "export declare class SomeClass " + } + ], + "releaseTag": "Public", + "name": "SomeClass", + "preserveMemberOrder": false, + "members": [], + "implementsTokenRanges": [] + } + ] + }, + { + "kind": "Function", + "canonicalReference": "api-extractor-scenarios!someFunction:function(1)", + "docComment": "/**\n * @public\n */\n", + "excerptTokens": [ + { + "kind": "Content", + "text": "export declare function someFunction(): " + }, + { + "kind": "Reference", + "text": "SomeClass", + "canonicalReference": "api-extractor-scenarios!SomeClass:class" + }, + { + "kind": "Content", + "text": ";" + } + ], + "returnTypeTokenRange": { + "startIndex": 1, + "endIndex": 2 + }, + "releaseTag": "Public", + "overloadIndex": 1, + "parameters": [], + "name": "someFunction" + } + ] + } + ] +} diff --git a/build-tests/api-extractor-scenarios/etc/namespaceImports2/api-extractor-scenarios.api.md b/build-tests/api-extractor-scenarios/etc/namespaceImports2/api-extractor-scenarios.api.md new file mode 100644 index 00000000000..2c624b8845b --- /dev/null +++ b/build-tests/api-extractor-scenarios/etc/namespaceImports2/api-extractor-scenarios.api.md @@ -0,0 +1,23 @@ +## 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 + +declare namespace internal { + export { + SomeClass + } +} +export { internal } + +// @public (undocumented) +export class SomeClass { +} + +// @public (undocumented) +export function someFunction(): SomeClass; + +// (No @packageDocumentation comment for this package) + +``` diff --git a/build-tests/api-extractor-scenarios/etc/namespaceImports2/rollup.d.ts b/build-tests/api-extractor-scenarios/etc/namespaceImports2/rollup.d.ts new file mode 100644 index 00000000000..06bf37c06b6 --- /dev/null +++ b/build-tests/api-extractor-scenarios/etc/namespaceImports2/rollup.d.ts @@ -0,0 +1,15 @@ +declare namespace internal { + export { + SomeClass + } +} +export { internal } + +/** @public */ +export declare class SomeClass { +} + +/** @public */ +export declare function someFunction(): SomeClass; + +export { } diff --git a/build-tests/api-extractor-scenarios/src/namespaceImports/index.ts b/build-tests/api-extractor-scenarios/src/namespaceImports/index.ts new file mode 100644 index 00000000000..5376cd34998 --- /dev/null +++ b/build-tests/api-extractor-scenarios/src/namespaceImports/index.ts @@ -0,0 +1,12 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. +// See LICENSE in the project root for license information. + +import * as i1 from './intermediate1'; +import * as i2 from './intermediate2'; + +export { i1, i2 }; + +/** @public */ +export function someFunction(): i1.internal.SomeClass { + return new i1.internal.SomeClass(); +} diff --git a/build-tests/api-extractor-scenarios/src/namespaceImports/intermediate1.ts b/build-tests/api-extractor-scenarios/src/namespaceImports/intermediate1.ts new file mode 100644 index 00000000000..59eb9ff7bc8 --- /dev/null +++ b/build-tests/api-extractor-scenarios/src/namespaceImports/intermediate1.ts @@ -0,0 +1,5 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. +// See LICENSE in the project root for license information. + +import * as internal from './internal'; +export { internal }; diff --git a/build-tests/api-extractor-scenarios/src/namespaceImports/intermediate2.ts b/build-tests/api-extractor-scenarios/src/namespaceImports/intermediate2.ts new file mode 100644 index 00000000000..59eb9ff7bc8 --- /dev/null +++ b/build-tests/api-extractor-scenarios/src/namespaceImports/intermediate2.ts @@ -0,0 +1,5 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. +// See LICENSE in the project root for license information. + +import * as internal from './internal'; +export { internal }; diff --git a/build-tests/api-extractor-scenarios/src/namespaceImports/internal.ts b/build-tests/api-extractor-scenarios/src/namespaceImports/internal.ts new file mode 100644 index 00000000000..6fd90b44eda --- /dev/null +++ b/build-tests/api-extractor-scenarios/src/namespaceImports/internal.ts @@ -0,0 +1,5 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. +// See LICENSE in the project root for license information. + +/** @public */ +export class SomeClass {} diff --git a/build-tests/api-extractor-scenarios/src/namespaceImports2/index.ts b/build-tests/api-extractor-scenarios/src/namespaceImports2/index.ts new file mode 100644 index 00000000000..03b57b5c9ab --- /dev/null +++ b/build-tests/api-extractor-scenarios/src/namespaceImports2/index.ts @@ -0,0 +1,12 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. +// See LICENSE in the project root for license information. + +import * as internal from './internal'; +import { SomeClass } from './internal'; + +export { internal, SomeClass }; + +/** @public */ +export function someFunction(): SomeClass { + return new SomeClass(); +} diff --git a/build-tests/api-extractor-scenarios/src/namespaceImports2/internal.ts b/build-tests/api-extractor-scenarios/src/namespaceImports2/internal.ts new file mode 100644 index 00000000000..6fd90b44eda --- /dev/null +++ b/build-tests/api-extractor-scenarios/src/namespaceImports2/internal.ts @@ -0,0 +1,5 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. +// See LICENSE in the project root for license information. + +/** @public */ +export class SomeClass {} From 1e11c5c1ac34fffba2f6f2b72dcb37dc674752f3 Mon Sep 17 00:00:00 2001 From: Zack Elliott Date: Wed, 7 Sep 2022 17:42:31 -0700 Subject: [PATCH 2/2] Added a comment --- .../src/generators/DeclarationReferenceGenerator.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/apps/api-extractor/src/generators/DeclarationReferenceGenerator.ts b/apps/api-extractor/src/generators/DeclarationReferenceGenerator.ts index 8b16a787ba5..49ce1124f75 100644 --- a/apps/api-extractor/src/generators/DeclarationReferenceGenerator.ts +++ b/apps/api-extractor/src/generators/DeclarationReferenceGenerator.ts @@ -272,6 +272,9 @@ export class DeclarationReferenceGenerator { const declaration: ts.Node | undefined = TypeScriptHelpers.tryGetADeclaration(symbol); const sourceFile: ts.SourceFile | undefined = declaration?.getSourceFile(); + // Note that it's possible for a symbol to be exported from an entry point as well as one or more + // namespaces. In that case, it's not clear what to choose as its parent. Today's logic is neither + // perfect nor particularly stable to API items being renamed and shuffled around. const entity: CollectorEntity | undefined = this._collector.tryGetEntityForSymbol(symbol); if (entity) { if (entity.exportedFromEntryPoint) {