From 42ab8f094adf207bc8e46b1c382c281a358eec97 Mon Sep 17 00:00:00 2001 From: Zack Elliott Date: Tue, 26 Jul 2022 09:08:03 -0700 Subject: [PATCH 1/8] WIP --- apps/api-extractor/src/api/ExtractorConfig.ts | 14 + apps/api-extractor/src/api/IConfigFile.ts | 24 +- apps/api-extractor/src/collector/Collector.ts | 87 ++-- .../src/collector/CollectorEntity.ts | 74 ++-- .../src/enhancers/DocCommentEnhancer.ts | 2 +- .../src/enhancers/ValidationEnhancer.ts | 6 +- .../src/generators/ApiModelGenerator.ts | 58 ++- .../src/generators/ApiReportGenerator.ts | 15 +- .../src/schemas/api-extractor-defaults.json | 6 +- .../src/schemas/api-extractor-template.json | 27 +- .../src/schemas/api-extractor.schema.json | 12 +- .../etc/api-extractor-lib1-test.api.md | 1 - .../etc/api-extractor-lib2-test.api.md | 2 - .../config/build-config.json | 1 + .../api-extractor-scenarios.api.json | 12 +- .../api-extractor-scenarios.api.md | 6 +- .../etc/exportImportStarAs/rollup.d.ts | 6 +- .../api-extractor-scenarios.api.md | 4 - .../api-extractor-scenarios.api.json | 371 ++++++++++++++++++ .../api-extractor-scenarios.api.md | 38 ++ .../etc/includeForgottenExports/rollup.d.ts | 26 ++ .../src/exportImportStarAs/common.ts | 2 +- .../config/api-extractor-overrides.json | 13 + .../src/includeForgottenExports/index.ts | 23 ++ .../src/includeForgottenExports/internal.ts | 6 + .../workspace/common/pnpm-lock.yaml | 22 +- common/reviews/api/api-extractor-model.api.md | 35 +- common/reviews/api/api-extractor.api.md | 4 + libraries/api-extractor-model/src/index.ts | 1 + .../src/mixins/ApiExportedMixin.ts | 119 ++++++ .../api-extractor-model/src/model/ApiClass.ts | 18 +- .../api-extractor-model/src/model/ApiEnum.ts | 11 +- .../src/model/ApiFunction.ts | 11 +- .../src/model/ApiInterface.ts | 16 +- .../src/model/ApiNamespace.ts | 11 +- .../src/model/ApiTypeAlias.ts | 18 +- .../src/model/ApiVariable.ts | 15 +- 37 files changed, 946 insertions(+), 171 deletions(-) create mode 100644 build-tests/api-extractor-scenarios/etc/includeForgottenExports/api-extractor-scenarios.api.json create mode 100644 build-tests/api-extractor-scenarios/etc/includeForgottenExports/api-extractor-scenarios.api.md create mode 100644 build-tests/api-extractor-scenarios/etc/includeForgottenExports/rollup.d.ts create mode 100644 build-tests/api-extractor-scenarios/src/includeForgottenExports/config/api-extractor-overrides.json create mode 100644 build-tests/api-extractor-scenarios/src/includeForgottenExports/index.ts create mode 100644 build-tests/api-extractor-scenarios/src/includeForgottenExports/internal.ts create mode 100644 libraries/api-extractor-model/src/mixins/ApiExportedMixin.ts diff --git a/apps/api-extractor/src/api/ExtractorConfig.ts b/apps/api-extractor/src/api/ExtractorConfig.ts index 33192aa2e25..66817f7f38c 100644 --- a/apps/api-extractor/src/api/ExtractorConfig.ts +++ b/apps/api-extractor/src/api/ExtractorConfig.ts @@ -158,8 +158,10 @@ interface IExtractorConfigParameters { apiReportEnabled: boolean; reportFilePath: string; reportTempFilePath: string; + apiReportIncludeForgottenExports: boolean; docModelEnabled: boolean; apiJsonFilePath: string; + docModelIncludeForgottenExports: boolean; rollupEnabled: boolean; untrimmedFilePath: string; alphaTrimmedFilePath: string; @@ -246,11 +248,15 @@ export class ExtractorConfig { public readonly reportFilePath: string; /** The `reportTempFolder` path combined with the `reportFileName`. */ public readonly reportTempFilePath: string; + /** {@inheritDoc IConfigApiReport.includeForgottenExports} */ + public readonly apiReportIncludeForgottenExports: boolean; /** {@inheritDoc IConfigDocModel.enabled} */ public readonly docModelEnabled: boolean; /** {@inheritDoc IConfigDocModel.apiJsonFilePath} */ public readonly apiJsonFilePath: string; + /** {@inheritDoc IConfigDocModel.includeForgottenExports} */ + public readonly docModelIncludeForgottenExports: boolean; /** {@inheritDoc IConfigDtsRollup.enabled} */ public readonly rollupEnabled: boolean; @@ -307,8 +313,10 @@ export class ExtractorConfig { this.apiReportEnabled = parameters.apiReportEnabled; this.reportFilePath = parameters.reportFilePath; this.reportTempFilePath = parameters.reportTempFilePath; + this.apiReportIncludeForgottenExports = parameters.apiReportIncludeForgottenExports; this.docModelEnabled = parameters.docModelEnabled; this.apiJsonFilePath = parameters.apiJsonFilePath; + this.docModelIncludeForgottenExports = parameters.docModelIncludeForgottenExports; this.rollupEnabled = parameters.rollupEnabled; this.untrimmedFilePath = parameters.untrimmedFilePath; this.alphaTrimmedFilePath = parameters.alphaTrimmedFilePath; @@ -848,6 +856,7 @@ export class ExtractorConfig { let apiReportEnabled: boolean = false; let reportFilePath: string = ''; let reportTempFilePath: string = ''; + let apiReportIncludeForgottenExports: boolean = false; if (configObject.apiReport) { apiReportEnabled = !!configObject.apiReport.enabled; @@ -879,10 +888,12 @@ export class ExtractorConfig { reportFilePath = path.join(reportFolder, reportFilename); reportTempFilePath = path.join(reportTempFolder, reportFilename); + apiReportIncludeForgottenExports = !!configObject.apiReport.includeForgottenExports; } let docModelEnabled: boolean = false; let apiJsonFilePath: string = ''; + let docModelIncludeForgottenExports: boolean = false; if (configObject.docModel) { docModelEnabled = !!configObject.docModel.enabled; apiJsonFilePath = ExtractorConfig._resolvePathWithTokens( @@ -890,6 +901,7 @@ export class ExtractorConfig { configObject.docModel.apiJsonFilePath, tokenContext ); + docModelIncludeForgottenExports = !!configObject.docModel.includeForgottenExports; } let tsdocMetadataEnabled: boolean = false; @@ -993,8 +1005,10 @@ export class ExtractorConfig { apiReportEnabled, reportFilePath, reportTempFilePath, + apiReportIncludeForgottenExports, docModelEnabled, apiJsonFilePath, + docModelIncludeForgottenExports, rollupEnabled, untrimmedFilePath, alphaTrimmedFilePath, diff --git a/apps/api-extractor/src/api/IConfigFile.ts b/apps/api-extractor/src/api/IConfigFile.ts index a07c3118aeb..fffe284e385 100644 --- a/apps/api-extractor/src/api/IConfigFile.ts +++ b/apps/api-extractor/src/api/IConfigFile.ts @@ -96,6 +96,17 @@ export interface IConfigApiReport { * prepend a folder token such as ``. */ reportTempFolder?: string; + + /** + * Whether "forgotten exports" should be included in the API report file. + * + * @remarks + * Forgotten exports are declarations flagged with `ae-forgotten-export` warnings. See + * https://api-extractor.com/pages/messages/ae-forgotten-export/ to learn more. + * + * @defaultValue `false` + */ + includeForgottenExports?: boolean; } /** @@ -120,6 +131,17 @@ export interface IConfigDocModel { * prepend a folder token such as ``. */ apiJsonFilePath?: string; + + /** + * Whether "forgotten exports" should be included in the doc model file. + * + * @remarks + * Forgotten exports are declarations flagged with `ae-forgotten-export` warnings. See + * https://api-extractor.com/pages/messages/ae-forgotten-export/ to learn more. + * + * @defaultValue `false` + */ + includeForgottenExports?: boolean; } /** @@ -376,7 +398,7 @@ export interface IConfigFile { testMode?: boolean; /** - * Specifies how API Extractor sorts members of an enum when generating api.json. + * Specifies how API Extractor sorts members of an enum when generating the .api.json file. * * @remarks * By default, the output files will be sorted alphabetically, which is "by-name". diff --git a/apps/api-extractor/src/collector/Collector.ts b/apps/api-extractor/src/collector/Collector.ts index 86fcd1eee16..cbb13942241 100644 --- a/apps/api-extractor/src/collector/Collector.ts +++ b/apps/api-extractor/src/collector/Collector.ts @@ -246,28 +246,23 @@ export class Collector { this.workingPackage.tsdocComment = this.workingPackage.tsdocParserContext!.docComment; } - const exportedAstEntities: AstEntity[] = []; - - // Create a CollectorEntity for each top-level export - const astModuleExportInfo: AstModuleExportInfo = this.astSymbolTable.fetchAstModuleExportInfo(astEntryPoint); + // Create a CollectorEntity for each top-level export. + const processedAstEntities: AstEntity[] = []; for (const [exportName, astEntity] of astModuleExportInfo.exportedLocalEntities) { this._createCollectorEntity(astEntity, exportName); - - exportedAstEntities.push(astEntity); + processedAstEntities.push(astEntity); } - // Create a CollectorEntity for each indirectly referenced export. - // Note that we do this *after* the above loop, so that references to exported AstSymbols - // are encountered first as exports. - const alreadySeenAstSymbols: Set = new Set(); - for (const exportedAstEntity of exportedAstEntities) { - this._createEntityForIndirectReferences(exportedAstEntity, alreadySeenAstSymbols); - - if (exportedAstEntity instanceof AstSymbol) { - this.fetchSymbolMetadata(exportedAstEntity); + // Recursively create the remaining CollectorEntities after the top-level entities + // have been processed. + const alreadySeenAstEntities: Set = new Set(); + for (const astEntity of processedAstEntities) { + this._recursivelyCreateEntities(astEntity, alreadySeenAstEntities); + if (astEntity instanceof AstSymbol) { + this.fetchSymbolMetadata(astEntity); } } @@ -414,7 +409,11 @@ export class Collector { return overloadIndex; } - private _createCollectorEntity(astEntity: AstEntity, exportedName: string | undefined): CollectorEntity { + private _createCollectorEntity( + astEntity: AstEntity, + exportName?: string, + parent?: CollectorEntity + ): CollectorEntity { let entity: CollectorEntity | undefined = this._entitiesByAstEntity.get(astEntity); if (!entity) { @@ -425,50 +424,54 @@ export class Collector { this._collectReferenceDirectives(astEntity); } - if (exportedName) { - entity.addExportName(exportedName); + if (exportName) { + if (parent) { + entity.addLocalExportName(exportName, parent); + } else { + entity.addExportName(exportName); + } } return entity; } - private _createEntityForIndirectReferences( - astEntity: AstEntity, - alreadySeenAstEntities: Set - ): void { - if (alreadySeenAstEntities.has(astEntity)) { - return; - } + private _recursivelyCreateEntities(astEntity: AstEntity, alreadySeenAstEntities: Set): void { + if (alreadySeenAstEntities.has(astEntity)) return; alreadySeenAstEntities.add(astEntity); if (astEntity instanceof AstSymbol) { astEntity.forEachDeclarationRecursive((astDeclaration: AstDeclaration) => { for (const referencedAstEntity of astDeclaration.referencedAstEntities) { if (referencedAstEntity instanceof AstSymbol) { - // We only create collector entities for root-level symbols. - // For example, if a symbols is nested inside a namespace, only the root-level namespace - // get a collector entity + // We only create collector entities for root-level symbols. For example, if a symbol is + // nested inside a namespace, only the namespace gets a collector entity. Note that this + // is not true for AstNamespaceImports below. if (referencedAstEntity.parentAstSymbol === undefined) { - this._createCollectorEntity(referencedAstEntity, undefined); + this._createCollectorEntity(referencedAstEntity); } } else { - this._createCollectorEntity(referencedAstEntity, undefined); + this._createCollectorEntity(referencedAstEntity); } - this._createEntityForIndirectReferences(referencedAstEntity, alreadySeenAstEntities); + this._recursivelyCreateEntities(referencedAstEntity, alreadySeenAstEntities); } }); } if (astEntity instanceof AstNamespaceImport) { const astModuleExportInfo: AstModuleExportInfo = astEntity.fetchAstModuleExportInfo(this); + const parentEntity: CollectorEntity | undefined = this._entitiesByAstEntity.get(astEntity); + if (!parentEntity) { + // This should never happen, as we've already created entities for all AstNamespaceImports. + throw new InternalError( + `Failed to get CollectorEntity for AstNamespaceImport with namespace name "${astEntity.namespaceName}"` + ); + } - for (const exportedEntity of astModuleExportInfo.exportedLocalEntities.values()) { - // Create a CollectorEntity for each top-level export of AstImportInternal entity - const entity: CollectorEntity = this._createCollectorEntity(exportedEntity, undefined); - entity.addAstNamespaceImports(astEntity); - - this._createEntityForIndirectReferences(exportedEntity, alreadySeenAstEntities); + for (const [localExportName, localAstEntity] of astModuleExportInfo.exportedLocalEntities) { + // Create a CollectorEntity for each local export within an AstNamespaceImport entity. + this._createCollectorEntity(localAstEntity, localExportName, parentEntity); + this._recursivelyCreateEntities(localAstEntity, alreadySeenAstEntities); } } } @@ -815,7 +818,7 @@ export class Collector { // Don't report missing release tags for forgotten exports const astSymbol: AstSymbol = astDeclaration.astSymbol; const entity: CollectorEntity | undefined = this._entitiesByAstEntity.get(astSymbol.rootAstSymbol); - if (entity && entity.consumable) { + if (entity && entity.exported) { // We also don't report errors for the default export of an entry point, since its doc comment // isn't easy to obtain from the .d.ts file if (astSymbol.rootAstSymbol.localName !== '_default') { @@ -826,10 +829,14 @@ export class Collector { astSymbol ); } + + // All internal, exported APIs default to public if no release tag is specified. + options.effectiveReleaseTag = ReleaseTag.Public; } + } else { + // All external APIs default to public if no release tag is specified. + options.effectiveReleaseTag = ReleaseTag.Public; } - - options.effectiveReleaseTag = ReleaseTag.Public; } const apiItemMetadata: ApiItemMetadata = new ApiItemMetadata(options); diff --git a/apps/api-extractor/src/collector/CollectorEntity.ts b/apps/api-extractor/src/collector/CollectorEntity.ts index 23f57db2c6b..7eeb37a14ba 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. @@ -27,13 +26,12 @@ export class CollectorEntity { private _exportNames: Set = new Set(); private _exportNamesSorted: boolean = false; private _singleExportName: string | undefined = undefined; + private _localExportNamesByParent: Map> = new Map(); private _nameForEmit: string | undefined = undefined; private _sortKey: string | undefined = undefined; - private _astNamespaceImports: Set = new Set(); - public constructor(astEntity: AstEntity) { this.astEntity = astEntity; } @@ -53,7 +51,7 @@ export class CollectorEntity { } /** - * If this symbol is exported from the entry point, the list of export names. + * The list of export names if this symbol is exported from the entry point. * * @remarks * Note that a given symbol may be exported more than once: @@ -98,56 +96,54 @@ export class CollectorEntity { } /** - * Returns true if this symbol is an export for the entry point being analyzed. - */ - public get exported(): boolean { - return this.exportNames.size > 0; - } - - /** - * Indicates that it is possible for a consumer of the API to access this declaration, either by importing - * it directly, or via some other alias such as a member of a namespace. If a collector entity is not consumable, - * then API Extractor will report a ExtractorMessageId.ForgottenExport warning. + * Returns true if the entity is exported from the entry point. If the entity is not exported, + * then API Extractor will report an `ae-forgotten-export` warning. * * @remarks - * Generally speaking, an API item is consumable if: - * - * - The collector encounters it while crawling the entry point, and it is a root symbol - * (i.e. there is a corresponding a CollectorEntity) + * An API item is exported if: * - * - AND it is exported by the entry point + * 1. It is exported from the top-level entry point OR + * 2. It is exported from a parent entity that is also exported. * - * However a special case occurs with `AstNamespaceImport` which produces a rollup like this: + * #2 occurs when processing `AstNamespaceImport` entities. A generated rollup.d.ts + * might look like this: * * ```ts - * declare interface IForgottenExport { } + * declare function add(): void; * - * declare function member(): IForgottenExport; - * - * declare namespace ns { + * declare namespace calculator { * export { - * member + * add * } * } - * export { ns } + * export { calculator } * ``` * - * In this example, `IForgottenExport` is not consumable. Whereas `member()` is consumable as `ns.member()` - * even though `member()` itself is not exported. + * In this example, `add` is exported via the exported `calculator` namespace. */ - public get consumable(): boolean { - return this.exported || this._astNamespaceImports.size > 0; + public get exported(): boolean { + const exportedFromTopLevel: boolean = this.exportNames.size > 0; + + let exportedFromExportedParent: boolean = false; + for (const [parent, localExportNames] of this._localExportNamesByParent) { + if (localExportNames.size > 0 && parent.exported) { + exportedFromExportedParent = true; + break; + } + } + + return exportedFromTopLevel || exportedFromExportedParent; } /** - * Associates this entity with a `AstNamespaceImport`. + * Whether the entity has any parent entities. */ - public addAstNamespaceImports(astNamespaceImport: AstNamespaceImport): void { - this._astNamespaceImports.add(astNamespaceImport); + public get hasParents(): boolean { + return this._localExportNamesByParent.size > 0; } /** - * Adds a new exportName to the exportNames set. + * Adds a new export name to the entity. */ public addExportName(exportName: string): void { if (!this._exportNames.has(exportName)) { @@ -162,6 +158,16 @@ export class CollectorEntity { } } + /** + * Adds a new local export name to the entity. + */ + public addLocalExportName(localExportName: string, parent: CollectorEntity): void { + const localExportNames: Set = this._localExportNamesByParent.get(parent) || new Set(); + localExportNames.add(localExportName); + + this._localExportNamesByParent.set(parent, localExportNames); + } + /** * A sorting key used by DtsRollupGenerator._makeUniqueNames() */ diff --git a/apps/api-extractor/src/enhancers/DocCommentEnhancer.ts b/apps/api-extractor/src/enhancers/DocCommentEnhancer.ts index ee7637d0161..fcdb17b2bdf 100644 --- a/apps/api-extractor/src/enhancers/DocCommentEnhancer.ts +++ b/apps/api-extractor/src/enhancers/DocCommentEnhancer.ts @@ -28,7 +28,7 @@ export class DocCommentEnhancer { public analyze(): void { for (const entity of this._collector.entities) { if (entity.astEntity instanceof AstSymbol) { - if (entity.consumable) { + if (entity.exported || this._collector.extractorConfig.apiReportIncludeForgottenExports) { entity.astEntity.forEachDeclarationRecursive((astDeclaration: AstDeclaration) => { this._analyzeApiItem(astDeclaration); }); diff --git a/apps/api-extractor/src/enhancers/ValidationEnhancer.ts b/apps/api-extractor/src/enhancers/ValidationEnhancer.ts index 302480ee86d..67cec6ded9d 100644 --- a/apps/api-extractor/src/enhancers/ValidationEnhancer.ts +++ b/apps/api-extractor/src/enhancers/ValidationEnhancer.ts @@ -21,7 +21,7 @@ export class ValidationEnhancer { const alreadyWarnedEntities: Set = new Set(); for (const entity of collector.entities) { - if (!entity.consumable) { + if (!(entity.exported || collector.extractorConfig.apiReportIncludeForgottenExports)) { continue; } @@ -73,7 +73,7 @@ export class ValidationEnhancer { if (symbolMetadata.maxEffectiveReleaseTag === ReleaseTag.Internal) { if (!astSymbol.parentAstSymbol) { - // If it's marked as @internal and has no parent, then it needs and underscore. + // If it's marked as @internal and has no parent, then it needs an underscore. // We use maxEffectiveReleaseTag because a merged declaration would NOT need an underscore in a case like this: // // /** @public */ @@ -227,7 +227,7 @@ export class ValidationEnhancer { continue; } - if (collectorEntity && collectorEntity.consumable) { + if (collectorEntity && collectorEntity.exported) { if (ReleaseTag.compare(declarationReleaseTag, referencedReleaseTag) > 0) { collector.messageRouter.addAnalyzerIssue( ExtractorMessageId.IncompatibleReleaseTags, diff --git a/apps/api-extractor/src/generators/ApiModelGenerator.ts b/apps/api-extractor/src/generators/ApiModelGenerator.ts index 5fae347ee5b..b7245422ecd 100644 --- a/apps/api-extractor/src/generators/ApiModelGenerator.ts +++ b/apps/api-extractor/src/generators/ApiModelGenerator.ts @@ -35,6 +35,7 @@ import { } from '@microsoft/api-extractor-model'; import { Collector } from '../collector/Collector'; +import { CollectorEntity } from '../collector/CollectorEntity'; import { AstDeclaration } from '../analyzer/AstDeclaration'; import { ExcerptBuilder, IExcerptBuilderNodeToCapture } from './ExcerptBuilder'; import { AstSymbol } from '../analyzer/AstSymbol'; @@ -45,6 +46,7 @@ import { AstNamespaceImport } from '../analyzer/AstNamespaceImport'; import { AstEntity } from '../analyzer/AstEntity'; import { AstModule } from '../analyzer/AstModule'; import { TypeScriptInternals } from '../analyzer/TypeScriptInternals'; +import { InternalError } from '@rushstack/node-core-library'; export class ApiModelGenerator { private readonly _collector: Collector; @@ -80,9 +82,13 @@ export class ApiModelGenerator { const apiEntryPoint: ApiEntryPoint = new ApiEntryPoint({ name: '' }); apiPackage.addMember(apiEntryPoint); - // Create a CollectorEntity for each top-level export for (const entity of this._collector.entities) { - if (entity.exported) { + // Note that we don't process entities that have parents, because those entities will be recursively + // processed by the parent. + if ( + !entity.hasParents && + (entity.exported || this._collector.extractorConfig.docModelIncludeForgottenExports) + ) { this._processAstEntity(entity.astEntity, entity.nameForEmit, apiEntryPoint); } } @@ -114,7 +120,7 @@ export class ApiModelGenerator { // export { example1, example2 } // // The current logic does not try to associate "thing()" with a specific parent. Instead - // the API documentation will show duplicated entries for example1.thing() and example2.thing()./ + // the API documentation will show duplicated entries for example1.thing() and example2.thing(). // // This could be improved in the future, but it requires a stable mechanism for choosing an associated parent. // For thoughts about this: https://github.com/microsoft/rushstack/issues/1308 @@ -144,7 +150,8 @@ export class ApiModelGenerator { name, docComment: undefined, releaseTag: ReleaseTag.None, - excerptTokens: [] + excerptTokens: [], + isExported: true }); parentApiItem.addMember(apiNamespace); } @@ -393,6 +400,7 @@ export class ApiModelGenerator { const apiItemMetadata: ApiItemMetadata = this._collector.fetchApiItemMetadata(astDeclaration); const docComment: tsdoc.DocComment | undefined = apiItemMetadata.tsdocComment; const releaseTag: ReleaseTag = apiItemMetadata.effectiveReleaseTag; + const isExported: boolean = this._isExported(astDeclaration); apiClass = new ApiClass({ name, @@ -401,7 +409,8 @@ export class ApiModelGenerator { excerptTokens, typeParameters, extendsTokenRange, - implementsTokenRanges + implementsTokenRanges, + isExported }); parentApiItem.addMember(apiClass); @@ -477,8 +486,9 @@ export class ApiModelGenerator { const releaseTag: ReleaseTag = apiItemMetadata.effectiveReleaseTag; const preserveMemberOrder: boolean = this._collector.extractorConfig.enumMemberOrder === EnumMemberOrder.Preserve; + const isExported: boolean = this._isExported(astDeclaration); - apiEnum = new ApiEnum({ name, docComment, releaseTag, excerptTokens, preserveMemberOrder }); + apiEnum = new ApiEnum({ name, docComment, releaseTag, excerptTokens, preserveMemberOrder, isExported }); parentApiItem.addMember(apiEnum); } @@ -560,9 +570,7 @@ export class ApiModelGenerator { const apiItemMetadata: ApiItemMetadata = this._collector.fetchApiItemMetadata(astDeclaration); const docComment: tsdoc.DocComment | undefined = apiItemMetadata.tsdocComment; const releaseTag: ReleaseTag = apiItemMetadata.effectiveReleaseTag; - if (releaseTag === ReleaseTag.Internal || releaseTag === ReleaseTag.Alpha) { - return; // trim out items marked as "@internal" or "@alpha" - } + const isExported: boolean = this._isExported(astDeclaration); apiFunction = new ApiFunction({ name, @@ -572,7 +580,8 @@ export class ApiModelGenerator { parameters, overloadIndex, excerptTokens, - returnTypeTokenRange + returnTypeTokenRange, + isExported }); parentApiItem.addMember(apiFunction); @@ -664,6 +673,7 @@ export class ApiModelGenerator { const apiItemMetadata: ApiItemMetadata = this._collector.fetchApiItemMetadata(astDeclaration); const docComment: tsdoc.DocComment | undefined = apiItemMetadata.tsdocComment; const releaseTag: ReleaseTag = apiItemMetadata.effectiveReleaseTag; + const isExported: boolean = this._isExported(astDeclaration); apiInterface = new ApiInterface({ name, @@ -671,7 +681,8 @@ export class ApiModelGenerator { releaseTag, excerptTokens, typeParameters, - extendsTokenRanges + extendsTokenRanges, + isExported }); parentApiItem.addMember(apiInterface); @@ -812,8 +823,9 @@ export class ApiModelGenerator { const apiItemMetadata: ApiItemMetadata = this._collector.fetchApiItemMetadata(astDeclaration); const docComment: tsdoc.DocComment | undefined = apiItemMetadata.tsdocComment; const releaseTag: ReleaseTag = apiItemMetadata.effectiveReleaseTag; + const isExported: boolean = this._isExported(astDeclaration); - apiNamespace = new ApiNamespace({ name, docComment, releaseTag, excerptTokens }); + apiNamespace = new ApiNamespace({ name, docComment, releaseTag, excerptTokens, isExported }); parentApiItem.addMember(apiNamespace); } @@ -961,6 +973,7 @@ export class ApiModelGenerator { const apiItemMetadata: ApiItemMetadata = this._collector.fetchApiItemMetadata(astDeclaration); const docComment: tsdoc.DocComment | undefined = apiItemMetadata.tsdocComment; const releaseTag: ReleaseTag = apiItemMetadata.effectiveReleaseTag; + const isExported: boolean = this._isExported(astDeclaration); apiTypeAlias = new ApiTypeAlias({ name, @@ -968,7 +981,8 @@ export class ApiModelGenerator { typeParameters, releaseTag, excerptTokens, - typeTokenRange + typeTokenRange, + isExported }); parentApiItem.addMember(apiTypeAlias); @@ -1006,6 +1020,7 @@ export class ApiModelGenerator { const docComment: tsdoc.DocComment | undefined = apiItemMetadata.tsdocComment; const releaseTag: ReleaseTag = apiItemMetadata.effectiveReleaseTag; const isReadonly: boolean = this._isReadonly(astDeclaration); + const isExported: boolean = this._isExported(astDeclaration); apiVariable = new ApiVariable({ name, @@ -1014,7 +1029,8 @@ export class ApiModelGenerator { excerptTokens, variableTypeTokenRange, initializerTokenRange, - isReadonly + isReadonly, + isExported }); parentApiItem.addMember(apiVariable); @@ -1119,4 +1135,18 @@ export class ApiModelGenerator { } } } + + private _isExported(astDeclaration: AstDeclaration): boolean { + // Collector entities are only created for root symbols. + const entity: CollectorEntity | undefined = this._collector.tryGetCollectorEntity( + astDeclaration.astSymbol.rootAstSymbol + ); + + if (!entity) { + // This should never happen. + throw new InternalError('Failed to get collector entity for root symbol of declaration'); + } + + return entity.exported; + } } diff --git a/apps/api-extractor/src/generators/ApiReportGenerator.ts b/apps/api-extractor/src/generators/ApiReportGenerator.ts index 4f927135a4c..e2d33c82f40 100644 --- a/apps/api-extractor/src/generators/ApiReportGenerator.ts +++ b/apps/api-extractor/src/generators/ApiReportGenerator.ts @@ -78,7 +78,7 @@ export class ApiReportGenerator { // Emit the regular declarations for (const entity of collector.entities) { const astEntity: AstEntity = entity.astEntity; - if (entity.consumable) { + if (entity.exported || collector.extractorConfig.apiReportIncludeForgottenExports) { // First, collect the list of export names for this symbol. When reporting messages with // ExtractorMessage.properties.exportName, this will enable us to emit the warning comments alongside // the associated export statement. @@ -146,7 +146,7 @@ export class ApiReportGenerator { if (astModuleExportInfo.starExportedExternalModules.size > 0) { // We could support this, but we would need to find a way to safely represent it. throw new Error( - `The ${entity.nameForEmit} namespace import includes a start export, which is not supported:\n` + + `The ${entity.nameForEmit} namespace import includes a star export, which is not supported:\n` + SourceFileLocationFormatter.formatDeclaration(astEntity.declaration) ); } @@ -172,21 +172,20 @@ export class ApiReportGenerator { writer.increaseIndent(); const exportClauses: string[] = []; - for (const [exportedName, exportedEntity] of astModuleExportInfo.exportedLocalEntities) { - const collectorEntity: CollectorEntity | undefined = - collector.tryGetCollectorEntity(exportedEntity); + for (const [exportName, astEntity] of astModuleExportInfo.exportedLocalEntities) { + const collectorEntity: CollectorEntity | undefined = collector.tryGetCollectorEntity(astEntity); if (collectorEntity === undefined) { // This should never happen // top-level exports of local imported module should be added as collector entities before throw new InternalError( - `Cannot find collector entity for ${entity.nameForEmit}.${exportedEntity.localName}` + `Cannot find collector entity for ${entity.nameForEmit}.${astEntity.localName}` ); } - if (collectorEntity.nameForEmit === exportedName) { + if (collectorEntity.nameForEmit === exportName) { exportClauses.push(collectorEntity.nameForEmit); } else { - exportClauses.push(`${collectorEntity.nameForEmit} as ${exportedName}`); + exportClauses.push(`${collectorEntity.nameForEmit} as ${exportName}`); } } writer.writeLine(exportClauses.join(',\n')); diff --git a/apps/api-extractor/src/schemas/api-extractor-defaults.json b/apps/api-extractor/src/schemas/api-extractor-defaults.json index a6f1afbe4dc..d3a949e4c3f 100644 --- a/apps/api-extractor/src/schemas/api-extractor-defaults.json +++ b/apps/api-extractor/src/schemas/api-extractor-defaults.json @@ -18,12 +18,14 @@ // ("enabled" is required) "reportFileName": ".api.md", "reportFolder": "/etc/", - "reportTempFolder": "/temp/" + "reportTempFolder": "/temp/", + "includeForgottenExports": false }, "docModel": { // ("enabled" is required) - "apiJsonFilePath": "/temp/.api.json" + "apiJsonFilePath": "/temp/.api.json", + "includeForgottenExports": false }, "dtsRollup": { diff --git a/apps/api-extractor/src/schemas/api-extractor-template.json b/apps/api-extractor/src/schemas/api-extractor-template.json index 0dcdc009009..53c09b3db99 100644 --- a/apps/api-extractor/src/schemas/api-extractor-template.json +++ b/apps/api-extractor/src/schemas/api-extractor-template.json @@ -80,8 +80,9 @@ // "testMode": false, /** - * Specifies how API Extractor sorts members of an enum when generating api.json. By default, the output files - * will be sorted alphabetically, which is "by-name". To keep the ordering in the source code, specify "preserve". + * Specifies how API Extractor sorts members of an enum when generating the .api.json file. By default, the output + * files will be sorted alphabetically, which is "by-name". To keep the ordering in the source code, specify + * "preserve". * * DEFAULT VALUE: "by-name" */ @@ -175,7 +176,16 @@ * SUPPORTED TOKENS: , , * DEFAULT VALUE: "/temp/" */ - // "reportTempFolder": "/temp/" + // "reportTempFolder": "/temp/", + + /** + * Whether "forgotten exports" should be included in the API report file. Forgotten exports are declarations + * flagged with `ae-forgotten-export` warnings. See https://api-extractor.com/pages/messages/ae-forgotten-export/ to + * learn more. + * + * DEFAULT VALUE: "false" + */ + // includeForgottenExports?: boolean }, /** @@ -196,7 +206,16 @@ * SUPPORTED TOKENS: , , * DEFAULT VALUE: "/temp/.api.json" */ - // "apiJsonFilePath": "/temp/.api.json" + // "apiJsonFilePath": "/temp/.api.json", + + /** + * Whether "forgotten exports" should be included in the doc model file. Forgotten exports are declarations + * flagged with `ae-forgotten-export` warnings. See https://api-extractor.com/pages/messages/ae-forgotten-export/ to + * learn more. + * + * DEFAULT VALUE: "false" + */ + // includeForgottenExports?: boolean }, /** diff --git a/apps/api-extractor/src/schemas/api-extractor.schema.json b/apps/api-extractor/src/schemas/api-extractor.schema.json index 1556487f0b3..14d18a4f6cf 100644 --- a/apps/api-extractor/src/schemas/api-extractor.schema.json +++ b/apps/api-extractor/src/schemas/api-extractor.schema.json @@ -32,7 +32,7 @@ }, "enumMemberOrder": { - "description": "Specifies how API Extractor sorts the members of an enum when generating the .api.json doc model. \n 'by-name': sort the items according to the enum member name \n 'preserve': keep the original order that items appear in the source code", + "description": "Specifies how API Extractor sorts the members of an enum when generating the .api.json doc model. \n 'by-name': sort the items according to the enum member name \n 'preserve': keep the original order that items appear in the source code", "type": "string", "enum": ["by-name", "preserve"], "default": "by-name" @@ -80,6 +80,11 @@ "reportTempFolder": { "description": "Specifies the folder where the temporary report file is written. The file name portion is determined by the \"reportFileName\" setting. After the temporary file is written to disk, it is compared with the file in the \"reportFolder\". If they are different, a production build will fail. The path is resolved relative to the folder of the config file that contains the setting; to change this, prepend a folder token such as \"\".", "type": "string" + }, + + "includeForgottenExports": { + "description": "Whether \"forgotten exports\" should be included in the API report file. Forgotten exports are declarations flagged with `ae-forgotten-export` warnings. See https://api-extractor.com/pages/messages/ae-forgotten-export/ to learn more.", + "type": "boolean" } }, "required": ["enabled"], @@ -97,6 +102,11 @@ "apiJsonFilePath": { "description": "The output path for the doc model file. The file extension should be \".api.json\". The path is resolved relative to the folder of the config file that contains the setting; to change this, prepend a folder token such as \"\".", "type": "string" + }, + + "includeForgottenExports": { + "description": "Whether \"forgotten exports\" should be included in the doc model file. Forgotten exports are declarations flagged with `ae-forgotten-export` warnings. See https://api-extractor.com/pages/messages/ae-forgotten-export/ to learn more.", + "type": "boolean" } }, "required": ["enabled"], diff --git a/build-tests/api-extractor-lib1-test/etc/api-extractor-lib1-test.api.md b/build-tests/api-extractor-lib1-test/etc/api-extractor-lib1-test.api.md index d7856bac80f..ae7e32783b4 100644 --- a/build-tests/api-extractor-lib1-test/etc/api-extractor-lib1-test.api.md +++ b/build-tests/api-extractor-lib1-test/etc/api-extractor-lib1-test.api.md @@ -37,5 +37,4 @@ export namespace Lib1Namespace { } } - ``` diff --git a/build-tests/api-extractor-lib2-test/etc/api-extractor-lib2-test.api.md b/build-tests/api-extractor-lib2-test/etc/api-extractor-lib2-test.api.md index c44758dcb4f..f33239b2e9e 100644 --- a/build-tests/api-extractor-lib2-test/etc/api-extractor-lib2-test.api.md +++ b/build-tests/api-extractor-lib2-test/etc/api-extractor-lib2-test.api.md @@ -7,7 +7,6 @@ // @public (undocumented) class DefaultClass { } - export default DefaultClass; // @public (undocumented) @@ -18,5 +17,4 @@ export class Lib2Class { export interface Lib2Interface { } - ``` diff --git a/build-tests/api-extractor-scenarios/config/build-config.json b/build-tests/api-extractor-scenarios/config/build-config.json index b1c0327f4e7..9501b49e9f2 100644 --- a/build-tests/api-extractor-scenarios/config/build-config.json +++ b/build-tests/api-extractor-scenarios/config/build-config.json @@ -33,6 +33,7 @@ "functionOverload", "importEquals", "importType", + "includeForgottenExports", "inconsistentReleaseTags", "internationalCharacters", "mergedDeclarations", diff --git a/build-tests/api-extractor-scenarios/etc/exportImportStarAs/api-extractor-scenarios.api.json b/build-tests/api-extractor-scenarios/etc/exportImportStarAs/api-extractor-scenarios.api.json index 0c8f7cfee5f..98e888539c8 100644 --- a/build-tests/api-extractor-scenarios/etc/exportImportStarAs/api-extractor-scenarios.api.json +++ b/build-tests/api-extractor-scenarios/etc/exportImportStarAs/api-extractor-scenarios.api.json @@ -243,12 +243,12 @@ }, { "kind": "Variable", - "canonicalReference": "api-extractor-scenarios!calculator.calucatorVersion:var", + "canonicalReference": "api-extractor-scenarios!calculator.calculatorVersion:var", "docComment": "/**\n * Returns the version of the calculator.\n *\n * @public\n */\n", "excerptTokens": [ { "kind": "Content", - "text": "calucatorVersion: " + "text": "calculatorVersion: " }, { "kind": "Content", @@ -257,7 +257,7 @@ ], "isReadonly": true, "releaseTag": "Public", - "name": "calucatorVersion", + "name": "calculatorVersion", "variableTypeTokenRange": { "startIndex": 1, "endIndex": 2 @@ -396,12 +396,12 @@ }, { "kind": "Variable", - "canonicalReference": "api-extractor-scenarios!calculator2.calucatorVersion:var", + "canonicalReference": "api-extractor-scenarios!calculator2.calculatorVersion:var", "docComment": "/**\n * Returns the version of the calculator.\n *\n * @public\n */\n", "excerptTokens": [ { "kind": "Content", - "text": "calucatorVersion: " + "text": "calculatorVersion: " }, { "kind": "Content", @@ -410,7 +410,7 @@ ], "isReadonly": true, "releaseTag": "Public", - "name": "calucatorVersion", + "name": "calculatorVersion", "variableTypeTokenRange": { "startIndex": 1, "endIndex": 2 diff --git a/build-tests/api-extractor-scenarios/etc/exportImportStarAs/api-extractor-scenarios.api.md b/build-tests/api-extractor-scenarios/etc/exportImportStarAs/api-extractor-scenarios.api.md index 6c1cc0cec80..a05e6f34a4a 100644 --- a/build-tests/api-extractor-scenarios/etc/exportImportStarAs/api-extractor-scenarios.api.md +++ b/build-tests/api-extractor-scenarios/etc/exportImportStarAs/api-extractor-scenarios.api.md @@ -14,7 +14,7 @@ declare namespace calculator { export { add, subtract, - calucatorVersion + calculatorVersion } } export { calculator } @@ -23,13 +23,13 @@ declare namespace calculator2 { export { add_2 as add, subtract_2 as subtract, - calucatorVersion + calculatorVersion } } export { calculator2 } // @public -const calucatorVersion: string; +const calculatorVersion: string; // @beta function subtract(a: number, b: number): number; diff --git a/build-tests/api-extractor-scenarios/etc/exportImportStarAs/rollup.d.ts b/build-tests/api-extractor-scenarios/etc/exportImportStarAs/rollup.d.ts index bd8c3300e64..042af626326 100644 --- a/build-tests/api-extractor-scenarios/etc/exportImportStarAs/rollup.d.ts +++ b/build-tests/api-extractor-scenarios/etc/exportImportStarAs/rollup.d.ts @@ -20,7 +20,7 @@ declare namespace calculator { export { add, subtract, - calucatorVersion + calculatorVersion } } export { calculator } @@ -29,7 +29,7 @@ declare namespace calculator2 { export { add_2 as add, subtract_2 as subtract, - calucatorVersion + calculatorVersion } } export { calculator2 } @@ -38,7 +38,7 @@ export { calculator2 } * Returns the version of the calculator. * @public */ -declare const calucatorVersion: string; +declare const calculatorVersion: string; /** * Returns the sum of subtracting `b` from `a` diff --git a/build-tests/api-extractor-scenarios/etc/exportImportStarAs2/api-extractor-scenarios.api.md b/build-tests/api-extractor-scenarios/etc/exportImportStarAs2/api-extractor-scenarios.api.md index 0acea2fe3d6..be6c99dbe02 100644 --- a/build-tests/api-extractor-scenarios/etc/exportImportStarAs2/api-extractor-scenarios.api.md +++ b/build-tests/api-extractor-scenarios/etc/exportImportStarAs2/api-extractor-scenarios.api.md @@ -9,10 +9,6 @@ // @public (undocumented) function exportedApi(): forgottenNs.ForgottenClass; -// @public (undocumented) -class ForgottenClass { -} - declare namespace ns { export { exportedApi 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 new file mode 100644 index 00000000000..db315044ac3 --- /dev/null +++ b/build-tests/api-extractor-scenarios/etc/includeForgottenExports/api-extractor-scenarios.api.json @@ -0,0 +1,371 @@ +{ + "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": "TypeAlias", + "canonicalReference": "api-extractor-scenarios!~AnotherForgottenExport:type", + "docComment": "/**\n * {@inheritDoc ForgottenExport}\n */\n", + "excerptTokens": [ + { + "kind": "Content", + "text": "declare type AnotherForgottenExport = " + }, + { + "kind": "Content", + "text": "number" + }, + { + "kind": "Content", + "text": ";" + } + ], + "releaseTag": "None", + "name": "AnotherForgottenExport", + "typeTokenRange": { + "startIndex": 1, + "endIndex": 2 + } + }, + { + "kind": "Function", + "canonicalReference": "api-extractor-scenarios!anotherFunction:function(1)", + "docComment": "/**\n * @public\n */\n", + "excerptTokens": [ + { + "kind": "Content", + "text": "export declare function anotherFunction(): " + }, + { + "kind": "Reference", + "text": "DuplicateName", + "canonicalReference": "api-extractor-scenarios!~DuplicateName:type" + }, + { + "kind": "Content", + "text": ";" + } + ], + "returnTypeTokenRange": { + "startIndex": 1, + "endIndex": 2 + }, + "releaseTag": "Public", + "overloadIndex": 1, + "parameters": [], + "name": "anotherFunction" + }, + { + "kind": "TypeAlias", + "canonicalReference": "api-extractor-scenarios!~DuplicateName_2:type", + "docComment": "", + "excerptTokens": [ + { + "kind": "Content", + "text": "declare type DuplicateName = " + }, + { + "kind": "Content", + "text": "number" + }, + { + "kind": "Content", + "text": ";" + } + ], + "releaseTag": "None", + "name": "DuplicateName_2", + "typeTokenRange": { + "startIndex": 1, + "endIndex": 2 + } + }, + { + "kind": "TypeAlias", + "canonicalReference": "api-extractor-scenarios!DuplicateName:type", + "docComment": "/**\n * This type is exported but has the same name as an unexported type in './internal.ts'. This unexported type is also included in the API report and doc model files. The unexported type will be renamed to avoid a name conflict.\n *\n * @public\n */\n", + "excerptTokens": [ + { + "kind": "Content", + "text": "export declare type DuplicateName = " + }, + { + "kind": "Content", + "text": "boolean" + }, + { + "kind": "Content", + "text": ";" + } + ], + "releaseTag": "Public", + "name": "DuplicateName", + "typeTokenRange": { + "startIndex": 1, + "endIndex": 2 + } + }, + { + "kind": "Class", + "canonicalReference": "api-extractor-scenarios!~ForgottenExport:class", + "docComment": "/**\n * This doc comment should be inherited by `AnotherForgottenExport`\n */\n", + "excerptTokens": [ + { + "kind": "Content", + "text": "declare class ForgottenExport " + } + ], + "releaseTag": "None", + "name": "ForgottenExport", + "preserveMemberOrder": false, + "members": [ + { + "kind": "Constructor", + "canonicalReference": "api-extractor-scenarios!~ForgottenExport:constructor(1)", + "docComment": "/**\n * Constructs a new instance of the `ForgottenExport` class\n */\n", + "excerptTokens": [ + { + "kind": "Content", + "text": "constructor();" + } + ], + "releaseTag": "None", + "isProtected": false, + "overloadIndex": 1, + "parameters": [] + }, + { + "kind": "Property", + "canonicalReference": "api-extractor-scenarios!~ForgottenExport#prop:member", + "docComment": "", + "excerptTokens": [ + { + "kind": "Content", + "text": "prop?: " + }, + { + "kind": "Reference", + "text": "AnotherForgottenExport", + "canonicalReference": "api-extractor-scenarios!~AnotherForgottenExport:type" + }, + { + "kind": "Content", + "text": ";" + } + ], + "isReadonly": false, + "isOptional": true, + "releaseTag": "None", + "name": "prop", + "propertyTypeTokenRange": { + "startIndex": 1, + "endIndex": 2 + }, + "isStatic": false, + "isProtected": false + } + ], + "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": "ForgottenExport", + "canonicalReference": "api-extractor-scenarios!~ForgottenExport: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/includeForgottenExports/api-extractor-scenarios.api.md b/build-tests/api-extractor-scenarios/etc/includeForgottenExports/api-extractor-scenarios.api.md new file mode 100644 index 00000000000..73b2f95f9ee --- /dev/null +++ b/build-tests/api-extractor-scenarios/etc/includeForgottenExports/api-extractor-scenarios.api.md @@ -0,0 +1,38 @@ +## 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 + +// Warning: (ae-unresolved-inheritdoc-reference) The @inheritDoc reference could not be resolved: The package "api-extractor-scenarios" does not have an export "ForgottenExport" +// +// (undocumented) +type AnotherForgottenExport = number; + +// Warning: (ae-forgotten-export) The symbol "DuplicateName" needs to be exported by the entry point index.d.ts +// +// @public (undocumented) +export function anotherFunction(): DuplicateName_2; + +// @public +export type DuplicateName = boolean; + +// (undocumented) +type DuplicateName_2 = number; + +class ForgottenExport { + constructor(); + // Warning: (ae-forgotten-export) The symbol "AnotherForgottenExport" needs to be exported by the entry point index.d.ts + // + // (undocumented) + prop?: AnotherForgottenExport; +} + +// Warning: (ae-forgotten-export) The symbol "ForgottenExport" needs to be exported by the entry point index.d.ts +// +// @public (undocumented) +export function someFunction(): ForgottenExport; + +// (No @packageDocumentation comment for this package) + +``` diff --git a/build-tests/api-extractor-scenarios/etc/includeForgottenExports/rollup.d.ts b/build-tests/api-extractor-scenarios/etc/includeForgottenExports/rollup.d.ts new file mode 100644 index 00000000000..1c8e8798337 --- /dev/null +++ b/build-tests/api-extractor-scenarios/etc/includeForgottenExports/rollup.d.ts @@ -0,0 +1,26 @@ +/** {@inheritDoc ForgottenExport} */ +declare type AnotherForgottenExport = number; + +/** @public */ +export declare function anotherFunction(): DuplicateName_2; + +/** + * This type is exported but has the same name as an unexported type in './internal.ts'. This + * unexported type is also included in the API report and doc model files. The unexported type + * will be renamed to avoid a name conflict. + * @public + */ +export declare type DuplicateName = boolean; + +declare type DuplicateName_2 = number; + +/** This doc comment should be inherited by `AnotherForgottenExport` */ +declare class ForgottenExport { + prop?: AnotherForgottenExport; + constructor(); +} + +/** @public */ +export declare function someFunction(): ForgottenExport; + +export { } diff --git a/build-tests/api-extractor-scenarios/src/exportImportStarAs/common.ts b/build-tests/api-extractor-scenarios/src/exportImportStarAs/common.ts index 1e4914ce34e..67d0445901d 100644 --- a/build-tests/api-extractor-scenarios/src/exportImportStarAs/common.ts +++ b/build-tests/api-extractor-scenarios/src/exportImportStarAs/common.ts @@ -2,4 +2,4 @@ * Returns the version of the calculator. * @public */ -export const calucatorVersion: string = '1.0.0'; +export const calculatorVersion: string = '1.0.0'; diff --git a/build-tests/api-extractor-scenarios/src/includeForgottenExports/config/api-extractor-overrides.json b/build-tests/api-extractor-scenarios/src/includeForgottenExports/config/api-extractor-overrides.json new file mode 100644 index 00000000000..103f3c5ec14 --- /dev/null +++ b/build-tests/api-extractor-scenarios/src/includeForgottenExports/config/api-extractor-overrides.json @@ -0,0 +1,13 @@ +{ + "apiReport": { + "enabled": true, + "reportFolder": "/etc/includeForgottenExports", + "includeForgottenExports": true + }, + + "docModel": { + "enabled": true, + "apiJsonFilePath": "/etc/includeForgottenExports/.api.json", + "includeForgottenExports": true + } +} diff --git a/build-tests/api-extractor-scenarios/src/includeForgottenExports/index.ts b/build-tests/api-extractor-scenarios/src/includeForgottenExports/index.ts new file mode 100644 index 00000000000..ef0e6d3dcb5 --- /dev/null +++ b/build-tests/api-extractor-scenarios/src/includeForgottenExports/index.ts @@ -0,0 +1,23 @@ +/** This doc comment should be inherited by `AnotherForgottenExport` */ +class ForgottenExport { + prop?: AnotherForgottenExport; + constructor() {} +} + +/** {@inheritDoc ForgottenExport} */ +type AnotherForgottenExport = number; + +/** @public */ +export function someFunction(): ForgottenExport { + return new ForgottenExport(); +} + +/** + * This type is exported but has the same name as an unexported type in './internal.ts'. This + * unexported type is also included in the API report and doc model files. The unexported type + * will be renamed to avoid a name conflict. + * @public + */ +export type DuplicateName = boolean; + +export { anotherFunction } from './internal'; diff --git a/build-tests/api-extractor-scenarios/src/includeForgottenExports/internal.ts b/build-tests/api-extractor-scenarios/src/includeForgottenExports/internal.ts new file mode 100644 index 00000000000..ef68abebf8c --- /dev/null +++ b/build-tests/api-extractor-scenarios/src/includeForgottenExports/internal.ts @@ -0,0 +1,6 @@ +type DuplicateName = number; + +/** @public */ +export function anotherFunction(): DuplicateName { + return 5; +} diff --git a/build-tests/install-test-workspace/workspace/common/pnpm-lock.yaml b/build-tests/install-test-workspace/workspace/common/pnpm-lock.yaml index 0edc8cb3e66..0e31dce8c0a 100644 --- a/build-tests/install-test-workspace/workspace/common/pnpm-lock.yaml +++ b/build-tests/install-test-workspace/workspace/common/pnpm-lock.yaml @@ -4,28 +4,28 @@ importers: typescript-newest-test: specifiers: - '@rushstack/eslint-config': file:rushstack-eslint-config-3.0.0.tgz - '@rushstack/heft': file:rushstack-heft-0.47.0.tgz + '@rushstack/eslint-config': file:rushstack-eslint-config-2.6.2.tgz + '@rushstack/heft': file:rushstack-heft-0.46.6.tgz eslint: ~8.7.0 tslint: ~5.20.1 typescript: ~4.7.4 devDependencies: - '@rushstack/eslint-config': file:../temp/tarballs/rushstack-eslint-config-3.0.0.tgz_eslint@8.7.0+typescript@4.7.4 - '@rushstack/heft': file:../temp/tarballs/rushstack-heft-0.47.0.tgz + '@rushstack/eslint-config': file:../temp/tarballs/rushstack-eslint-config-2.6.2.tgz_eslint@8.7.0+typescript@4.6.4 + '@rushstack/heft': file:../temp/tarballs/rushstack-heft-0.46.6.tgz eslint: 8.7.0 tslint: 5.20.1_typescript@4.7.4 typescript: 4.7.4 typescript-v3-test: specifiers: - '@rushstack/eslint-config': file:rushstack-eslint-config-3.0.0.tgz - '@rushstack/heft': file:rushstack-heft-0.47.0.tgz + '@rushstack/eslint-config': file:rushstack-eslint-config-2.6.2.tgz + '@rushstack/heft': file:rushstack-heft-0.46.6.tgz eslint: ~8.7.0 tslint: ~5.20.1 typescript: ~4.7.4 devDependencies: - '@rushstack/eslint-config': file:../temp/tarballs/rushstack-eslint-config-3.0.0.tgz_eslint@8.7.0+typescript@4.7.4 - '@rushstack/heft': file:../temp/tarballs/rushstack-heft-0.47.0.tgz + '@rushstack/eslint-config': file:../temp/tarballs/rushstack-eslint-config-2.6.2.tgz_eslint@8.7.0+typescript@4.6.4 + '@rushstack/heft': file:../temp/tarballs/rushstack-heft-0.46.6.tgz eslint: 8.7.0 tslint: 5.20.1_typescript@4.7.4 typescript: 4.7.4 @@ -1803,10 +1803,10 @@ packages: - typescript dev: true - file:../temp/tarballs/rushstack-heft-0.47.0.tgz: - resolution: {tarball: file:../temp/tarballs/rushstack-heft-0.47.0.tgz} + file:../temp/tarballs/rushstack-heft-0.46.6.tgz: + resolution: {tarball: file:../temp/tarballs/rushstack-heft-0.46.6.tgz} name: '@rushstack/heft' - version: 0.47.0 + version: 0.46.6 engines: {node: '>=10.13.0'} hasBin: true dependencies: diff --git a/common/reviews/api/api-extractor-model.api.md b/common/reviews/api/api-extractor-model.api.md index c062e02597f..a9505bde992 100644 --- a/common/reviews/api/api-extractor-model.api.md +++ b/common/reviews/api/api-extractor-model.api.md @@ -172,6 +172,21 @@ export class ApiEnumMember extends ApiEnumMember_base { get kind(): ApiItemKind; } +// @public +export function ApiExportedMixin(baseClass: TBaseClass): TBaseClass & (new (...args: any[]) => ApiExportedMixin); + +// @public +export interface ApiExportedMixin extends ApiItem { + readonly isExported: boolean; + // @override (undocumented) + serializeInto(jsonObject: Partial): void; +} + +// @public +export namespace ApiExportedMixin { + export function isBaseClassOf(apiItem: ApiItem): apiItem is ApiExportedMixin; +} + // Warning: (ae-forgotten-export) The symbol "ApiFunction_base" needs to be exported by the entry point index.d.ts // // @public @@ -708,7 +723,7 @@ export interface IApiCallSignatureOptions extends IApiTypeParameterListMixinOpti } // @public -export interface IApiClassOptions extends IApiItemContainerMixinOptions, IApiNameMixinOptions, IApiReleaseTagMixinOptions, IApiDeclaredItemOptions, IApiTypeParameterListMixinOptions { +export interface IApiClassOptions extends IApiItemContainerMixinOptions, IApiNameMixinOptions, IApiReleaseTagMixinOptions, IApiDeclaredItemOptions, IApiTypeParameterListMixinOptions, IApiExportedMixinOptions { // (undocumented) extendsTokenRange: IExcerptTokenRange | undefined; // (undocumented) @@ -744,11 +759,17 @@ export interface IApiEnumMemberOptions extends IApiNameMixinOptions, IApiRelease } // @public -export interface IApiEnumOptions extends IApiItemContainerMixinOptions, IApiNameMixinOptions, IApiReleaseTagMixinOptions, IApiDeclaredItemOptions { +export interface IApiEnumOptions extends IApiItemContainerMixinOptions, IApiNameMixinOptions, IApiReleaseTagMixinOptions, IApiDeclaredItemOptions, IApiExportedMixinOptions { +} + +// @public +export interface IApiExportedMixinOptions extends IApiItemOptions { + // (undocumented) + isExported: boolean; } // @public -export interface IApiFunctionOptions extends IApiNameMixinOptions, IApiTypeParameterListMixinOptions, IApiParameterListMixinOptions, IApiReleaseTagMixinOptions, IApiReturnTypeMixinOptions, IApiDeclaredItemOptions { +export interface IApiFunctionOptions extends IApiNameMixinOptions, IApiTypeParameterListMixinOptions, IApiParameterListMixinOptions, IApiReleaseTagMixinOptions, IApiReturnTypeMixinOptions, IApiDeclaredItemOptions, IApiExportedMixinOptions { } // @public @@ -762,7 +783,7 @@ export interface IApiInitializerMixinOptions extends IApiItemOptions { } // @public -export interface IApiInterfaceOptions extends IApiItemContainerMixinOptions, IApiNameMixinOptions, IApiTypeParameterListMixinOptions, IApiReleaseTagMixinOptions, IApiDeclaredItemOptions { +export interface IApiInterfaceOptions extends IApiItemContainerMixinOptions, IApiNameMixinOptions, IApiTypeParameterListMixinOptions, IApiReleaseTagMixinOptions, IApiDeclaredItemOptions, IApiExportedMixinOptions { // (undocumented) extendsTokenRanges: IExcerptTokenRange[]; } @@ -798,7 +819,7 @@ export interface IApiNameMixinOptions extends IApiItemOptions { } // @public -export interface IApiNamespaceOptions extends IApiItemContainerMixinOptions, IApiNameMixinOptions, IApiReleaseTagMixinOptions, IApiDeclaredItemOptions { +export interface IApiNamespaceOptions extends IApiItemContainerMixinOptions, IApiNameMixinOptions, IApiReleaseTagMixinOptions, IApiDeclaredItemOptions, IApiExportedMixinOptions { } // @public @@ -883,7 +904,7 @@ export interface IApiStaticMixinOptions extends IApiItemOptions { } // @public -export interface IApiTypeAliasOptions extends IApiNameMixinOptions, IApiReleaseTagMixinOptions, IApiDeclaredItemOptions, IApiTypeParameterListMixinOptions { +export interface IApiTypeAliasOptions extends IApiNameMixinOptions, IApiReleaseTagMixinOptions, IApiDeclaredItemOptions, IApiTypeParameterListMixinOptions, IApiExportedMixinOptions { // (undocumented) typeTokenRange: IExcerptTokenRange; } @@ -905,7 +926,7 @@ export interface IApiTypeParameterOptions { } // @public -export interface IApiVariableOptions extends IApiNameMixinOptions, IApiReleaseTagMixinOptions, IApiReadonlyMixinOptions, IApiDeclaredItemOptions, IApiInitializerMixinOptions { +export interface IApiVariableOptions extends IApiNameMixinOptions, IApiReleaseTagMixinOptions, IApiReadonlyMixinOptions, IApiDeclaredItemOptions, IApiInitializerMixinOptions, IApiExportedMixinOptions { // (undocumented) variableTypeTokenRange: IExcerptTokenRange; } diff --git a/common/reviews/api/api-extractor.api.md b/common/reviews/api/api-extractor.api.md index 0ad47931415..c9706b61323 100644 --- a/common/reviews/api/api-extractor.api.md +++ b/common/reviews/api/api-extractor.api.md @@ -49,9 +49,11 @@ export class ExtractorConfig { readonly alphaTrimmedFilePath: string; readonly apiJsonFilePath: string; readonly apiReportEnabled: boolean; + readonly apiReportIncludeForgottenExports: boolean; readonly betaTrimmedFilePath: string; readonly bundledPackages: string[]; readonly docModelEnabled: boolean; + readonly docModelIncludeForgottenExports: boolean; readonly enumMemberOrder: EnumMemberOrder; static readonly FILENAME: string; getDiagnosticDump(): string; @@ -168,6 +170,7 @@ export interface ICompilerStateCreateOptions { // @public export interface IConfigApiReport { enabled: boolean; + includeForgottenExports?: boolean; reportFileName?: string; reportFolder?: string; reportTempFolder?: string; @@ -184,6 +187,7 @@ export interface IConfigCompiler { export interface IConfigDocModel { apiJsonFilePath?: string; enabled: boolean; + includeForgottenExports?: boolean; } // @public diff --git a/libraries/api-extractor-model/src/index.ts b/libraries/api-extractor-model/src/index.ts index 6b475af89d1..7e3bf3b9b9e 100644 --- a/libraries/api-extractor-model/src/index.ts +++ b/libraries/api-extractor-model/src/index.ts @@ -39,6 +39,7 @@ export { IApiNameMixinOptions, ApiNameMixin } from './mixins/ApiNameMixin'; export { IApiOptionalMixinOptions, ApiOptionalMixin } from './mixins/ApiOptionalMixin'; export { IApiReadonlyMixinOptions, ApiReadonlyMixin } from './mixins/ApiReadonlyMixin'; export { IApiInitializerMixinOptions, ApiInitializerMixin } from './mixins/ApiInitializerMixin'; +export { IApiExportedMixinOptions, ApiExportedMixin } from './mixins/ApiExportedMixin'; export { IFindApiItemsResult, IFindApiItemsMessage, diff --git a/libraries/api-extractor-model/src/mixins/ApiExportedMixin.ts b/libraries/api-extractor-model/src/mixins/ApiExportedMixin.ts new file mode 100644 index 00000000000..2c06ad93d58 --- /dev/null +++ b/libraries/api-extractor-model/src/mixins/ApiExportedMixin.ts @@ -0,0 +1,119 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. +// See LICENSE in the project root for license information.s + +import { DeclarationReference, Navigation } from '@microsoft/tsdoc/lib-commonjs/beta/DeclarationReference'; +import { ApiItem, IApiItemJson, IApiItemConstructor, IApiItemOptions } from '../items/ApiItem'; +import { DeserializerContext } from '../model/DeserializerContext'; + +/** + * Constructor options for {@link (IApiExportedMixinOptions:interface)}. + * @public + */ +export interface IApiExportedMixinOptions extends IApiItemOptions { + isExported: boolean; +} + +export interface IApiExportedMixinJson extends IApiItemJson { + isExported: boolean; +} + +const _isExported: unique symbol = Symbol('ApiExportedMixin._isExported'); + +/** + * The mixin base class for API items that can be exported. + * + * @remarks + * + * This is part of the {@link ApiModel} hierarchy of classes, which are serializable representations of + * API declarations. The non-abstract classes (e.g. `ApiClass`, `ApiEnum`, `ApiInterface`, etc.) use + * TypeScript "mixin" functions (e.g. `ApiDeclaredItem`, `ApiItemContainerMixin`, etc.) to add various + * features that cannot be represented as a normal inheritance chain (since TypeScript does not allow a child class + * to extend more than one base class). The "mixin" is a TypeScript merged declaration with three components: + * the function that generates a subclass, an interface that describes the members of the subclass, and + * a namespace containing static members of the class. + * + * @public + */ +// eslint-disable-next-line @typescript-eslint/naming-convention +export interface ApiExportedMixin extends ApiItem { + /** + * Whether the declaration is exported. + */ + readonly isExported: boolean; + + /** @override */ + serializeInto(jsonObject: Partial): void; +} + +/** + * Mixin function for {@link (ApiExportedMixin:interface)}. + * + * @param baseClass - The base class to be extended + * @returns A child class that extends baseClass, adding the {@link (ApiExportedMixin:interface)} functionality. + * + * @public + */ +export function ApiExportedMixin( + baseClass: TBaseClass + // eslint-disable-next-line @typescript-eslint/no-explicit-any +): TBaseClass & (new (...args: any[]) => ApiExportedMixin) { + class MixedClass extends baseClass implements ApiExportedMixin { + public [_isExported]: boolean; + + // eslint-disable-next-line @typescript-eslint/no-explicit-any + public constructor(...args: any[]) { + super(...args); + + const options: IApiExportedMixinOptions = args[0]; + this[_isExported] = options.isExported; + } + + /** @override */ + public static onDeserializeInto( + options: Partial, + context: DeserializerContext, + jsonObject: IApiExportedMixinJson + ): void { + baseClass.onDeserializeInto(options, context, jsonObject); + + const declarationReference: DeclarationReference = DeclarationReference.parse( + jsonObject.canonicalReference + ); + options.isExported = declarationReference.navigation === Navigation.Exports; + } + + public get isExported(): boolean { + return this[_isExported]; + } + + /** + * The `isExported` property is intentionally not serialized because the information is already present + * in the item's `canonicalReference`. + * @override + */ + public serializeInto(jsonObject: Partial): void { + super.serializeInto(jsonObject); + } + } + + return MixedClass; +} + +/** + * Static members for {@link (ApiExportedMixin:interface)}. + * @public + */ +export namespace ApiExportedMixin { + /** + * A type guard that tests whether the specified `ApiItem` subclass extends the `ApiExportedMixin` mixin. + * + * @remarks + * + * The JavaScript `instanceof` operator cannot be used to test for mixin inheritance, because each invocation of + * the mixin function produces a different subclass. (This could be mitigated by `Symbol.hasInstance`, however + * the TypeScript type system cannot invoke a runtime test.) + */ + export function isBaseClassOf(apiItem: ApiItem): apiItem is ApiExportedMixin { + return apiItem.hasOwnProperty(_isExported); + } +} diff --git a/libraries/api-extractor-model/src/model/ApiClass.ts b/libraries/api-extractor-model/src/model/ApiClass.ts index df6de4ac5bb..d207d96a6b9 100644 --- a/libraries/api-extractor-model/src/model/ApiClass.ts +++ b/libraries/api-extractor-model/src/model/ApiClass.ts @@ -20,6 +20,11 @@ import { IApiTypeParameterListMixinJson } from '../mixins/ApiTypeParameterListMixin'; import { DeserializerContext } from './DeserializerContext'; +import { + IApiExportedMixinJson, + IApiExportedMixinOptions, + ApiExportedMixin +} from '../mixins/ApiExportedMixin'; /** * Constructor options for {@link ApiClass}. @@ -30,12 +35,16 @@ export interface IApiClassOptions IApiNameMixinOptions, IApiReleaseTagMixinOptions, IApiDeclaredItemOptions, - IApiTypeParameterListMixinOptions { + IApiTypeParameterListMixinOptions, + IApiExportedMixinOptions { extendsTokenRange: IExcerptTokenRange | undefined; implementsTokenRanges: IExcerptTokenRange[]; } -export interface IApiClassJson extends IApiDeclaredItemJson, IApiTypeParameterListMixinJson { +export interface IApiClassJson + extends IApiDeclaredItemJson, + IApiTypeParameterListMixinJson, + IApiExportedMixinJson { extendsTokenRange?: IExcerptTokenRange; implementsTokenRanges: IExcerptTokenRange[]; } @@ -57,7 +66,7 @@ export interface IApiClassJson extends IApiDeclaredItemJson, IApiTypeParameterLi * @public */ export class ApiClass extends ApiItemContainerMixin( - ApiNameMixin(ApiTypeParameterListMixin(ApiReleaseTagMixin(ApiDeclaredItem))) + ApiNameMixin(ApiTypeParameterListMixin(ApiReleaseTagMixin(ApiExportedMixin(ApiDeclaredItem)))) ) { /** * The base class that this class inherits from (using the `extends` keyword), or undefined if there is no base class. @@ -128,8 +137,9 @@ export class ApiClass extends ApiItemContainerMixin( /** @beta @override */ public buildCanonicalReference(): DeclarationReference { const nameComponent: Component = DeclarationReference.parseComponent(this.name); + const navigation: Navigation = this.isExported ? Navigation.Exports : Navigation.Locals; return (this.parent ? this.parent.canonicalReference : DeclarationReference.empty()) - .addNavigationStep(Navigation.Exports, nameComponent) + .addNavigationStep(navigation, nameComponent) .withMeaning(Meaning.Class); } } diff --git a/libraries/api-extractor-model/src/model/ApiEnum.ts b/libraries/api-extractor-model/src/model/ApiEnum.ts index 14675678135..f58a6d91272 100644 --- a/libraries/api-extractor-model/src/model/ApiEnum.ts +++ b/libraries/api-extractor-model/src/model/ApiEnum.ts @@ -13,6 +13,7 @@ import { ApiReleaseTagMixin, IApiReleaseTagMixinOptions } from '../mixins/ApiRel import { ApiItemContainerMixin, IApiItemContainerMixinOptions } from '../mixins/ApiItemContainerMixin'; import { ApiEnumMember } from './ApiEnumMember'; import { IApiNameMixinOptions, ApiNameMixin } from '../mixins/ApiNameMixin'; +import { IApiExportedMixinOptions, ApiExportedMixin } from '../mixins/ApiExportedMixin'; /** * Constructor options for {@link ApiEnum}. @@ -22,7 +23,8 @@ export interface IApiEnumOptions extends IApiItemContainerMixinOptions, IApiNameMixinOptions, IApiReleaseTagMixinOptions, - IApiDeclaredItemOptions {} + IApiDeclaredItemOptions, + IApiExportedMixinOptions {} /** * Represents a TypeScript enum declaration. @@ -44,7 +46,9 @@ export interface IApiEnumOptions * * @public */ -export class ApiEnum extends ApiItemContainerMixin(ApiNameMixin(ApiReleaseTagMixin(ApiDeclaredItem))) { +export class ApiEnum extends ApiItemContainerMixin( + ApiNameMixin(ApiReleaseTagMixin(ApiExportedMixin(ApiDeclaredItem))) +) { public constructor(options: IApiEnumOptions) { super(options); } @@ -79,8 +83,9 @@ export class ApiEnum extends ApiItemContainerMixin(ApiNameMixin(ApiReleaseTagMix /** @beta @override */ public buildCanonicalReference(): DeclarationReference { const nameComponent: Component = DeclarationReference.parseComponent(this.name); + const navigation: Navigation = this.isExported ? Navigation.Exports : Navigation.Locals; return (this.parent ? this.parent.canonicalReference : DeclarationReference.empty()) - .addNavigationStep(Navigation.Exports, nameComponent) + .addNavigationStep(navigation, nameComponent) .withMeaning(Meaning.Enum); } } diff --git a/libraries/api-extractor-model/src/model/ApiFunction.ts b/libraries/api-extractor-model/src/model/ApiFunction.ts index d74dc44e8a9..9bcba681ca4 100644 --- a/libraries/api-extractor-model/src/model/ApiFunction.ts +++ b/libraries/api-extractor-model/src/model/ApiFunction.ts @@ -17,6 +17,7 @@ import { IApiTypeParameterListMixinOptions, ApiTypeParameterListMixin } from '../mixins/ApiTypeParameterListMixin'; +import { IApiExportedMixinOptions, ApiExportedMixin } from '../mixins/ApiExportedMixin'; /** * Constructor options for {@link ApiFunction}. @@ -28,7 +29,8 @@ export interface IApiFunctionOptions IApiParameterListMixinOptions, IApiReleaseTagMixinOptions, IApiReturnTypeMixinOptions, - IApiDeclaredItemOptions {} + IApiDeclaredItemOptions, + IApiExportedMixinOptions {} /** * Represents a TypeScript function declaration. @@ -52,7 +54,9 @@ export interface IApiFunctionOptions * @public */ export class ApiFunction extends ApiNameMixin( - ApiTypeParameterListMixin(ApiParameterListMixin(ApiReleaseTagMixin(ApiReturnTypeMixin(ApiDeclaredItem)))) + ApiTypeParameterListMixin( + ApiParameterListMixin(ApiReleaseTagMixin(ApiReturnTypeMixin(ApiExportedMixin(ApiDeclaredItem)))) + ) ) { public constructor(options: IApiFunctionOptions) { super(options); @@ -75,8 +79,9 @@ export class ApiFunction extends ApiNameMixin( /** @beta @override */ public buildCanonicalReference(): DeclarationReference { const nameComponent: Component = DeclarationReference.parseComponent(this.name); + const navigation: Navigation = this.isExported ? Navigation.Exports : Navigation.Locals; return (this.parent ? this.parent.canonicalReference : DeclarationReference.empty()) - .addNavigationStep(Navigation.Exports, nameComponent) + .addNavigationStep(navigation, nameComponent) .withMeaning(Meaning.Function) .withOverloadIndex(this.overloadIndex); } diff --git a/libraries/api-extractor-model/src/model/ApiInterface.ts b/libraries/api-extractor-model/src/model/ApiInterface.ts index bef31906e2e..21bb42b806b 100644 --- a/libraries/api-extractor-model/src/model/ApiInterface.ts +++ b/libraries/api-extractor-model/src/model/ApiInterface.ts @@ -28,6 +28,11 @@ import { ApiTypeParameterListMixin } from '../mixins/ApiTypeParameterListMixin'; import { DeserializerContext } from './DeserializerContext'; +import { + IApiExportedMixinJson, + IApiExportedMixinOptions, + ApiExportedMixin +} from '../mixins/ApiExportedMixin'; /** * Constructor options for {@link ApiInterface}. @@ -38,7 +43,8 @@ export interface IApiInterfaceOptions IApiNameMixinOptions, IApiTypeParameterListMixinOptions, IApiReleaseTagMixinOptions, - IApiDeclaredItemOptions { + IApiDeclaredItemOptions, + IApiExportedMixinOptions { extendsTokenRanges: IExcerptTokenRange[]; } @@ -47,7 +53,8 @@ export interface IApiInterfaceJson IApiNameMixinJson, IApiTypeParameterListMixinJson, IApiReleaseTagMixinJson, - IApiDeclaredItemJson { + IApiDeclaredItemJson, + IApiExportedMixinJson { extendsTokenRanges: IExcerptTokenRange[]; } @@ -69,7 +76,7 @@ export interface IApiInterfaceJson * @public */ export class ApiInterface extends ApiItemContainerMixin( - ApiNameMixin(ApiTypeParameterListMixin(ApiReleaseTagMixin(ApiDeclaredItem))) + ApiNameMixin(ApiTypeParameterListMixin(ApiReleaseTagMixin(ApiExportedMixin(ApiDeclaredItem)))) ) { private readonly _extendsTypes: HeritageType[] = []; @@ -123,8 +130,9 @@ export class ApiInterface extends ApiItemContainerMixin( /** @beta @override */ public buildCanonicalReference(): DeclarationReference { const nameComponent: Component = DeclarationReference.parseComponent(this.name); + const navigation: Navigation = this.isExported ? Navigation.Exports : Navigation.Locals; return (this.parent ? this.parent.canonicalReference : DeclarationReference.empty()) - .addNavigationStep(Navigation.Exports, nameComponent) + .addNavigationStep(navigation, nameComponent) .withMeaning(Meaning.Interface); } } diff --git a/libraries/api-extractor-model/src/model/ApiNamespace.ts b/libraries/api-extractor-model/src/model/ApiNamespace.ts index b19569c22b5..49281089332 100644 --- a/libraries/api-extractor-model/src/model/ApiNamespace.ts +++ b/libraries/api-extractor-model/src/model/ApiNamespace.ts @@ -12,6 +12,7 @@ import { ApiItemContainerMixin, IApiItemContainerMixinOptions } from '../mixins/ import { IApiDeclaredItemOptions, ApiDeclaredItem } from '../items/ApiDeclaredItem'; import { ApiReleaseTagMixin, IApiReleaseTagMixinOptions } from '../mixins/ApiReleaseTagMixin'; import { IApiNameMixinOptions, ApiNameMixin } from '../mixins/ApiNameMixin'; +import { IApiExportedMixinOptions, ApiExportedMixin } from '../mixins/ApiExportedMixin'; /** * Constructor options for {@link ApiClass}. @@ -21,7 +22,8 @@ export interface IApiNamespaceOptions extends IApiItemContainerMixinOptions, IApiNameMixinOptions, IApiReleaseTagMixinOptions, - IApiDeclaredItemOptions {} + IApiDeclaredItemOptions, + IApiExportedMixinOptions {} /** * Represents a TypeScript namespace declaration. @@ -45,7 +47,9 @@ export interface IApiNamespaceOptions * * @public */ -export class ApiNamespace extends ApiItemContainerMixin(ApiNameMixin(ApiReleaseTagMixin(ApiDeclaredItem))) { +export class ApiNamespace extends ApiItemContainerMixin( + ApiNameMixin(ApiReleaseTagMixin(ApiExportedMixin(ApiDeclaredItem))) +) { public constructor(options: IApiNamespaceOptions) { super(options); } @@ -67,8 +71,9 @@ export class ApiNamespace extends ApiItemContainerMixin(ApiNameMixin(ApiReleaseT /** @beta @override */ public buildCanonicalReference(): DeclarationReference { const nameComponent: Component = DeclarationReference.parseComponent(this.name); + const navigation: Navigation = this.isExported ? Navigation.Exports : Navigation.Locals; return (this.parent ? this.parent.canonicalReference : DeclarationReference.empty()) - .addNavigationStep(Navigation.Exports, nameComponent) + .addNavigationStep(navigation, nameComponent) .withMeaning(Meaning.Namespace); } } diff --git a/libraries/api-extractor-model/src/model/ApiTypeAlias.ts b/libraries/api-extractor-model/src/model/ApiTypeAlias.ts index 759971b8cd3..71f018ef259 100644 --- a/libraries/api-extractor-model/src/model/ApiTypeAlias.ts +++ b/libraries/api-extractor-model/src/model/ApiTypeAlias.ts @@ -18,6 +18,11 @@ import { IApiTypeParameterListMixinJson } from '../mixins/ApiTypeParameterListMixin'; import { DeserializerContext } from './DeserializerContext'; +import { + IApiExportedMixinJson, + IApiExportedMixinOptions, + ApiExportedMixin +} from '../mixins/ApiExportedMixin'; /** * Constructor options for {@link ApiTypeAlias}. @@ -27,11 +32,15 @@ export interface IApiTypeAliasOptions extends IApiNameMixinOptions, IApiReleaseTagMixinOptions, IApiDeclaredItemOptions, - IApiTypeParameterListMixinOptions { + IApiTypeParameterListMixinOptions, + IApiExportedMixinOptions { typeTokenRange: IExcerptTokenRange; } -export interface IApiTypeAliasJson extends IApiDeclaredItemJson, IApiTypeParameterListMixinJson { +export interface IApiTypeAliasJson + extends IApiDeclaredItemJson, + IApiTypeParameterListMixinJson, + IApiExportedMixinJson { typeTokenRange: IExcerptTokenRange; } @@ -62,7 +71,7 @@ export interface IApiTypeAliasJson extends IApiDeclaredItemJson, IApiTypeParamet * @public */ export class ApiTypeAlias extends ApiTypeParameterListMixin( - ApiNameMixin(ApiReleaseTagMixin(ApiDeclaredItem)) + ApiNameMixin(ApiReleaseTagMixin(ApiExportedMixin(ApiDeclaredItem))) ) { /** * An {@link Excerpt} that describes the type of the alias. @@ -118,8 +127,9 @@ export class ApiTypeAlias extends ApiTypeParameterListMixin( /** @beta @override */ public buildCanonicalReference(): DeclarationReference { const nameComponent: Component = DeclarationReference.parseComponent(this.name); + const navigation: Navigation = this.isExported ? Navigation.Exports : Navigation.Locals; return (this.parent ? this.parent.canonicalReference : DeclarationReference.empty()) - .addNavigationStep(Navigation.Exports, nameComponent) + .addNavigationStep(navigation, nameComponent) .withMeaning(Meaning.TypeAlias); } } diff --git a/libraries/api-extractor-model/src/model/ApiVariable.ts b/libraries/api-extractor-model/src/model/ApiVariable.ts index d7e55624496..a3ac4b9614e 100644 --- a/libraries/api-extractor-model/src/model/ApiVariable.ts +++ b/libraries/api-extractor-model/src/model/ApiVariable.ts @@ -15,6 +15,11 @@ import { IApiNameMixinOptions, ApiNameMixin } from '../mixins/ApiNameMixin'; import { ApiInitializerMixin, IApiInitializerMixinOptions } from '../mixins/ApiInitializerMixin'; import { IExcerptTokenRange, Excerpt } from '../mixins/Excerpt'; import { DeserializerContext } from './DeserializerContext'; +import { + IApiExportedMixinJson, + IApiExportedMixinOptions, + ApiExportedMixin +} from '../mixins/ApiExportedMixin'; /** * Constructor options for {@link ApiVariable}. @@ -25,11 +30,12 @@ export interface IApiVariableOptions IApiReleaseTagMixinOptions, IApiReadonlyMixinOptions, IApiDeclaredItemOptions, - IApiInitializerMixinOptions { + IApiInitializerMixinOptions, + IApiExportedMixinOptions { variableTypeTokenRange: IExcerptTokenRange; } -export interface IApiVariableJson extends IApiDeclaredItemJson { +export interface IApiVariableJson extends IApiDeclaredItemJson, IApiExportedMixinJson { variableTypeTokenRange: IExcerptTokenRange; } @@ -54,7 +60,7 @@ export interface IApiVariableJson extends IApiDeclaredItemJson { * @public */ export class ApiVariable extends ApiNameMixin( - ApiReleaseTagMixin(ApiReadonlyMixin(ApiInitializerMixin(ApiDeclaredItem))) + ApiReleaseTagMixin(ApiReadonlyMixin(ApiInitializerMixin(ApiExportedMixin(ApiDeclaredItem)))) ) { /** * An {@link Excerpt} that describes the type of the variable. @@ -102,8 +108,9 @@ export class ApiVariable extends ApiNameMixin( /** @beta @override */ public buildCanonicalReference(): DeclarationReference { const nameComponent: Component = DeclarationReference.parseComponent(this.name); + const navigation: Navigation = this.isExported ? Navigation.Exports : Navigation.Locals; return (this.parent ? this.parent.canonicalReference : DeclarationReference.empty()) - .addNavigationStep(Navigation.Exports, nameComponent) + .addNavigationStep(navigation, nameComponent) .withMeaning(Meaning.Variable); } } From d0393b0560021475f7b4d2dedaedcc579a0b119e Mon Sep 17 00:00:00 2001 From: Zack Elliott Date: Tue, 26 Jul 2022 09:13:02 -0700 Subject: [PATCH 2/8] Ran rush change --- .../api-extractor-model/exported_2022-07-26-16-12.json | 10 ++++++++++ .../api-extractor/exported_2022-07-26-16-12.json | 10 ++++++++++ 2 files changed, 20 insertions(+) create mode 100644 common/changes/@microsoft/api-extractor-model/exported_2022-07-26-16-12.json create mode 100644 common/changes/@microsoft/api-extractor/exported_2022-07-26-16-12.json diff --git a/common/changes/@microsoft/api-extractor-model/exported_2022-07-26-16-12.json b/common/changes/@microsoft/api-extractor-model/exported_2022-07-26-16-12.json new file mode 100644 index 00000000000..9d4cc4a1201 --- /dev/null +++ b/common/changes/@microsoft/api-extractor-model/exported_2022-07-26-16-12.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@microsoft/api-extractor-model", + "comment": "Add new ApiExportedMixin mixin class for determining whether an API item is exported or not", + "type": "minor" + } + ], + "packageName": "@microsoft/api-extractor-model" +} \ No newline at end of file diff --git a/common/changes/@microsoft/api-extractor/exported_2022-07-26-16-12.json b/common/changes/@microsoft/api-extractor/exported_2022-07-26-16-12.json new file mode 100644 index 00000000000..2ff6210fef3 --- /dev/null +++ b/common/changes/@microsoft/api-extractor/exported_2022-07-26-16-12.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@microsoft/api-extractor", + "comment": "Add new \"apiReport.includeForgottenExports\" and \"docModel.includeForgottenExports\" properties to control whether forgotten exports are included in the API report and doc model files.", + "type": "minor" + } + ], + "packageName": "@microsoft/api-extractor" +} \ No newline at end of file From f9a98d4fd82505be40be1c3493c7d48a12d96e38 Mon Sep 17 00:00:00 2001 From: Zack Elliott Date: Tue, 26 Jul 2022 10:30:24 -0700 Subject: [PATCH 3/8] Small bug fix --- apps/api-extractor/src/enhancers/ValidationEnhancer.ts | 7 +++---- .../etc/ambientNameConflict/api-extractor-scenarios.api.md | 2 +- .../includeForgottenExports/api-extractor-scenarios.api.md | 2 +- .../api-extractor-test-01/etc/api-extractor-test-01.api.md | 3 +-- 4 files changed, 6 insertions(+), 8 deletions(-) diff --git a/apps/api-extractor/src/enhancers/ValidationEnhancer.ts b/apps/api-extractor/src/enhancers/ValidationEnhancer.ts index 67cec6ded9d..c1777d9c383 100644 --- a/apps/api-extractor/src/enhancers/ValidationEnhancer.ts +++ b/apps/api-extractor/src/enhancers/ValidationEnhancer.ts @@ -210,9 +210,8 @@ export class ValidationEnhancer { continue; } - localName = rootSymbol.localName; - collectorEntity = collector.tryGetCollectorEntity(rootSymbol); + localName = collectorEntity?.nameForEmit || rootSymbol.localName; const referencedMetadata: SymbolMetadata = collector.fetchSymbolMetadata(referencedEntity); referencedReleaseTag = referencedMetadata.maxEffectiveReleaseTag; @@ -222,7 +221,7 @@ export class ValidationEnhancer { // TODO: Currently the "import * as ___ from ___" syntax does not yet support doc comments referencedReleaseTag = ReleaseTag.Public; - localName = referencedEntity.localName; + localName = collectorEntity?.nameForEmit || referencedEntity.localName; } else { continue; } @@ -233,7 +232,7 @@ export class ValidationEnhancer { ExtractorMessageId.IncompatibleReleaseTags, `The symbol "${astDeclaration.astSymbol.localName}"` + ` is marked as ${ReleaseTag.getTagName(declarationReleaseTag)},` + - ` but its signature references "${referencedEntity.localName}"` + + ` but its signature references "${localName}"` + ` which is marked as ${ReleaseTag.getTagName(referencedReleaseTag)}`, astDeclaration ); diff --git a/build-tests/api-extractor-scenarios/etc/ambientNameConflict/api-extractor-scenarios.api.md b/build-tests/api-extractor-scenarios/etc/ambientNameConflict/api-extractor-scenarios.api.md index 5c7a3d96b52..570e8a2cc45 100644 --- a/build-tests/api-extractor-scenarios/etc/ambientNameConflict/api-extractor-scenarios.api.md +++ b/build-tests/api-extractor-scenarios/etc/ambientNameConflict/api-extractor-scenarios.api.md @@ -4,7 +4,7 @@ ```ts -// Warning: (ae-forgotten-export) The symbol "Promise" needs to be exported by the entry point index.d.ts +// Warning: (ae-forgotten-export) The symbol "Promise_2" needs to be exported by the entry point index.d.ts // // @public (undocumented) export function ambientNameConflict(p1: Promise, p2: Promise_2): void; diff --git a/build-tests/api-extractor-scenarios/etc/includeForgottenExports/api-extractor-scenarios.api.md b/build-tests/api-extractor-scenarios/etc/includeForgottenExports/api-extractor-scenarios.api.md index 73b2f95f9ee..ca544a91588 100644 --- a/build-tests/api-extractor-scenarios/etc/includeForgottenExports/api-extractor-scenarios.api.md +++ b/build-tests/api-extractor-scenarios/etc/includeForgottenExports/api-extractor-scenarios.api.md @@ -9,7 +9,7 @@ // (undocumented) type AnotherForgottenExport = number; -// Warning: (ae-forgotten-export) The symbol "DuplicateName" needs to be exported by the entry point index.d.ts +// Warning: (ae-forgotten-export) The symbol "DuplicateName_2" needs to be exported by the entry point index.d.ts // // @public (undocumented) export function anotherFunction(): DuplicateName_2; diff --git a/build-tests/api-extractor-test-01/etc/api-extractor-test-01.api.md b/build-tests/api-extractor-test-01/etc/api-extractor-test-01.api.md index 87e44be72c0..395686f0c86 100644 --- a/build-tests/api-extractor-test-01/etc/api-extractor-test-01.api.md +++ b/build-tests/api-extractor-test-01/etc/api-extractor-test-01.api.md @@ -110,7 +110,7 @@ export class ForgottenExportConsumer1 { // @public (undocumented) export class ForgottenExportConsumer2 { - // Warning: (ae-forgotten-export) The symbol "IForgottenExport" needs to be exported by the entry point index.d.ts + // Warning: (ae-forgotten-export) The symbol "IForgottenExport_2" needs to be exported by the entry point index.d.ts // // (undocumented) test2(): IForgottenExport_2 | undefined; @@ -203,5 +203,4 @@ export const VARIABLE: string; // @public export function virtual(target: Object, propertyKey: string | symbol, descriptor: TypedPropertyDescriptor): void; - ``` From d4e758da81e75d3a5aac1fb4d2ada5be4e46e3bf Mon Sep 17 00:00:00 2001 From: Zack Elliott Date: Mon, 1 Aug 2022 08:53:08 -0700 Subject: [PATCH 4/8] Update PR based upon reviewer feedback --- apps/api-extractor/src/collector/Collector.ts | 2 +- .../src/collector/CollectorEntity.ts | 49 ++- .../src/enhancers/DocCommentEnhancer.ts | 2 +- .../src/enhancers/ValidationEnhancer.ts | 4 +- .../src/generators/ApiModelGenerator.ts | 2 +- .../src/generators/ApiReportGenerator.ts | 2 +- .../api-extractor-scenarios.api.json | 315 ++++++++++++++---- .../api-extractor-scenarios.api.md | 68 +++- .../etc/includeForgottenExports/rollup.d.ts | 52 ++- .../src/includeForgottenExports/index.ts | 41 ++- .../src/includeForgottenExports/internal.ts | 6 - .../src/includeForgottenExports/internal1.ts | 10 + .../src/includeForgottenExports/internal2.ts | 1 + .../src/mixins/ApiExportedMixin.ts | 25 +- 14 files changed, 456 insertions(+), 123 deletions(-) delete mode 100644 build-tests/api-extractor-scenarios/src/includeForgottenExports/internal.ts create mode 100644 build-tests/api-extractor-scenarios/src/includeForgottenExports/internal1.ts create mode 100644 build-tests/api-extractor-scenarios/src/includeForgottenExports/internal2.ts diff --git a/apps/api-extractor/src/collector/Collector.ts b/apps/api-extractor/src/collector/Collector.ts index cbb13942241..40d05bf12c4 100644 --- a/apps/api-extractor/src/collector/Collector.ts +++ b/apps/api-extractor/src/collector/Collector.ts @@ -818,7 +818,7 @@ export class Collector { // Don't report missing release tags for forgotten exports const astSymbol: AstSymbol = astDeclaration.astSymbol; const entity: CollectorEntity | undefined = this._entitiesByAstEntity.get(astSymbol.rootAstSymbol); - if (entity && entity.exported) { + if (entity && entity.consumable) { // We also don't report errors for the default export of an entry point, since its doc comment // isn't easy to obtain from the .d.ts file if (astSymbol.rootAstSymbol.localName !== '_default') { diff --git a/apps/api-extractor/src/collector/CollectorEntity.ts b/apps/api-extractor/src/collector/CollectorEntity.ts index 7eeb37a14ba..9d62e2079ce 100644 --- a/apps/api-extractor/src/collector/CollectorEntity.ts +++ b/apps/api-extractor/src/collector/CollectorEntity.ts @@ -96,21 +96,54 @@ export class CollectorEntity { } /** - * Returns true if the entity is exported from the entry point. If the entity is not exported, + * Indicates that this entity is exported from its parent module (i.e. either the package entry point or + * a local namespace). + * + * @remarks + * In the example below: + * + * ```ts + * declare function add(): void; + * declare namespace calculator { + * export { + * add + * } + * } + * ``` + * + * Namespace `calculator` is neither exported nor consumable, function `add` is exported (from `calculator`) + * but not consumable. + */ + public get exported(): boolean { + const exportedFromTopLevel: boolean = this.exportNames.size > 0; + + let exportedFromParent: boolean = false; + for (const localExportNames of this._localExportNamesByParent.values()) { + if (localExportNames.size > 0) { + exportedFromParent = true; + break; + } + } + + return exportedFromTopLevel || exportedFromParent; + } + + /** + * Indicates that it is possible for a consumer of the API to access this entity, either by importing + * it directly, or via some other alias such as a member of a namespace. If an entity is not consumable, * then API Extractor will report an `ae-forgotten-export` warning. * * @remarks - * An API item is exported if: + * An API item is consumable if: * * 1. It is exported from the top-level entry point OR - * 2. It is exported from a parent entity that is also exported. + * 2. It is exported from a consumable parent entity. * - * #2 occurs when processing `AstNamespaceImport` entities. A generated rollup.d.ts + * For an example of #2, consider how `AstNamespaceImport` entities are processed. A generated rollup.d.ts * might look like this: * * ```ts * declare function add(): void; - * * declare namespace calculator { * export { * add @@ -119,14 +152,14 @@ export class CollectorEntity { * export { calculator } * ``` * - * In this example, `add` is exported via the exported `calculator` namespace. + * In this example, `add` is exported via the consumable `calculator` namespace. */ - public get exported(): boolean { + public get consumable(): boolean { const exportedFromTopLevel: boolean = this.exportNames.size > 0; let exportedFromExportedParent: boolean = false; for (const [parent, localExportNames] of this._localExportNamesByParent) { - if (localExportNames.size > 0 && parent.exported) { + if (localExportNames.size > 0 && parent.consumable) { exportedFromExportedParent = true; break; } diff --git a/apps/api-extractor/src/enhancers/DocCommentEnhancer.ts b/apps/api-extractor/src/enhancers/DocCommentEnhancer.ts index fcdb17b2bdf..dc99d2cdc80 100644 --- a/apps/api-extractor/src/enhancers/DocCommentEnhancer.ts +++ b/apps/api-extractor/src/enhancers/DocCommentEnhancer.ts @@ -28,7 +28,7 @@ export class DocCommentEnhancer { public analyze(): void { for (const entity of this._collector.entities) { if (entity.astEntity instanceof AstSymbol) { - if (entity.exported || this._collector.extractorConfig.apiReportIncludeForgottenExports) { + if (entity.consumable || this._collector.extractorConfig.apiReportIncludeForgottenExports) { entity.astEntity.forEachDeclarationRecursive((astDeclaration: AstDeclaration) => { this._analyzeApiItem(astDeclaration); }); diff --git a/apps/api-extractor/src/enhancers/ValidationEnhancer.ts b/apps/api-extractor/src/enhancers/ValidationEnhancer.ts index c1777d9c383..6898fa6884f 100644 --- a/apps/api-extractor/src/enhancers/ValidationEnhancer.ts +++ b/apps/api-extractor/src/enhancers/ValidationEnhancer.ts @@ -21,7 +21,7 @@ export class ValidationEnhancer { const alreadyWarnedEntities: Set = new Set(); for (const entity of collector.entities) { - if (!(entity.exported || collector.extractorConfig.apiReportIncludeForgottenExports)) { + if (!(entity.consumable || collector.extractorConfig.apiReportIncludeForgottenExports)) { continue; } @@ -226,7 +226,7 @@ export class ValidationEnhancer { continue; } - if (collectorEntity && collectorEntity.exported) { + if (collectorEntity && collectorEntity.consumable) { if (ReleaseTag.compare(declarationReleaseTag, referencedReleaseTag) > 0) { collector.messageRouter.addAnalyzerIssue( ExtractorMessageId.IncompatibleReleaseTags, diff --git a/apps/api-extractor/src/generators/ApiModelGenerator.ts b/apps/api-extractor/src/generators/ApiModelGenerator.ts index b7245422ecd..baabc4b32ff 100644 --- a/apps/api-extractor/src/generators/ApiModelGenerator.ts +++ b/apps/api-extractor/src/generators/ApiModelGenerator.ts @@ -87,7 +87,7 @@ export class ApiModelGenerator { // processed by the parent. if ( !entity.hasParents && - (entity.exported || this._collector.extractorConfig.docModelIncludeForgottenExports) + (entity.consumable || this._collector.extractorConfig.docModelIncludeForgottenExports) ) { this._processAstEntity(entity.astEntity, entity.nameForEmit, apiEntryPoint); } diff --git a/apps/api-extractor/src/generators/ApiReportGenerator.ts b/apps/api-extractor/src/generators/ApiReportGenerator.ts index e2d33c82f40..74dfe7cdf2b 100644 --- a/apps/api-extractor/src/generators/ApiReportGenerator.ts +++ b/apps/api-extractor/src/generators/ApiReportGenerator.ts @@ -78,7 +78,7 @@ export class ApiReportGenerator { // Emit the regular declarations for (const entity of collector.entities) { const astEntity: AstEntity = entity.astEntity; - if (entity.exported || collector.extractorConfig.apiReportIncludeForgottenExports) { + if (entity.consumable || collector.extractorConfig.apiReportIncludeForgottenExports) { // First, collect the list of export names for this symbol. When reporting messages with // ExtractorMessage.properties.exportName, this will enable us to emit the warning comments alongside // the associated export statement. 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 db315044ac3..1fd2038b563 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 @@ -172,63 +172,10 @@ "name": "", "preserveMemberOrder": false, "members": [ - { - "kind": "TypeAlias", - "canonicalReference": "api-extractor-scenarios!~AnotherForgottenExport:type", - "docComment": "/**\n * {@inheritDoc ForgottenExport}\n */\n", - "excerptTokens": [ - { - "kind": "Content", - "text": "declare type AnotherForgottenExport = " - }, - { - "kind": "Content", - "text": "number" - }, - { - "kind": "Content", - "text": ";" - } - ], - "releaseTag": "None", - "name": "AnotherForgottenExport", - "typeTokenRange": { - "startIndex": 1, - "endIndex": 2 - } - }, - { - "kind": "Function", - "canonicalReference": "api-extractor-scenarios!anotherFunction:function(1)", - "docComment": "/**\n * @public\n */\n", - "excerptTokens": [ - { - "kind": "Content", - "text": "export declare function anotherFunction(): " - }, - { - "kind": "Reference", - "text": "DuplicateName", - "canonicalReference": "api-extractor-scenarios!~DuplicateName:type" - }, - { - "kind": "Content", - "text": ";" - } - ], - "returnTypeTokenRange": { - "startIndex": 1, - "endIndex": 2 - }, - "releaseTag": "Public", - "overloadIndex": 1, - "parameters": [], - "name": "anotherFunction" - }, { "kind": "TypeAlias", "canonicalReference": "api-extractor-scenarios!~DuplicateName_2:type", - "docComment": "", + "docComment": "/**\n * Will be renamed to avoid a name conflict with the exported `DuplicateName` from index.ts.\n */\n", "excerptTokens": [ { "kind": "Content", @@ -277,22 +224,22 @@ }, { "kind": "Class", - "canonicalReference": "api-extractor-scenarios!~ForgottenExport:class", - "docComment": "/**\n * This doc comment should be inherited by `AnotherForgottenExport`\n */\n", + "canonicalReference": "api-extractor-scenarios!~ForgottenExport1:class", + "docComment": "/**\n * This doc comment should be inherited by `ForgottenExport2`\n */\n", "excerptTokens": [ { "kind": "Content", - "text": "declare class ForgottenExport " + "text": "declare class ForgottenExport1 " } ], "releaseTag": "None", - "name": "ForgottenExport", + "name": "ForgottenExport1", "preserveMemberOrder": false, "members": [ { "kind": "Constructor", - "canonicalReference": "api-extractor-scenarios!~ForgottenExport:constructor(1)", - "docComment": "/**\n * Constructs a new instance of the `ForgottenExport` class\n */\n", + "canonicalReference": "api-extractor-scenarios!~ForgottenExport1:constructor(1)", + "docComment": "/**\n * Constructs a new instance of the `ForgottenExport1` class\n */\n", "excerptTokens": [ { "kind": "Content", @@ -306,7 +253,7 @@ }, { "kind": "Property", - "canonicalReference": "api-extractor-scenarios!~ForgottenExport#prop:member", + "canonicalReference": "api-extractor-scenarios!~ForgottenExport1#prop:member", "docComment": "", "excerptTokens": [ { @@ -315,8 +262,8 @@ }, { "kind": "Reference", - "text": "AnotherForgottenExport", - "canonicalReference": "api-extractor-scenarios!~AnotherForgottenExport:type" + "text": "ForgottenExport2", + "canonicalReference": "api-extractor-scenarios!~ForgottenExport2:type" }, { "kind": "Content", @@ -337,19 +284,159 @@ ], "implementsTokenRanges": [] }, + { + "kind": "TypeAlias", + "canonicalReference": "api-extractor-scenarios!~ForgottenExport2:type", + "docComment": "/**\n * {@inheritDoc ForgottenExport1}\n */\n", + "excerptTokens": [ + { + "kind": "Content", + "text": "declare type ForgottenExport2 = " + }, + { + "kind": "Content", + "text": "number" + }, + { + "kind": "Content", + "text": ";" + } + ], + "releaseTag": "None", + "name": "ForgottenExport2", + "typeTokenRange": { + "startIndex": 1, + "endIndex": 2 + } + }, + { + "kind": "Namespace", + "canonicalReference": "api-extractor-scenarios!~ForgottenExport4:namespace", + "docComment": "", + "excerptTokens": [ + { + "kind": "Content", + "text": "declare namespace ForgottenExport4 " + } + ], + "releaseTag": "None", + "name": "ForgottenExport4", + "preserveMemberOrder": false, + "members": [ + { + "kind": "Class", + "canonicalReference": "api-extractor-scenarios!~ForgottenExport4~ForgottenExport5:class", + "docComment": "", + "excerptTokens": [ + { + "kind": "Content", + "text": "class ForgottenExport5 " + } + ], + "releaseTag": "None", + "name": "ForgottenExport5", + "preserveMemberOrder": false, + "members": [], + "implementsTokenRanges": [] + } + ] + }, + { + "kind": "Namespace", + "canonicalReference": "api-extractor-scenarios!internal2:namespace", + "docComment": "", + "excerptTokens": [], + "releaseTag": "None", + "name": "internal2", + "preserveMemberOrder": false, + "members": [ + { + "kind": "Class", + "canonicalReference": "api-extractor-scenarios!internal2.ForgottenExport6:class", + "docComment": "", + "excerptTokens": [ + { + "kind": "Content", + "text": "export declare class ForgottenExport6 " + } + ], + "releaseTag": "None", + "name": "ForgottenExport6", + "preserveMemberOrder": false, + "members": [], + "implementsTokenRanges": [] + } + ] + }, + { + "kind": "Function", + "canonicalReference": "api-extractor-scenarios!someFunction1:function(1)", + "docComment": "/**\n * @public\n */\n", + "excerptTokens": [ + { + "kind": "Content", + "text": "export declare function someFunction1(): " + }, + { + "kind": "Reference", + "text": "ForgottenExport1", + "canonicalReference": "api-extractor-scenarios!~ForgottenExport1:class" + }, + { + "kind": "Content", + "text": ";" + } + ], + "returnTypeTokenRange": { + "startIndex": 1, + "endIndex": 2 + }, + "releaseTag": "Public", + "overloadIndex": 1, + "parameters": [], + "name": "someFunction1" + }, + { + "kind": "Function", + "canonicalReference": "api-extractor-scenarios!someFunction2:function(1)", + "docComment": "/**\n * @public\n */\n", + "excerptTokens": [ + { + "kind": "Content", + "text": "export declare function someFunction2(): " + }, + { + "kind": "Reference", + "text": "DuplicateName", + "canonicalReference": "api-extractor-scenarios!~DuplicateName:type" + }, + { + "kind": "Content", + "text": ";" + } + ], + "returnTypeTokenRange": { + "startIndex": 1, + "endIndex": 2 + }, + "releaseTag": "Public", + "overloadIndex": 1, + "parameters": [], + "name": "someFunction2" + }, { "kind": "Function", - "canonicalReference": "api-extractor-scenarios!someFunction:function(1)", + "canonicalReference": "api-extractor-scenarios!someFunction4:function(1)", "docComment": "/**\n * @public\n */\n", "excerptTokens": [ { "kind": "Content", - "text": "export declare function someFunction(): " + "text": "export declare function someFunction4(): " }, { "kind": "Reference", - "text": "ForgottenExport", - "canonicalReference": "api-extractor-scenarios!~ForgottenExport:class" + "text": "ForgottenExport4.ForgottenExport5", + "canonicalReference": "api-extractor-scenarios!~ForgottenExport4.ForgottenExport5:class" }, { "kind": "Content", @@ -363,7 +450,99 @@ "releaseTag": "Public", "overloadIndex": 1, "parameters": [], - "name": "someFunction" + "name": "someFunction4" + }, + { + "kind": "Function", + "canonicalReference": "api-extractor-scenarios!someFunction5:function(1)", + "docComment": "/**\n * @public\n */\n", + "excerptTokens": [ + { + "kind": "Content", + "text": "export declare function someFunction5(): " + }, + { + "kind": "Content", + "text": "internal2." + }, + { + "kind": "Reference", + "text": "ForgottenExport6", + "canonicalReference": "api-extractor-scenarios!ForgottenExport6:class" + }, + { + "kind": "Content", + "text": ";" + } + ], + "returnTypeTokenRange": { + "startIndex": 1, + "endIndex": 3 + }, + "releaseTag": "Public", + "overloadIndex": 1, + "parameters": [], + "name": "someFunction5" + }, + { + "kind": "Namespace", + "canonicalReference": "api-extractor-scenarios!SomeNamespace1:namespace", + "docComment": "/**\n * @public\n */\n", + "excerptTokens": [ + { + "kind": "Content", + "text": "export declare namespace SomeNamespace1 " + } + ], + "releaseTag": "Public", + "name": "SomeNamespace1", + "preserveMemberOrder": false, + "members": [ + { + "kind": "Class", + "canonicalReference": "api-extractor-scenarios!SomeNamespace1.ForgottenExport3:class", + "docComment": "", + "excerptTokens": [ + { + "kind": "Content", + "text": "class ForgottenExport3 " + } + ], + "releaseTag": "Public", + "name": "ForgottenExport3", + "preserveMemberOrder": false, + "members": [], + "implementsTokenRanges": [] + }, + { + "kind": "Function", + "canonicalReference": "api-extractor-scenarios!SomeNamespace1.someFunction3:function(1)", + "docComment": "", + "excerptTokens": [ + { + "kind": "Content", + "text": "export function someFunction3(): " + }, + { + "kind": "Reference", + "text": "ForgottenExport3", + "canonicalReference": "api-extractor-scenarios!~ForgottenExport3:class" + }, + { + "kind": "Content", + "text": ";" + } + ], + "returnTypeTokenRange": { + "startIndex": 1, + "endIndex": 2 + }, + "releaseTag": "Public", + "overloadIndex": 1, + "parameters": [], + "name": "someFunction3" + } + ] } ] } diff --git a/build-tests/api-extractor-scenarios/etc/includeForgottenExports/api-extractor-scenarios.api.md b/build-tests/api-extractor-scenarios/etc/includeForgottenExports/api-extractor-scenarios.api.md index ca544a91588..0b72f7f87ee 100644 --- a/build-tests/api-extractor-scenarios/etc/includeForgottenExports/api-extractor-scenarios.api.md +++ b/build-tests/api-extractor-scenarios/etc/includeForgottenExports/api-extractor-scenarios.api.md @@ -4,34 +4,70 @@ ```ts -// Warning: (ae-unresolved-inheritdoc-reference) The @inheritDoc reference could not be resolved: The package "api-extractor-scenarios" does not have an export "ForgottenExport" -// -// (undocumented) -type AnotherForgottenExport = number; - -// Warning: (ae-forgotten-export) The symbol "DuplicateName_2" needs to be exported by the entry point index.d.ts -// -// @public (undocumented) -export function anotherFunction(): DuplicateName_2; - // @public export type DuplicateName = boolean; -// (undocumented) type DuplicateName_2 = number; -class ForgottenExport { +class ForgottenExport1 { constructor(); - // Warning: (ae-forgotten-export) The symbol "AnotherForgottenExport" needs to be exported by the entry point index.d.ts + // Warning: (ae-forgotten-export) The symbol "ForgottenExport2" needs to be exported by the entry point index.d.ts // // (undocumented) - prop?: AnotherForgottenExport; + prop?: ForgottenExport2; } -// Warning: (ae-forgotten-export) The symbol "ForgottenExport" needs to be exported by the entry point index.d.ts +// Warning: (ae-unresolved-inheritdoc-reference) The @inheritDoc reference could not be resolved: The package "api-extractor-scenarios" does not have an export "ForgottenExport1" +// +// (undocumented) +type ForgottenExport2 = number; + +// (undocumented) +namespace ForgottenExport4 { + // (undocumented) + class ForgottenExport5 { + } +} + +// (undocumented) +class ForgottenExport6 { +} + +declare namespace internal2 { + export { + ForgottenExport6 + } +} + +// Warning: (ae-forgotten-export) The symbol "ForgottenExport1" needs to be exported by the entry point index.d.ts +// +// @public (undocumented) +export function someFunction1(): ForgottenExport1; + +// Warning: (ae-forgotten-export) The symbol "DuplicateName_2" needs to be exported by the entry point index.d.ts +// +// @public (undocumented) +export function someFunction2(): DuplicateName_2; + +// Warning: (ae-forgotten-export) The symbol "ForgottenExport4" needs to be exported by the entry point index.d.ts // // @public (undocumented) -export function someFunction(): ForgottenExport; +export function someFunction4(): ForgottenExport4.ForgottenExport5; + +// Warning: (ae-forgotten-export) The symbol "internal2" needs to be exported by the entry point index.d.ts +// +// @public (undocumented) +export function someFunction5(): internal2.ForgottenExport6; + +// @public (undocumented) +export namespace SomeNamespace1 { + // (undocumented) + export class ForgottenExport3 { + } + // (undocumented) + export function someFunction3(): ForgottenExport3; + {}; +} // (No @packageDocumentation comment for this package) diff --git a/build-tests/api-extractor-scenarios/etc/includeForgottenExports/rollup.d.ts b/build-tests/api-extractor-scenarios/etc/includeForgottenExports/rollup.d.ts index 1c8e8798337..1813908c601 100644 --- a/build-tests/api-extractor-scenarios/etc/includeForgottenExports/rollup.d.ts +++ b/build-tests/api-extractor-scenarios/etc/includeForgottenExports/rollup.d.ts @@ -1,9 +1,3 @@ -/** {@inheritDoc ForgottenExport} */ -declare type AnotherForgottenExport = number; - -/** @public */ -export declare function anotherFunction(): DuplicateName_2; - /** * This type is exported but has the same name as an unexported type in './internal.ts'. This * unexported type is also included in the API report and doc model files. The unexported type @@ -12,15 +6,53 @@ export declare function anotherFunction(): DuplicateName_2; */ export declare type DuplicateName = boolean; +/** + * Will be renamed to avoid a name conflict with the exported `DuplicateName` from + * index.ts. + */ declare type DuplicateName_2 = number; -/** This doc comment should be inherited by `AnotherForgottenExport` */ -declare class ForgottenExport { - prop?: AnotherForgottenExport; +/** This doc comment should be inherited by `ForgottenExport2` */ +declare class ForgottenExport1 { + prop?: ForgottenExport2; constructor(); } +/** {@inheritDoc ForgottenExport1} */ +declare type ForgottenExport2 = number; + +declare namespace ForgottenExport4 { + class ForgottenExport5 { + } +} + +declare class ForgottenExport6 { +} + +declare namespace internal2 { + export { + ForgottenExport6 + } +} + +/** @public */ +export declare function someFunction1(): ForgottenExport1; + +/** @public */ +export declare function someFunction2(): DuplicateName_2; + /** @public */ -export declare function someFunction(): ForgottenExport; +export declare function someFunction4(): ForgottenExport4.ForgottenExport5; + +/** @public */ +export declare function someFunction5(): internal2.ForgottenExport6; + +/** @public */ +export declare namespace SomeNamespace1 { + export class ForgottenExport3 { + } + export function someFunction3(): ForgottenExport3; + {}; +} export { } diff --git a/build-tests/api-extractor-scenarios/src/includeForgottenExports/index.ts b/build-tests/api-extractor-scenarios/src/includeForgottenExports/index.ts index ef0e6d3dcb5..bc73016b707 100644 --- a/build-tests/api-extractor-scenarios/src/includeForgottenExports/index.ts +++ b/build-tests/api-extractor-scenarios/src/includeForgottenExports/index.ts @@ -1,15 +1,17 @@ -/** This doc comment should be inherited by `AnotherForgottenExport` */ -class ForgottenExport { - prop?: AnotherForgottenExport; +import * as internal2 from './internal2'; + +/** This doc comment should be inherited by `ForgottenExport2` */ +class ForgottenExport1 { + prop?: ForgottenExport2; constructor() {} } -/** {@inheritDoc ForgottenExport} */ -type AnotherForgottenExport = number; +/** {@inheritDoc ForgottenExport1} */ +type ForgottenExport2 = number; /** @public */ -export function someFunction(): ForgottenExport { - return new ForgottenExport(); +export function someFunction1(): ForgottenExport1 { + return new ForgottenExport1(); } /** @@ -20,4 +22,27 @@ export function someFunction(): ForgottenExport { */ export type DuplicateName = boolean; -export { anotherFunction } from './internal'; +export { someFunction2 } from './internal1'; + +/** @public */ +export namespace SomeNamespace1 { + class ForgottenExport3 {} + + export function someFunction3(): ForgottenExport3 { + return new ForgottenExport3(); + } +} + +namespace ForgottenExport4 { + export class ForgottenExport5 {} +} + +/** @public */ +export function someFunction4(): ForgottenExport4.ForgottenExport5 { + return new ForgottenExport4.ForgottenExport5(); +} + +/** @public */ +export function someFunction5(): internal2.ForgottenExport6 { + return new internal2.ForgottenExport6(); +} diff --git a/build-tests/api-extractor-scenarios/src/includeForgottenExports/internal.ts b/build-tests/api-extractor-scenarios/src/includeForgottenExports/internal.ts deleted file mode 100644 index ef68abebf8c..00000000000 --- a/build-tests/api-extractor-scenarios/src/includeForgottenExports/internal.ts +++ /dev/null @@ -1,6 +0,0 @@ -type DuplicateName = number; - -/** @public */ -export function anotherFunction(): DuplicateName { - return 5; -} diff --git a/build-tests/api-extractor-scenarios/src/includeForgottenExports/internal1.ts b/build-tests/api-extractor-scenarios/src/includeForgottenExports/internal1.ts new file mode 100644 index 00000000000..80c55355f29 --- /dev/null +++ b/build-tests/api-extractor-scenarios/src/includeForgottenExports/internal1.ts @@ -0,0 +1,10 @@ +/** + * Will be renamed to avoid a name conflict with the exported `DuplicateName` from + * index.ts. + */ +type DuplicateName = number; + +/** @public */ +export function someFunction2(): DuplicateName { + return 5; +} diff --git a/build-tests/api-extractor-scenarios/src/includeForgottenExports/internal2.ts b/build-tests/api-extractor-scenarios/src/includeForgottenExports/internal2.ts new file mode 100644 index 00000000000..ef36f4ea517 --- /dev/null +++ b/build-tests/api-extractor-scenarios/src/includeForgottenExports/internal2.ts @@ -0,0 +1 @@ +export class ForgottenExport6 {} diff --git a/libraries/api-extractor-model/src/mixins/ApiExportedMixin.ts b/libraries/api-extractor-model/src/mixins/ApiExportedMixin.ts index 2c06ad93d58..de37c3bb80c 100644 --- a/libraries/api-extractor-model/src/mixins/ApiExportedMixin.ts +++ b/libraries/api-extractor-model/src/mixins/ApiExportedMixin.ts @@ -37,7 +37,30 @@ const _isExported: unique symbol = Symbol('ApiExportedMixin._isExported'); // eslint-disable-next-line @typescript-eslint/naming-convention export interface ApiExportedMixin extends ApiItem { /** - * Whether the declaration is exported. + * Whether the declaration is exported from its parent item container (i.e. either an `ApiEntryPoint` or an + * `ApiNamespace`). + * + * @remarks + * Suppose `index.ts` is your entry point: + * + * ```ts + * // index.ts + * + * export class A {} + * class B {} + * + * namespace n { + * export class C {} + * class D {} + * } + * + * // file.ts + * export class E {} + * ``` + * + * Classes `A` and `C` are both exported, while classes `B`, `D`, and `E` are not. `E` is exported from its + * local file, but not from its parent item container (i.e. the entry point). + * */ readonly isExported: boolean; From 84de9f312c828e6170d683f5c5744e11b0adb5eb Mon Sep 17 00:00:00 2001 From: Zack Elliott Date: Sun, 14 Aug 2022 14:58:03 -0700 Subject: [PATCH 5/8] Rebased off latest master changes --- .../api-extractor-scenarios.api.json | 2 +- .../workspace/common/pnpm-lock.yaml | 28 +++++++++---------- 2 files changed, 15 insertions(+), 15 deletions(-) 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 1fd2038b563..9a8c473046c 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 @@ -526,7 +526,7 @@ { "kind": "Reference", "text": "ForgottenExport3", - "canonicalReference": "api-extractor-scenarios!~ForgottenExport3:class" + "canonicalReference": "api-extractor-scenarios!SomeNamespace1~ForgottenExport3:class" }, { "kind": "Content", diff --git a/build-tests/install-test-workspace/workspace/common/pnpm-lock.yaml b/build-tests/install-test-workspace/workspace/common/pnpm-lock.yaml index 0e31dce8c0a..c4bda4390cb 100644 --- a/build-tests/install-test-workspace/workspace/common/pnpm-lock.yaml +++ b/build-tests/install-test-workspace/workspace/common/pnpm-lock.yaml @@ -4,28 +4,28 @@ importers: typescript-newest-test: specifiers: - '@rushstack/eslint-config': file:rushstack-eslint-config-2.6.2.tgz - '@rushstack/heft': file:rushstack-heft-0.46.6.tgz + '@rushstack/eslint-config': file:rushstack-eslint-config-3.0.0.tgz + '@rushstack/heft': file:rushstack-heft-0.47.2.tgz eslint: ~8.7.0 tslint: ~5.20.1 typescript: ~4.7.4 devDependencies: - '@rushstack/eslint-config': file:../temp/tarballs/rushstack-eslint-config-2.6.2.tgz_eslint@8.7.0+typescript@4.6.4 - '@rushstack/heft': file:../temp/tarballs/rushstack-heft-0.46.6.tgz + '@rushstack/eslint-config': file:../temp/tarballs/rushstack-eslint-config-3.0.0.tgz_eslint@8.7.0+typescript@4.7.4 + '@rushstack/heft': file:../temp/tarballs/rushstack-heft-0.47.2.tgz eslint: 8.7.0 tslint: 5.20.1_typescript@4.7.4 typescript: 4.7.4 typescript-v3-test: specifiers: - '@rushstack/eslint-config': file:rushstack-eslint-config-2.6.2.tgz - '@rushstack/heft': file:rushstack-heft-0.46.6.tgz + '@rushstack/eslint-config': file:rushstack-eslint-config-3.0.0.tgz + '@rushstack/heft': file:rushstack-heft-0.47.2.tgz eslint: ~8.7.0 tslint: ~5.20.1 typescript: ~4.7.4 devDependencies: - '@rushstack/eslint-config': file:../temp/tarballs/rushstack-eslint-config-2.6.2.tgz_eslint@8.7.0+typescript@4.6.4 - '@rushstack/heft': file:../temp/tarballs/rushstack-heft-0.46.6.tgz + '@rushstack/eslint-config': file:../temp/tarballs/rushstack-eslint-config-3.0.0.tgz_eslint@8.7.0+typescript@4.7.4 + '@rushstack/heft': file:../temp/tarballs/rushstack-heft-0.47.2.tgz eslint: 8.7.0 tslint: 5.20.1_typescript@4.7.4 typescript: 4.7.4 @@ -358,7 +358,7 @@ packages: dev: true /balanced-match/1.0.2: - resolution: {integrity: sha1-6D46fj8wCzTLnYf2FfoMvzV2kO4=} + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} dev: true /binary-extensions/2.2.0: @@ -367,7 +367,7 @@ packages: dev: true /brace-expansion/1.1.11: - resolution: {integrity: sha1-PH/L9SnYcibz0vUrlm/1Jx60Qd0=} + resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} dependencies: balanced-match: 1.0.2 concat-map: 0.0.1 @@ -460,7 +460,7 @@ packages: dev: true /concat-map/0.0.1: - resolution: {integrity: sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=} + resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} dev: true /cross-spawn/7.0.3: @@ -1803,10 +1803,10 @@ packages: - typescript dev: true - file:../temp/tarballs/rushstack-heft-0.46.6.tgz: - resolution: {tarball: file:../temp/tarballs/rushstack-heft-0.46.6.tgz} + file:../temp/tarballs/rushstack-heft-0.47.2.tgz: + resolution: {tarball: file:../temp/tarballs/rushstack-heft-0.47.2.tgz} name: '@rushstack/heft' - version: 0.46.6 + version: 0.47.2 engines: {node: '>=10.13.0'} hasBin: true dependencies: From 3b2129d70f1330dd0693c3ab6aa7de01e4dfe3b4 Mon Sep 17 00:00:00 2001 From: Zack Elliott Date: Mon, 15 Aug 2022 07:41:00 -0700 Subject: [PATCH 6/8] Responded to PR comments and prepared PR for review --- .../src/collector/CollectorEntity.ts | 62 +++++++++---- .../src/enhancers/DocCommentEnhancer.ts | 6 +- .../src/enhancers/ValidationEnhancer.ts | 8 +- .../src/generators/ApiModelGenerator.ts | 4 +- .../src/generators/ApiReportGenerator.ts | 11 ++- .../api-extractor-scenarios.api.json | 92 ++++++++++++++++++- .../api-extractor-scenarios.api.md | 17 ++++ .../etc/includeForgottenExports/rollup.d.ts | 25 ++++- .../src/includeForgottenExports/index.ts | 22 ++++- .../src/includeForgottenExports/internal1.ts | 7 ++ 10 files changed, 221 insertions(+), 33 deletions(-) diff --git a/apps/api-extractor/src/collector/CollectorEntity.ts b/apps/api-extractor/src/collector/CollectorEntity.ts index 9d62e2079ce..1ec6e772a5b 100644 --- a/apps/api-extractor/src/collector/CollectorEntity.ts +++ b/apps/api-extractor/src/collector/CollectorEntity.ts @@ -37,9 +37,9 @@ export class CollectorEntity { } /** - * The declaration name that will be emitted in a .d.ts rollup. For non-exported declarations, - * Collector._makeUniqueNames() may need to rename the declaration to avoid conflicts with other declarations - * in that module. + * The declaration name that will be emitted in the .d.ts rollup, .api.md, and .api.json files. Generated by + * `Collector._makeUniqueNames`. Be aware that the declaration may be renamed to avoid conflicts with (1) + * global names (e.g. `Promise`) and (2) if local, other local names across different files. */ public get nameForEmit(): string | undefined { return this._nameForEmit; @@ -97,7 +97,7 @@ export class CollectorEntity { /** * Indicates that this entity is exported from its parent module (i.e. either the package entry point or - * a local namespace). + * a local namespace). Compare to `CollectorEntity.consumable`. * * @remarks * In the example below: @@ -115,23 +115,23 @@ export class CollectorEntity { * but not consumable. */ public get exported(): boolean { - const exportedFromTopLevel: boolean = this.exportNames.size > 0; + // Exported from top-level? + if (this.exportNames.size > 0) return true; - let exportedFromParent: boolean = false; + // Exported from parent? for (const localExportNames of this._localExportNamesByParent.values()) { if (localExportNames.size > 0) { - exportedFromParent = true; - break; + return true; } } - return exportedFromTopLevel || exportedFromParent; + return false; } /** - * Indicates that it is possible for a consumer of the API to access this entity, either by importing - * it directly, or via some other alias such as a member of a namespace. If an entity is not consumable, - * then API Extractor will report an `ae-forgotten-export` warning. + * Indicates that it is possible for a consumer of the API to "consume" this entity, either by importing + * it directly or via a namespace. If an entity is not consumable, then API Extractor will report an + * `ae-forgotten-export` warning. Compare to `CollectorEntity.exported`. * * @remarks * An API item is consumable if: @@ -155,21 +155,35 @@ export class CollectorEntity { * In this example, `add` is exported via the consumable `calculator` namespace. */ public get consumable(): boolean { - const exportedFromTopLevel: boolean = this.exportNames.size > 0; + // Exported from top-level? + if (this.exportNames.size > 0) return true; - let exportedFromExportedParent: boolean = false; + // Exported from consumable parent? for (const [parent, localExportNames] of this._localExportNamesByParent) { if (localExportNames.size > 0 && parent.consumable) { - exportedFromExportedParent = true; - break; + return true; } } - return exportedFromTopLevel || exportedFromExportedParent; + return false; } /** * Whether the entity has any parent entities. + * + * @remarks + * In the example below: + * + * ```ts + * declare function add(): void; + * declare namespace calculator { + * export { + * add + * } + * } + * ``` + * + * The `CollectorEntity` for `calculator` is the parent of the `CollectorEntity` for `add`. */ public get hasParents(): boolean { return this._localExportNamesByParent.size > 0; @@ -193,6 +207,20 @@ export class CollectorEntity { /** * Adds a new local export name to the entity. + * + * @remarks + * In the example below: + * + * ```ts + * declare function add(): void; + * declare namespace calculator { + * export { + * add + * } + * } + * ``` + * + * `add` is the local export name for the `CollectorEntity` for `add`. */ public addLocalExportName(localExportName: string, parent: CollectorEntity): void { const localExportNames: Set = this._localExportNamesByParent.get(parent) || new Set(); diff --git a/apps/api-extractor/src/enhancers/DocCommentEnhancer.ts b/apps/api-extractor/src/enhancers/DocCommentEnhancer.ts index dc99d2cdc80..ba96c9db803 100644 --- a/apps/api-extractor/src/enhancers/DocCommentEnhancer.ts +++ b/apps/api-extractor/src/enhancers/DocCommentEnhancer.ts @@ -28,7 +28,11 @@ export class DocCommentEnhancer { public analyze(): void { for (const entity of this._collector.entities) { if (entity.astEntity instanceof AstSymbol) { - if (entity.consumable || this._collector.extractorConfig.apiReportIncludeForgottenExports) { + if ( + entity.consumable || + this._collector.extractorConfig.apiReportIncludeForgottenExports || + this._collector.extractorConfig.docModelIncludeForgottenExports + ) { entity.astEntity.forEachDeclarationRecursive((astDeclaration: AstDeclaration) => { this._analyzeApiItem(astDeclaration); }); diff --git a/apps/api-extractor/src/enhancers/ValidationEnhancer.ts b/apps/api-extractor/src/enhancers/ValidationEnhancer.ts index 6898fa6884f..8795a4ec1ed 100644 --- a/apps/api-extractor/src/enhancers/ValidationEnhancer.ts +++ b/apps/api-extractor/src/enhancers/ValidationEnhancer.ts @@ -21,7 +21,13 @@ export class ValidationEnhancer { const alreadyWarnedEntities: Set = new Set(); for (const entity of collector.entities) { - if (!(entity.consumable || collector.extractorConfig.apiReportIncludeForgottenExports)) { + if ( + !( + entity.consumable || + collector.extractorConfig.apiReportIncludeForgottenExports || + collector.extractorConfig.docModelIncludeForgottenExports + ) + ) { continue; } diff --git a/apps/api-extractor/src/generators/ApiModelGenerator.ts b/apps/api-extractor/src/generators/ApiModelGenerator.ts index baabc4b32ff..80e5011c741 100644 --- a/apps/api-extractor/src/generators/ApiModelGenerator.ts +++ b/apps/api-extractor/src/generators/ApiModelGenerator.ts @@ -1144,7 +1144,9 @@ export class ApiModelGenerator { if (!entity) { // This should never happen. - throw new InternalError('Failed to get collector entity for root symbol of declaration'); + throw new InternalError( + `Failed to get collector entity for root symbol of declaration ${astDeclaration.astSymbol.localName}` + ); } return entity.exported; diff --git a/apps/api-extractor/src/generators/ApiReportGenerator.ts b/apps/api-extractor/src/generators/ApiReportGenerator.ts index 74dfe7cdf2b..6c99bdee3db 100644 --- a/apps/api-extractor/src/generators/ApiReportGenerator.ts +++ b/apps/api-extractor/src/generators/ApiReportGenerator.ts @@ -172,20 +172,21 @@ export class ApiReportGenerator { writer.increaseIndent(); const exportClauses: string[] = []; - for (const [exportName, astEntity] of astModuleExportInfo.exportedLocalEntities) { - const collectorEntity: CollectorEntity | undefined = collector.tryGetCollectorEntity(astEntity); + for (const [exportedName, exportedEntity] of astModuleExportInfo.exportedLocalEntities) { + const collectorEntity: CollectorEntity | undefined = + collector.tryGetCollectorEntity(exportedEntity); if (collectorEntity === undefined) { // This should never happen // top-level exports of local imported module should be added as collector entities before throw new InternalError( - `Cannot find collector entity for ${entity.nameForEmit}.${astEntity.localName}` + `Cannot find collector entity for ${entity.nameForEmit}.${exportedEntity.localName}` ); } - if (collectorEntity.nameForEmit === exportName) { + if (collectorEntity.nameForEmit === exportedName) { exportClauses.push(collectorEntity.nameForEmit); } else { - exportClauses.push(`${collectorEntity.nameForEmit} as ${exportName}`); + exportClauses.push(`${collectorEntity.nameForEmit} as ${exportedName}`); } } writer.writeLine(exportClauses.join(',\n')); 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 9a8c473046c..e1a766f8a38 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 @@ -172,6 +172,38 @@ "name": "", "preserveMemberOrder": false, "members": [ + { + "kind": "Class", + "canonicalReference": "api-extractor-scenarios!~AnotherDuplicateName_2:class", + "docComment": "", + "excerptTokens": [ + { + "kind": "Content", + "text": "declare class AnotherDuplicateName " + } + ], + "releaseTag": "None", + "name": "AnotherDuplicateName_2", + "preserveMemberOrder": false, + "members": [], + "implementsTokenRanges": [] + }, + { + "kind": "Class", + "canonicalReference": "api-extractor-scenarios!~AnotherDuplicateName:class", + "docComment": "/**\n * This forgotten item has the same name as another forgotten item in another file. They should be given unique names.\n */\n", + "excerptTokens": [ + { + "kind": "Content", + "text": "declare class AnotherDuplicateName " + } + ], + "releaseTag": "None", + "name": "AnotherDuplicateName", + "preserveMemberOrder": false, + "members": [], + "implementsTokenRanges": [] + }, { "kind": "TypeAlias", "canonicalReference": "api-extractor-scenarios!~DuplicateName_2:type", @@ -200,7 +232,7 @@ { "kind": "TypeAlias", "canonicalReference": "api-extractor-scenarios!DuplicateName:type", - "docComment": "/**\n * This type is exported but has the same name as an unexported type in './internal.ts'. This unexported type is also included in the API report and doc model files. The unexported type will be renamed to avoid a name conflict.\n *\n * @public\n */\n", + "docComment": "/**\n * This type is exported but has the same name as a forgotten type in './internal.ts'. This forgotten type is also included in the API report and doc model files. The forgotten type will be renamed to avoid a name conflict.\n *\n * @public\n */\n", "excerptTokens": [ { "kind": "Content", @@ -225,7 +257,7 @@ { "kind": "Class", "canonicalReference": "api-extractor-scenarios!~ForgottenExport1:class", - "docComment": "/**\n * This doc comment should be inherited by `ForgottenExport2`\n */\n", + "docComment": "/**\n * `ForgottenExport2` wants to inherit this doc comment, but unfortunately this isn't supported yet\n */\n", "excerptTokens": [ { "kind": "Content", @@ -484,6 +516,62 @@ "parameters": [], "name": "someFunction5" }, + { + "kind": "Function", + "canonicalReference": "api-extractor-scenarios!someFunction6:function(1)", + "docComment": "/**\n * @public\n */\n", + "excerptTokens": [ + { + "kind": "Content", + "text": "export declare function someFunction6(): " + }, + { + "kind": "Reference", + "text": "AnotherDuplicateName", + "canonicalReference": "api-extractor-scenarios!~AnotherDuplicateName:class" + }, + { + "kind": "Content", + "text": ";" + } + ], + "returnTypeTokenRange": { + "startIndex": 1, + "endIndex": 2 + }, + "releaseTag": "Public", + "overloadIndex": 1, + "parameters": [], + "name": "someFunction6" + }, + { + "kind": "Function", + "canonicalReference": "api-extractor-scenarios!someFunction7:function(1)", + "docComment": "/**\n * @public\n */\n", + "excerptTokens": [ + { + "kind": "Content", + "text": "export declare function someFunction7(): " + }, + { + "kind": "Reference", + "text": "AnotherDuplicateName", + "canonicalReference": "api-extractor-scenarios!~AnotherDuplicateName:class" + }, + { + "kind": "Content", + "text": ";" + } + ], + "returnTypeTokenRange": { + "startIndex": 1, + "endIndex": 2 + }, + "releaseTag": "Public", + "overloadIndex": 1, + "parameters": [], + "name": "someFunction7" + }, { "kind": "Namespace", "canonicalReference": "api-extractor-scenarios!SomeNamespace1:namespace", diff --git a/build-tests/api-extractor-scenarios/etc/includeForgottenExports/api-extractor-scenarios.api.md b/build-tests/api-extractor-scenarios/etc/includeForgottenExports/api-extractor-scenarios.api.md index 0b72f7f87ee..67e41fd149f 100644 --- a/build-tests/api-extractor-scenarios/etc/includeForgottenExports/api-extractor-scenarios.api.md +++ b/build-tests/api-extractor-scenarios/etc/includeForgottenExports/api-extractor-scenarios.api.md @@ -4,6 +4,13 @@ ```ts +class AnotherDuplicateName { +} + +// (undocumented) +class AnotherDuplicateName_2 { +} + // @public export type DuplicateName = boolean; @@ -59,6 +66,16 @@ export function someFunction4(): ForgottenExport4.ForgottenExport5; // @public (undocumented) export function someFunction5(): internal2.ForgottenExport6; +// Warning: (ae-forgotten-export) The symbol "AnotherDuplicateName" needs to be exported by the entry point index.d.ts +// +// @public (undocumented) +export function someFunction6(): AnotherDuplicateName; + +// Warning: (ae-forgotten-export) The symbol "AnotherDuplicateName_2" needs to be exported by the entry point index.d.ts +// +// @public (undocumented) +export function someFunction7(): AnotherDuplicateName_2; + // @public (undocumented) export namespace SomeNamespace1 { // (undocumented) diff --git a/build-tests/api-extractor-scenarios/etc/includeForgottenExports/rollup.d.ts b/build-tests/api-extractor-scenarios/etc/includeForgottenExports/rollup.d.ts index 1813908c601..23a3fbeb410 100644 --- a/build-tests/api-extractor-scenarios/etc/includeForgottenExports/rollup.d.ts +++ b/build-tests/api-extractor-scenarios/etc/includeForgottenExports/rollup.d.ts @@ -1,6 +1,16 @@ /** - * This type is exported but has the same name as an unexported type in './internal.ts'. This - * unexported type is also included in the API report and doc model files. The unexported type + * This forgotten item has the same name as another forgotten item in another + * file. They should be given unique names. + */ +declare class AnotherDuplicateName { +} + +declare class AnotherDuplicateName_2 { +} + +/** + * This type is exported but has the same name as a forgotten type in './internal.ts'. This + * forgotten type is also included in the API report and doc model files. The forgotten type * will be renamed to avoid a name conflict. * @public */ @@ -12,7 +22,10 @@ export declare type DuplicateName = boolean; */ declare type DuplicateName_2 = number; -/** This doc comment should be inherited by `ForgottenExport2` */ +/** + * `ForgottenExport2` wants to inherit this doc comment, but unfortunately this isn't + * supported yet + */ declare class ForgottenExport1 { prop?: ForgottenExport2; constructor(); @@ -47,6 +60,12 @@ export declare function someFunction4(): ForgottenExport4.ForgottenExport5; /** @public */ export declare function someFunction5(): internal2.ForgottenExport6; +/** @public */ +export declare function someFunction6(): AnotherDuplicateName; + +/** @public */ +export declare function someFunction7(): AnotherDuplicateName_2; + /** @public */ export declare namespace SomeNamespace1 { export class ForgottenExport3 { diff --git a/build-tests/api-extractor-scenarios/src/includeForgottenExports/index.ts b/build-tests/api-extractor-scenarios/src/includeForgottenExports/index.ts index bc73016b707..528cbd486cb 100644 --- a/build-tests/api-extractor-scenarios/src/includeForgottenExports/index.ts +++ b/build-tests/api-extractor-scenarios/src/includeForgottenExports/index.ts @@ -1,6 +1,9 @@ import * as internal2 from './internal2'; -/** This doc comment should be inherited by `ForgottenExport2` */ +/** + * `ForgottenExport2` wants to inherit this doc comment, but unfortunately this isn't + * supported yet + */ class ForgottenExport1 { prop?: ForgottenExport2; constructor() {} @@ -15,8 +18,8 @@ export function someFunction1(): ForgottenExport1 { } /** - * This type is exported but has the same name as an unexported type in './internal.ts'. This - * unexported type is also included in the API report and doc model files. The unexported type + * This type is exported but has the same name as a forgotten type in './internal.ts'. This + * forgotten type is also included in the API report and doc model files. The forgotten type * will be renamed to avoid a name conflict. * @public */ @@ -46,3 +49,16 @@ export function someFunction4(): ForgottenExport4.ForgottenExport5 { export function someFunction5(): internal2.ForgottenExport6 { return new internal2.ForgottenExport6(); } + +/** + * This forgotten item has the same name as another forgotten item in another + * file. They should be given unique names. + */ +class AnotherDuplicateName {} + +/** @public */ +export function someFunction6(): AnotherDuplicateName { + return new AnotherDuplicateName(); +} + +export { someFunction7 } from './internal1'; diff --git a/build-tests/api-extractor-scenarios/src/includeForgottenExports/internal1.ts b/build-tests/api-extractor-scenarios/src/includeForgottenExports/internal1.ts index 80c55355f29..0d666df5677 100644 --- a/build-tests/api-extractor-scenarios/src/includeForgottenExports/internal1.ts +++ b/build-tests/api-extractor-scenarios/src/includeForgottenExports/internal1.ts @@ -8,3 +8,10 @@ type DuplicateName = number; export function someFunction2(): DuplicateName { return 5; } + +class AnotherDuplicateName {} + +/** @public */ +export function someFunction7(): AnotherDuplicateName { + return new AnotherDuplicateName(); +} From 02ee8a0643921453345fd4a828f2eb2c902a22bd Mon Sep 17 00:00:00 2001 From: Zack Elliott Date: Mon, 15 Aug 2022 08:52:56 -0700 Subject: [PATCH 7/8] Fix subtle bug with ApiNamespaceImports --- .../src/generators/ApiModelGenerator.ts | 34 +++++++++---------- .../api-extractor-scenarios.api.json | 4 +-- 2 files changed, 18 insertions(+), 20 deletions(-) diff --git a/apps/api-extractor/src/generators/ApiModelGenerator.ts b/apps/api-extractor/src/generators/ApiModelGenerator.ts index 80e5011c741..bce54fc5f5d 100644 --- a/apps/api-extractor/src/generators/ApiModelGenerator.ts +++ b/apps/api-extractor/src/generators/ApiModelGenerator.ts @@ -124,7 +124,7 @@ export class ApiModelGenerator { // // This could be improved in the future, but it requires a stable mechanism for choosing an associated parent. // For thoughts about this: https://github.com/microsoft/rushstack/issues/1308 - this._processAstModule(astEntity.astModule, exportedName, parentApiItem); + this._processAstNamespaceImport(astEntity, exportedName, parentApiItem); return; } @@ -133,13 +133,15 @@ export class ApiModelGenerator { // form "export { X } from 'external-package'". We can also use this to solve GitHub issue #950. } - private _processAstModule( - astModule: AstModule, + private _processAstNamespaceImport( + astNamespaceImport: AstNamespaceImport, exportedName: string | undefined, parentApiItem: ApiItemContainerMixin ): void { + const astModule: AstModule = astNamespaceImport.astModule; const name: string = exportedName ? exportedName : astModule.moduleSymbol.name; const containerKey: string = ApiNamespace.getContainerKey(name); + const isExported: boolean = this._isExported(astNamespaceImport); let apiNamespace: ApiNamespace | undefined = parentApiItem.tryGetMemberByKey( containerKey @@ -151,7 +153,7 @@ export class ApiModelGenerator { docComment: undefined, releaseTag: ReleaseTag.None, excerptTokens: [], - isExported: true + isExported }); parentApiItem.addMember(apiNamespace); } @@ -400,7 +402,7 @@ export class ApiModelGenerator { const apiItemMetadata: ApiItemMetadata = this._collector.fetchApiItemMetadata(astDeclaration); const docComment: tsdoc.DocComment | undefined = apiItemMetadata.tsdocComment; const releaseTag: ReleaseTag = apiItemMetadata.effectiveReleaseTag; - const isExported: boolean = this._isExported(astDeclaration); + const isExported: boolean = this._isExported(astDeclaration.astSymbol.rootAstSymbol); apiClass = new ApiClass({ name, @@ -486,7 +488,7 @@ export class ApiModelGenerator { const releaseTag: ReleaseTag = apiItemMetadata.effectiveReleaseTag; const preserveMemberOrder: boolean = this._collector.extractorConfig.enumMemberOrder === EnumMemberOrder.Preserve; - const isExported: boolean = this._isExported(astDeclaration); + const isExported: boolean = this._isExported(astDeclaration.astSymbol.rootAstSymbol); apiEnum = new ApiEnum({ name, docComment, releaseTag, excerptTokens, preserveMemberOrder, isExported }); parentApiItem.addMember(apiEnum); @@ -570,7 +572,7 @@ export class ApiModelGenerator { const apiItemMetadata: ApiItemMetadata = this._collector.fetchApiItemMetadata(astDeclaration); const docComment: tsdoc.DocComment | undefined = apiItemMetadata.tsdocComment; const releaseTag: ReleaseTag = apiItemMetadata.effectiveReleaseTag; - const isExported: boolean = this._isExported(astDeclaration); + const isExported: boolean = this._isExported(astDeclaration.astSymbol.rootAstSymbol); apiFunction = new ApiFunction({ name, @@ -673,7 +675,7 @@ export class ApiModelGenerator { const apiItemMetadata: ApiItemMetadata = this._collector.fetchApiItemMetadata(astDeclaration); const docComment: tsdoc.DocComment | undefined = apiItemMetadata.tsdocComment; const releaseTag: ReleaseTag = apiItemMetadata.effectiveReleaseTag; - const isExported: boolean = this._isExported(astDeclaration); + const isExported: boolean = this._isExported(astDeclaration.astSymbol.rootAstSymbol); apiInterface = new ApiInterface({ name, @@ -823,7 +825,7 @@ export class ApiModelGenerator { const apiItemMetadata: ApiItemMetadata = this._collector.fetchApiItemMetadata(astDeclaration); const docComment: tsdoc.DocComment | undefined = apiItemMetadata.tsdocComment; const releaseTag: ReleaseTag = apiItemMetadata.effectiveReleaseTag; - const isExported: boolean = this._isExported(astDeclaration); + const isExported: boolean = this._isExported(astDeclaration.astSymbol.rootAstSymbol); apiNamespace = new ApiNamespace({ name, docComment, releaseTag, excerptTokens, isExported }); parentApiItem.addMember(apiNamespace); @@ -973,7 +975,7 @@ export class ApiModelGenerator { const apiItemMetadata: ApiItemMetadata = this._collector.fetchApiItemMetadata(astDeclaration); const docComment: tsdoc.DocComment | undefined = apiItemMetadata.tsdocComment; const releaseTag: ReleaseTag = apiItemMetadata.effectiveReleaseTag; - const isExported: boolean = this._isExported(astDeclaration); + const isExported: boolean = this._isExported(astDeclaration.astSymbol.rootAstSymbol); apiTypeAlias = new ApiTypeAlias({ name, @@ -1020,7 +1022,7 @@ export class ApiModelGenerator { const docComment: tsdoc.DocComment | undefined = apiItemMetadata.tsdocComment; const releaseTag: ReleaseTag = apiItemMetadata.effectiveReleaseTag; const isReadonly: boolean = this._isReadonly(astDeclaration); - const isExported: boolean = this._isExported(astDeclaration); + const isExported: boolean = this._isExported(astDeclaration.astSymbol.rootAstSymbol); apiVariable = new ApiVariable({ name, @@ -1136,17 +1138,13 @@ export class ApiModelGenerator { } } - private _isExported(astDeclaration: AstDeclaration): boolean { + private _isExported(astEntity: AstEntity): boolean { // Collector entities are only created for root symbols. - const entity: CollectorEntity | undefined = this._collector.tryGetCollectorEntity( - astDeclaration.astSymbol.rootAstSymbol - ); + const entity: CollectorEntity | undefined = this._collector.tryGetCollectorEntity(astEntity); if (!entity) { // This should never happen. - throw new InternalError( - `Failed to get collector entity for root symbol of declaration ${astDeclaration.astSymbol.localName}` - ); + throw new InternalError(`Failed to get collector entity for AstEntity "${astEntity.localName}"`); } return entity.exported; 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 e1a766f8a38..cc1d4c1dc2d 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 @@ -375,7 +375,7 @@ }, { "kind": "Namespace", - "canonicalReference": "api-extractor-scenarios!internal2:namespace", + "canonicalReference": "api-extractor-scenarios!~internal2:namespace", "docComment": "", "excerptTokens": [], "releaseTag": "None", @@ -384,7 +384,7 @@ "members": [ { "kind": "Class", - "canonicalReference": "api-extractor-scenarios!internal2.ForgottenExport6:class", + "canonicalReference": "api-extractor-scenarios!~internal2.ForgottenExport6:class", "docComment": "", "excerptTokens": [ { From d9a05872c12356187d34782641b447341ff9f24d Mon Sep 17 00:00:00 2001 From: Zack Elliott Date: Wed, 24 Aug 2022 18:24:04 -0700 Subject: [PATCH 8/8] Respond to PR feedback --- .../src/api/ExtractorMessageId.ts | 2 +- apps/api-extractor/src/collector/Collector.ts | 20 ++++++----- .../src/schemas/api-extractor-template.json | 6 ++-- .../api-extractor-scenarios.api.json | 34 +++++++++---------- .../api-extractor-scenarios.api.md | 11 +++--- .../etc/includeForgottenExports/rollup.d.ts | 11 +++++- .../src/includeForgottenExports/index.ts | 8 ++++- .../src/includeForgottenExports/internal1.ts | 2 ++ .../src/includeForgottenExports/internal2.ts | 1 + 9 files changed, 59 insertions(+), 36 deletions(-) diff --git a/apps/api-extractor/src/api/ExtractorMessageId.ts b/apps/api-extractor/src/api/ExtractorMessageId.ts index 277fc678aeb..9af37df1ff9 100644 --- a/apps/api-extractor/src/api/ExtractorMessageId.ts +++ b/apps/api-extractor/src/api/ExtractorMessageId.ts @@ -28,7 +28,7 @@ export const enum ExtractorMessageId { IncompatibleReleaseTags = 'ae-incompatible-release-tags', /** - * "___ is exported by the package, but it is missing a release tag (`@alpha`, `@beta`, `@public`, or `@internal`)." + * "___ is part of the package's API, but it is missing a release tag (`@alpha`, `@beta`, `@public`, or `@internal`)." */ MissingReleaseTag = 'ae-missing-release-tag', diff --git a/apps/api-extractor/src/collector/Collector.ts b/apps/api-extractor/src/collector/Collector.ts index 40d05bf12c4..06420065f3a 100644 --- a/apps/api-extractor/src/collector/Collector.ts +++ b/apps/api-extractor/src/collector/Collector.ts @@ -815,28 +815,30 @@ export class Collector { if (options.effectiveReleaseTag === ReleaseTag.None) { if (!astDeclaration.astSymbol.isExternal) { // for now, don't report errors for external code - // Don't report missing release tags for forgotten exports + // Don't report missing release tags for forgotten exports (unless we're including forgotten exports + // in either the API report or doc model). const astSymbol: AstSymbol = astDeclaration.astSymbol; const entity: CollectorEntity | undefined = this._entitiesByAstEntity.get(astSymbol.rootAstSymbol); - if (entity && entity.consumable) { + if ( + entity && + (entity.consumable || + this.extractorConfig.apiReportIncludeForgottenExports || + this.extractorConfig.docModelIncludeForgottenExports) + ) { // We also don't report errors for the default export of an entry point, since its doc comment // isn't easy to obtain from the .d.ts file if (astSymbol.rootAstSymbol.localName !== '_default') { this.messageRouter.addAnalyzerIssue( ExtractorMessageId.MissingReleaseTag, - `"${entity.astEntity.localName}" is exported by the package, but it is missing ` + + `"${entity.astEntity.localName}" is part of the package's API, but it is missing ` + `a release tag (@alpha, @beta, @public, or @internal)`, astSymbol ); } - - // All internal, exported APIs default to public if no release tag is specified. - options.effectiveReleaseTag = ReleaseTag.Public; } - } else { - // All external APIs default to public if no release tag is specified. - options.effectiveReleaseTag = ReleaseTag.Public; } + + options.effectiveReleaseTag = ReleaseTag.Public; } const apiItemMetadata: ApiItemMetadata = new ApiItemMetadata(options); diff --git a/apps/api-extractor/src/schemas/api-extractor-template.json b/apps/api-extractor/src/schemas/api-extractor-template.json index 53c09b3db99..62f4fd32442 100644 --- a/apps/api-extractor/src/schemas/api-extractor-template.json +++ b/apps/api-extractor/src/schemas/api-extractor-template.json @@ -86,7 +86,7 @@ * * DEFAULT VALUE: "by-name" */ - // enumMemberOrder?: EnumMemberOrder, + // "enumMemberOrder": "by-name", /** * Determines how the TypeScript compiler engine will be invoked by API Extractor. @@ -185,7 +185,7 @@ * * DEFAULT VALUE: "false" */ - // includeForgottenExports?: boolean + // "includeForgottenExports": false }, /** @@ -215,7 +215,7 @@ * * DEFAULT VALUE: "false" */ - // includeForgottenExports?: boolean + // "includeForgottenExports": false }, /** 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 cc1d4c1dc2d..95a94902c4a 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 @@ -175,14 +175,14 @@ { "kind": "Class", "canonicalReference": "api-extractor-scenarios!~AnotherDuplicateName_2:class", - "docComment": "", + "docComment": "/**\n * @public\n */\n", "excerptTokens": [ { "kind": "Content", "text": "declare class AnotherDuplicateName " } ], - "releaseTag": "None", + "releaseTag": "Public", "name": "AnotherDuplicateName_2", "preserveMemberOrder": false, "members": [], @@ -191,14 +191,14 @@ { "kind": "Class", "canonicalReference": "api-extractor-scenarios!~AnotherDuplicateName:class", - "docComment": "/**\n * This forgotten item has the same name as another forgotten item in another file. They should be given unique names.\n */\n", + "docComment": "/**\n * This forgotten item has the same name as another forgotten item in another file. They should be given unique names.\n *\n * @public\n */\n", "excerptTokens": [ { "kind": "Content", "text": "declare class AnotherDuplicateName " } ], - "releaseTag": "None", + "releaseTag": "Public", "name": "AnotherDuplicateName", "preserveMemberOrder": false, "members": [], @@ -207,7 +207,7 @@ { "kind": "TypeAlias", "canonicalReference": "api-extractor-scenarios!~DuplicateName_2:type", - "docComment": "/**\n * Will be renamed to avoid a name conflict with the exported `DuplicateName` from index.ts.\n */\n", + "docComment": "/**\n * Will be renamed to avoid a name conflict with the exported `DuplicateName` from index.ts.\n *\n * @public\n */\n", "excerptTokens": [ { "kind": "Content", @@ -222,7 +222,7 @@ "text": ";" } ], - "releaseTag": "None", + "releaseTag": "Public", "name": "DuplicateName_2", "typeTokenRange": { "startIndex": 1, @@ -257,14 +257,14 @@ { "kind": "Class", "canonicalReference": "api-extractor-scenarios!~ForgottenExport1:class", - "docComment": "/**\n * `ForgottenExport2` wants to inherit this doc comment, but unfortunately this isn't supported yet\n */\n", + "docComment": "/**\n * `ForgottenExport2` wants to inherit this doc comment, but unfortunately this isn't supported yet\n *\n * @public\n */\n", "excerptTokens": [ { "kind": "Content", "text": "declare class ForgottenExport1 " } ], - "releaseTag": "None", + "releaseTag": "Public", "name": "ForgottenExport1", "preserveMemberOrder": false, "members": [ @@ -278,7 +278,7 @@ "text": "constructor();" } ], - "releaseTag": "None", + "releaseTag": "Public", "isProtected": false, "overloadIndex": 1, "parameters": [] @@ -304,7 +304,7 @@ ], "isReadonly": false, "isOptional": true, - "releaseTag": "None", + "releaseTag": "Public", "name": "prop", "propertyTypeTokenRange": { "startIndex": 1, @@ -319,7 +319,7 @@ { "kind": "TypeAlias", "canonicalReference": "api-extractor-scenarios!~ForgottenExport2:type", - "docComment": "/**\n * {@inheritDoc ForgottenExport1}\n */\n", + "docComment": "/**\n * {@inheritDoc ForgottenExport1}\n *\n * @public\n */\n", "excerptTokens": [ { "kind": "Content", @@ -334,7 +334,7 @@ "text": ";" } ], - "releaseTag": "None", + "releaseTag": "Public", "name": "ForgottenExport2", "typeTokenRange": { "startIndex": 1, @@ -344,14 +344,14 @@ { "kind": "Namespace", "canonicalReference": "api-extractor-scenarios!~ForgottenExport4:namespace", - "docComment": "", + "docComment": "/**\n * @public\n */\n", "excerptTokens": [ { "kind": "Content", "text": "declare namespace ForgottenExport4 " } ], - "releaseTag": "None", + "releaseTag": "Public", "name": "ForgottenExport4", "preserveMemberOrder": false, "members": [ @@ -365,7 +365,7 @@ "text": "class ForgottenExport5 " } ], - "releaseTag": "None", + "releaseTag": "Public", "name": "ForgottenExport5", "preserveMemberOrder": false, "members": [], @@ -385,14 +385,14 @@ { "kind": "Class", "canonicalReference": "api-extractor-scenarios!~internal2.ForgottenExport6:class", - "docComment": "", + "docComment": "/**\n * @public\n */\n", "excerptTokens": [ { "kind": "Content", "text": "export declare class ForgottenExport6 " } ], - "releaseTag": "None", + "releaseTag": "Public", "name": "ForgottenExport6", "preserveMemberOrder": false, "members": [], diff --git a/build-tests/api-extractor-scenarios/etc/includeForgottenExports/api-extractor-scenarios.api.md b/build-tests/api-extractor-scenarios/etc/includeForgottenExports/api-extractor-scenarios.api.md index 67e41fd149f..43157fe55bb 100644 --- a/build-tests/api-extractor-scenarios/etc/includeForgottenExports/api-extractor-scenarios.api.md +++ b/build-tests/api-extractor-scenarios/etc/includeForgottenExports/api-extractor-scenarios.api.md @@ -4,18 +4,21 @@ ```ts +// @public class AnotherDuplicateName { } -// (undocumented) +// @public (undocumented) class AnotherDuplicateName_2 { } // @public export type DuplicateName = boolean; +// @public type DuplicateName_2 = number; +// @public class ForgottenExport1 { constructor(); // Warning: (ae-forgotten-export) The symbol "ForgottenExport2" needs to be exported by the entry point index.d.ts @@ -26,17 +29,17 @@ class ForgottenExport1 { // Warning: (ae-unresolved-inheritdoc-reference) The @inheritDoc reference could not be resolved: The package "api-extractor-scenarios" does not have an export "ForgottenExport1" // -// (undocumented) +// @public (undocumented) type ForgottenExport2 = number; -// (undocumented) +// @public (undocumented) namespace ForgottenExport4 { // (undocumented) class ForgottenExport5 { } } -// (undocumented) +// @public (undocumented) class ForgottenExport6 { } diff --git a/build-tests/api-extractor-scenarios/etc/includeForgottenExports/rollup.d.ts b/build-tests/api-extractor-scenarios/etc/includeForgottenExports/rollup.d.ts index 23a3fbeb410..8010e499625 100644 --- a/build-tests/api-extractor-scenarios/etc/includeForgottenExports/rollup.d.ts +++ b/build-tests/api-extractor-scenarios/etc/includeForgottenExports/rollup.d.ts @@ -1,10 +1,12 @@ /** * This forgotten item has the same name as another forgotten item in another * file. They should be given unique names. + * @public */ declare class AnotherDuplicateName { } +/** @public */ declare class AnotherDuplicateName_2 { } @@ -19,26 +21,33 @@ export declare type DuplicateName = boolean; /** * Will be renamed to avoid a name conflict with the exported `DuplicateName` from * index.ts. + * @public */ declare type DuplicateName_2 = number; /** * `ForgottenExport2` wants to inherit this doc comment, but unfortunately this isn't * supported yet + * @public */ declare class ForgottenExport1 { prop?: ForgottenExport2; constructor(); } -/** {@inheritDoc ForgottenExport1} */ +/** + * @public + * {@inheritDoc ForgottenExport1} + */ declare type ForgottenExport2 = number; +/** @public */ declare namespace ForgottenExport4 { class ForgottenExport5 { } } +/** @public */ declare class ForgottenExport6 { } diff --git a/build-tests/api-extractor-scenarios/src/includeForgottenExports/index.ts b/build-tests/api-extractor-scenarios/src/includeForgottenExports/index.ts index 528cbd486cb..908200b2973 100644 --- a/build-tests/api-extractor-scenarios/src/includeForgottenExports/index.ts +++ b/build-tests/api-extractor-scenarios/src/includeForgottenExports/index.ts @@ -3,13 +3,17 @@ import * as internal2 from './internal2'; /** * `ForgottenExport2` wants to inherit this doc comment, but unfortunately this isn't * supported yet + * @public */ class ForgottenExport1 { prop?: ForgottenExport2; constructor() {} } -/** {@inheritDoc ForgottenExport1} */ +/** + * @public + * {@inheritDoc ForgottenExport1} + */ type ForgottenExport2 = number; /** @public */ @@ -36,6 +40,7 @@ export namespace SomeNamespace1 { } } +/** @public */ namespace ForgottenExport4 { export class ForgottenExport5 {} } @@ -53,6 +58,7 @@ export function someFunction5(): internal2.ForgottenExport6 { /** * This forgotten item has the same name as another forgotten item in another * file. They should be given unique names. + * @public */ class AnotherDuplicateName {} diff --git a/build-tests/api-extractor-scenarios/src/includeForgottenExports/internal1.ts b/build-tests/api-extractor-scenarios/src/includeForgottenExports/internal1.ts index 0d666df5677..e9adb34de2e 100644 --- a/build-tests/api-extractor-scenarios/src/includeForgottenExports/internal1.ts +++ b/build-tests/api-extractor-scenarios/src/includeForgottenExports/internal1.ts @@ -1,6 +1,7 @@ /** * Will be renamed to avoid a name conflict with the exported `DuplicateName` from * index.ts. + * @public */ type DuplicateName = number; @@ -9,6 +10,7 @@ export function someFunction2(): DuplicateName { return 5; } +/** @public */ class AnotherDuplicateName {} /** @public */ diff --git a/build-tests/api-extractor-scenarios/src/includeForgottenExports/internal2.ts b/build-tests/api-extractor-scenarios/src/includeForgottenExports/internal2.ts index ef36f4ea517..248395af66b 100644 --- a/build-tests/api-extractor-scenarios/src/includeForgottenExports/internal2.ts +++ b/build-tests/api-extractor-scenarios/src/includeForgottenExports/internal2.ts @@ -1 +1,2 @@ +/** @public */ export class ForgottenExport6 {}