From d5882785745ef74ad5d6f9dd29bb61d4035245e1 Mon Sep 17 00:00:00 2001 From: Joshua Smithrud Date: Tue, 14 Jan 2025 00:31:37 +0000 Subject: [PATCH 1/9] feat: [WIP]: Allow customization of tags included in API reports --- apps/api-extractor/src/api/ExtractorConfig.ts | 32 +++++++++- apps/api-extractor/src/api/IConfigFile.ts | 13 ++++ .../src/generators/ApiReportGenerator.ts | 59 +++++++++++++------ .../src/schemas/api-extractor-defaults.json | 1 - .../src/schemas/api-extractor.schema.json | 8 +++ common/reviews/api/api-extractor.api.md | 2 + 6 files changed, 94 insertions(+), 21 deletions(-) diff --git a/apps/api-extractor/src/api/ExtractorConfig.ts b/apps/api-extractor/src/api/ExtractorConfig.ts index 1cdf8c96eb2..fd0aee28fda 100644 --- a/apps/api-extractor/src/api/ExtractorConfig.ts +++ b/apps/api-extractor/src/api/ExtractorConfig.ts @@ -172,6 +172,25 @@ export interface IExtractorConfigApiReport { fileName: string; } +/** Default {@link IConfigApiReport.reportVariants} */ +const defaultApiReportVariants: readonly ApiReportVariant[] = ['complete']; + +/** + * Default {@link IConfigApiReport.tagsToInclude}. + * + * @remarks + * Note that this list is externally documented, and directly affects report output. + * Also note that the order of tags in this list is significant, as it determines the order of tags in the report. + * Any changes to this list should be considered breaking. + */ +const defaultApiReportTags: readonly string[] = [ + '@sealed', + '@virtual', + '@override', + '@eventProperty', + '@deprecated' +]; + interface IExtractorConfigParameters { projectFolder: string; packageJson: INodePackageJson | undefined; @@ -186,6 +205,7 @@ interface IExtractorConfigParameters { reportFolder: string; reportTempFolder: string; apiReportIncludeForgottenExports: boolean; + apiReportTagsToInclude: readonly string[]; docModelEnabled: boolean; apiJsonFilePath: string; docModelIncludeForgottenExports: boolean; @@ -281,6 +301,8 @@ export class ExtractorConfig { public readonly reportFolder: string; /** {@inheritDoc IConfigApiReport.reportTempFolder} */ public readonly reportTempFolder: string; + /** {@inheritDoc IConfigApiReport.tagsToInclude} */ + public readonly apiReportTagsToInclude: readonly string[]; /** * Gets the file path for the "complete" (default) report configuration, if one was specified. @@ -371,6 +393,7 @@ export class ExtractorConfig { this.reportConfigs = parameters.reportConfigs; this.reportFolder = parameters.reportFolder; this.reportTempFolder = parameters.reportTempFolder; + this.apiReportTagsToInclude = parameters.apiReportTagsToInclude; this.docModelEnabled = parameters.docModelEnabled; this.apiJsonFilePath = parameters.apiJsonFilePath; this.docModelIncludeForgottenExports = parameters.docModelIncludeForgottenExports; @@ -915,6 +938,7 @@ export class ExtractorConfig { let reportFolder: string = tokenContext.projectFolder; let reportTempFolder: string = tokenContext.projectFolder; const reportConfigs: IExtractorConfigApiReport[] = []; + let apiReportTagsToInclude: readonly string[] = defaultApiReportTags; if (apiReportEnabled) { // Undefined case checked above where we assign `apiReportEnabled` const apiReportConfig: IConfigApiReport = configObject.apiReport!; @@ -947,7 +971,8 @@ export class ExtractorConfig { reportFileNameBase = ''; } - const reportVariantKinds: ApiReportVariant[] = apiReportConfig.reportVariants ?? ['complete']; + const reportVariantKinds: readonly ApiReportVariant[] = + apiReportConfig.reportVariants ?? defaultApiReportVariants; for (const reportVariantKind of reportVariantKinds) { // Omit the variant kind from the "complete" report file name for simplicity and for backwards compatibility. @@ -981,6 +1006,10 @@ export class ExtractorConfig { tokenContext ); } + + if (apiReportConfig.tagsToInclude !== undefined) { + apiReportTagsToInclude = apiReportConfig.tagsToInclude; + } } let docModelEnabled: boolean = false; @@ -1101,6 +1130,7 @@ export class ExtractorConfig { reportFolder, reportTempFolder, apiReportIncludeForgottenExports, + apiReportTagsToInclude, docModelEnabled, apiJsonFilePath, docModelIncludeForgottenExports, diff --git a/apps/api-extractor/src/api/IConfigFile.ts b/apps/api-extractor/src/api/IConfigFile.ts index 1a0884d1af0..5c6e87cda4d 100644 --- a/apps/api-extractor/src/api/IConfigFile.ts +++ b/apps/api-extractor/src/api/IConfigFile.ts @@ -139,6 +139,19 @@ export interface IConfigApiReport { * @defaultValue `false` */ includeForgottenExports?: boolean; + + /** + * Specifies a list of TSDoc tags that should be reported in the API report file for items whose documentation includes them. + * + * @remarks + * This can be used to include standard TSDoc tags or custom ones. + * Specified tag names must begin with \"@\". + * Tags will appear in the order they are specified in this list. + * Note that an item's release tag will always reported; this behavior cannot be overridden. + * + * @defaultValue `["@sealed", "\@virtual", "@override", "@eventProperty", "@deprecated"]` + */ + tagsToInclude?: string[]; } /** diff --git a/apps/api-extractor/src/generators/ApiReportGenerator.ts b/apps/api-extractor/src/generators/ApiReportGenerator.ts index 0a65c999507..6cb8b32213a 100644 --- a/apps/api-extractor/src/generators/ApiReportGenerator.ts +++ b/apps/api-extractor/src/generators/ApiReportGenerator.ts @@ -558,34 +558,55 @@ export class ApiReportGenerator { if (!collector.isAncillaryDeclaration(astDeclaration)) { const footerParts: string[] = []; const apiItemMetadata: ApiItemMetadata = collector.fetchApiItemMetadata(astDeclaration); + + // 1. Release tag (if present) if (!apiItemMetadata.releaseTagSameAsParent) { if (apiItemMetadata.effectiveReleaseTag !== ReleaseTag.None) { footerParts.push(ReleaseTag.getTagName(apiItemMetadata.effectiveReleaseTag)); } } - if (apiItemMetadata.isSealed) { - footerParts.push('@sealed'); - } - - if (apiItemMetadata.isVirtual) { - footerParts.push('@virtual'); - } - - if (apiItemMetadata.isOverride) { - footerParts.push('@override'); - } - - if (apiItemMetadata.isEventProperty) { - footerParts.push('@eventProperty'); - } - - if (apiItemMetadata.tsdocComment) { - if (apiItemMetadata.tsdocComment.deprecatedBlock) { - footerParts.push('@deprecated'); + // 2. Enumerate configured tags in the order they were specified + for (const tag of collector.extractorConfig.apiReportTagsToInclude) { + // Note that we check some tags specially. + switch (tag) { + case '@sealed': + if (apiItemMetadata.isSealed) { + footerParts.push(tag); + } + break; + case '@virtual': + if (apiItemMetadata.isVirtual) { + footerParts.push(tag); + } + break; + case '@override': + if (apiItemMetadata.isOverride) { + footerParts.push(tag); + } + break; + case '@eventProperty': + if (apiItemMetadata.isEventProperty) { + footerParts.push(tag); + } + break; + case '@deprecated': + if (apiItemMetadata.tsdocComment?.deprecatedBlock) { + footerParts.push(tag); + } + break; + default: + // If the tag was not handled specially, check if it is present in the metadata. + if (apiItemMetadata.tsdocComment?.customBlocks.some((block) => block.blockTag.tagName === tag)) { + footerParts.push(tag); + } else if (apiItemMetadata.tsdocComment?.modifierTagSet.hasTagName(tag)) { + footerParts.push(tag); + } + break; } } + // 3. If the item is undocumented, append notice at the end of the list if (apiItemMetadata.undocumented) { footerParts.push('(undocumented)'); diff --git a/apps/api-extractor/src/schemas/api-extractor-defaults.json b/apps/api-extractor/src/schemas/api-extractor-defaults.json index 0a5c813a217..bb6b6157aaf 100644 --- a/apps/api-extractor/src/schemas/api-extractor-defaults.json +++ b/apps/api-extractor/src/schemas/api-extractor-defaults.json @@ -30,7 +30,6 @@ "dtsRollup": { // ("enabled" is required) - "untrimmedFilePath": "/dist/.d.ts", "alphaTrimmedFilePath": "", "betaTrimmedFilePath": "", diff --git a/apps/api-extractor/src/schemas/api-extractor.schema.json b/apps/api-extractor/src/schemas/api-extractor.schema.json index a8ce1970267..4bfd1872cc2 100644 --- a/apps/api-extractor/src/schemas/api-extractor.schema.json +++ b/apps/api-extractor/src/schemas/api-extractor.schema.json @@ -106,6 +106,14 @@ "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" + }, + + "tagsToReport": { + "description": "Specifies a list of TSDoc tags that should be reported in the API report file for items whose documentation includes them. This can be used to include standard TSDoc tags or custom ones. Specified tag names must begin with \"@\". By default, the following tags are reported: [@sealed, @virtual, @override, @eventProperty, @deprecated]. Tags will appear in the order they are specified in this list. Note that an item's release tag will always reported; this behavior cannot be overridden.", + "type": "array", + "items": { + "type": "string" + } } }, "required": ["enabled"], diff --git a/common/reviews/api/api-extractor.api.md b/common/reviews/api/api-extractor.api.md index 7b49ce35648..415aa5912df 100644 --- a/common/reviews/api/api-extractor.api.md +++ b/common/reviews/api/api-extractor.api.md @@ -54,6 +54,7 @@ export class ExtractorConfig { readonly apiJsonFilePath: string; readonly apiReportEnabled: boolean; readonly apiReportIncludeForgottenExports: boolean; + readonly apiReportTagsToInclude: readonly string[]; readonly betaTrimmedFilePath: string; readonly bundledPackages: string[]; readonly docModelEnabled: boolean; @@ -186,6 +187,7 @@ export interface IConfigApiReport { reportFolder?: string; reportTempFolder?: string; reportVariants?: ApiReportVariant[]; + tagsToInclude?: string[]; } // @public From 2eb8a36a105082d969f758ad7ee515b8bde009e8 Mon Sep 17 00:00:00 2001 From: Joshua Smithrud Date: Tue, 14 Jan 2025 00:40:03 +0000 Subject: [PATCH 2/9] docs: Update code comments --- apps/api-extractor/src/api/IConfigFile.ts | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/apps/api-extractor/src/api/IConfigFile.ts b/apps/api-extractor/src/api/IConfigFile.ts index 5c6e87cda4d..9a53e5e9bed 100644 --- a/apps/api-extractor/src/api/IConfigFile.ts +++ b/apps/api-extractor/src/api/IConfigFile.ts @@ -141,12 +141,17 @@ export interface IConfigApiReport { includeForgottenExports?: boolean; /** - * Specifies a list of TSDoc tags that should be reported in the API report file for items whose documentation includes them. + * Specifies a list of {@link https://tsdoc.org/ | TSDoc} tags that should be reported in the API report file for + * items whose documentation includes them. * * @remarks - * This can be used to include standard TSDoc tags or custom ones. - * Specified tag names must begin with \"@\". + * Tag names must begin with \"@\". + * + * This list may include standard TSDoc tags as well as custom ones. + * TODO: document requirements around custom tags. + * * Tags will appear in the order they are specified in this list. + * * Note that an item's release tag will always reported; this behavior cannot be overridden. * * @defaultValue `["@sealed", "\@virtual", "@override", "@eventProperty", "@deprecated"]` From 6a017cbf984642ef8e528527fe28aa04c51ca501 Mon Sep 17 00:00:00 2001 From: Joshua Smithrud Date: Tue, 14 Jan 2025 00:50:22 +0000 Subject: [PATCH 3/9] fix: Align schema property name with code --- apps/api-extractor/src/schemas/api-extractor.schema.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/api-extractor/src/schemas/api-extractor.schema.json b/apps/api-extractor/src/schemas/api-extractor.schema.json index 4bfd1872cc2..db52b2026d3 100644 --- a/apps/api-extractor/src/schemas/api-extractor.schema.json +++ b/apps/api-extractor/src/schemas/api-extractor.schema.json @@ -108,7 +108,7 @@ "type": "boolean" }, - "tagsToReport": { + "tagsToInclude": { "description": "Specifies a list of TSDoc tags that should be reported in the API report file for items whose documentation includes them. This can be used to include standard TSDoc tags or custom ones. Specified tag names must begin with \"@\". By default, the following tags are reported: [@sealed, @virtual, @override, @eventProperty, @deprecated]. Tags will appear in the order they are specified in this list. Note that an item's release tag will always reported; this behavior cannot be overridden.", "type": "array", "items": { From 05bae997da442e63b58d006b83225bc8e2812d35 Mon Sep 17 00:00:00 2001 From: Joshua Smithrud Date: Tue, 14 Jan 2025 00:50:40 +0000 Subject: [PATCH 4/9] test: Update test case to leverage new functionality --- .../config/api-extractor.json | 5 +++-- .../dist/api-extractor-lib3-test.d.ts | 9 ++++++++- .../etc/api-extractor-lib3-test.api.md | 4 ++-- .../api-extractor-lib3-test/src/index.ts | 9 ++++++++- .../api-extractor-lib3-test/tsdoc.json | 20 +++++++++++++++++++ 5 files changed, 41 insertions(+), 6 deletions(-) create mode 100644 build-tests/api-extractor-lib3-test/tsdoc.json diff --git a/build-tests/api-extractor-lib3-test/config/api-extractor.json b/build-tests/api-extractor-lib3-test/config/api-extractor.json index 609b62dc032..2d802372595 100644 --- a/build-tests/api-extractor-lib3-test/config/api-extractor.json +++ b/build-tests/api-extractor-lib3-test/config/api-extractor.json @@ -1,10 +1,11 @@ { - "$schema": "https://developer.microsoft.com/json-schemas/api-extractor/v7/api-extractor.schema.json", + "$schema": "../../../apps/api-extractor/src/schemas/api-extractor.schema.json", "mainEntryPointFilePath": "/lib/index.d.ts", "apiReport": { - "enabled": true + "enabled": true, + "tagsToInclude": ["@customBlockTag", "@customModifierTag"] }, "docModel": { diff --git a/build-tests/api-extractor-lib3-test/dist/api-extractor-lib3-test.d.ts b/build-tests/api-extractor-lib3-test/dist/api-extractor-lib3-test.d.ts index 433e071b772..0d1c1557242 100644 --- a/build-tests/api-extractor-lib3-test/dist/api-extractor-lib3-test.d.ts +++ b/build-tests/api-extractor-lib3-test/dist/api-extractor-lib3-test.d.ts @@ -11,8 +11,15 @@ import { Lib1Class } from 'api-extractor-lib1-test'; export { Lib1Class } -/** @public */ +/** + * @customModifierTag + * @public + */ export declare class Lib3Class { + /** + * I am a documented property! + * @customBlockTag My docs include a custom block tag! + */ prop: boolean; } diff --git a/build-tests/api-extractor-lib3-test/etc/api-extractor-lib3-test.api.md b/build-tests/api-extractor-lib3-test/etc/api-extractor-lib3-test.api.md index f79328f79fe..e2d17e2406c 100644 --- a/build-tests/api-extractor-lib3-test/etc/api-extractor-lib3-test.api.md +++ b/build-tests/api-extractor-lib3-test/etc/api-extractor-lib3-test.api.md @@ -8,9 +8,9 @@ import { Lib1Class } from 'api-extractor-lib1-test'; export { Lib1Class } -// @public (undocumented) +// @public @customModifierTag (undocumented) export class Lib3Class { - // (undocumented) + // @customBlockTag prop: boolean; } diff --git a/build-tests/api-extractor-lib3-test/src/index.ts b/build-tests/api-extractor-lib3-test/src/index.ts index 51aa8d2c85f..e934dd2bf18 100644 --- a/build-tests/api-extractor-lib3-test/src/index.ts +++ b/build-tests/api-extractor-lib3-test/src/index.ts @@ -12,7 +12,14 @@ export { Lib1Class } from 'api-extractor-lib1-test'; -/** @public */ +/** + * @customModifierTag + * @public + */ export class Lib3Class { + /** + * I am a documented property! + * @customBlockTag My docs include a custom block tag! + */ prop: boolean; } diff --git a/build-tests/api-extractor-lib3-test/tsdoc.json b/build-tests/api-extractor-lib3-test/tsdoc.json new file mode 100644 index 00000000000..e47e6c98b14 --- /dev/null +++ b/build-tests/api-extractor-lib3-test/tsdoc.json @@ -0,0 +1,20 @@ +{ + "$schema": "https://developer.microsoft.com/json-schemas/tsdoc/v0/tsdoc.schema.json", + + "extends": ["@microsoft/api-extractor/extends/tsdoc-base.json"], + + "tagDefinitions": [ + { + "tagName": "@customModifierTag", + "syntaxKind": "modifier" + }, + { + "tagName": "@customBlockTag", + "syntaxKind": "modifier" + } + ], + "supportForTags": { + "@customModifierTag": true, + "@customBlockTag": true + } +} From c2c0dc1a1f9a8f3ef4498e23da0410f75ea1047a Mon Sep 17 00:00:00 2001 From: Joshua Smithrud Date: Tue, 14 Jan 2025 00:55:01 +0000 Subject: [PATCH 5/9] refactor: Rename property --- apps/api-extractor/src/api/ExtractorConfig.ts | 20 +++++++++---------- apps/api-extractor/src/api/IConfigFile.ts | 4 ++-- .../src/generators/ApiReportGenerator.ts | 2 +- .../src/schemas/api-extractor.schema.json | 4 ++-- .../config/api-extractor.json | 2 +- common/reviews/api/api-extractor.api.md | 4 ++-- 6 files changed, 18 insertions(+), 18 deletions(-) diff --git a/apps/api-extractor/src/api/ExtractorConfig.ts b/apps/api-extractor/src/api/ExtractorConfig.ts index fd0aee28fda..707e9240714 100644 --- a/apps/api-extractor/src/api/ExtractorConfig.ts +++ b/apps/api-extractor/src/api/ExtractorConfig.ts @@ -176,14 +176,14 @@ export interface IExtractorConfigApiReport { const defaultApiReportVariants: readonly ApiReportVariant[] = ['complete']; /** - * Default {@link IConfigApiReport.tagsToInclude}. + * Default {@link IConfigApiReport.tagsToReport}. * * @remarks * Note that this list is externally documented, and directly affects report output. * Also note that the order of tags in this list is significant, as it determines the order of tags in the report. * Any changes to this list should be considered breaking. */ -const defaultApiReportTags: readonly string[] = [ +const defaultTagsToReport: readonly string[] = [ '@sealed', '@virtual', '@override', @@ -205,7 +205,7 @@ interface IExtractorConfigParameters { reportFolder: string; reportTempFolder: string; apiReportIncludeForgottenExports: boolean; - apiReportTagsToInclude: readonly string[]; + tagsToReport: readonly string[]; docModelEnabled: boolean; apiJsonFilePath: string; docModelIncludeForgottenExports: boolean; @@ -301,8 +301,8 @@ export class ExtractorConfig { public readonly reportFolder: string; /** {@inheritDoc IConfigApiReport.reportTempFolder} */ public readonly reportTempFolder: string; - /** {@inheritDoc IConfigApiReport.tagsToInclude} */ - public readonly apiReportTagsToInclude: readonly string[]; + /** {@inheritDoc IConfigApiReport.tagsToReport} */ + public readonly tagsToReport: readonly string[]; /** * Gets the file path for the "complete" (default) report configuration, if one was specified. @@ -393,7 +393,7 @@ export class ExtractorConfig { this.reportConfigs = parameters.reportConfigs; this.reportFolder = parameters.reportFolder; this.reportTempFolder = parameters.reportTempFolder; - this.apiReportTagsToInclude = parameters.apiReportTagsToInclude; + this.tagsToReport = parameters.tagsToReport; this.docModelEnabled = parameters.docModelEnabled; this.apiJsonFilePath = parameters.apiJsonFilePath; this.docModelIncludeForgottenExports = parameters.docModelIncludeForgottenExports; @@ -938,7 +938,7 @@ export class ExtractorConfig { let reportFolder: string = tokenContext.projectFolder; let reportTempFolder: string = tokenContext.projectFolder; const reportConfigs: IExtractorConfigApiReport[] = []; - let apiReportTagsToInclude: readonly string[] = defaultApiReportTags; + let tagsToReport: readonly string[] = defaultTagsToReport; if (apiReportEnabled) { // Undefined case checked above where we assign `apiReportEnabled` const apiReportConfig: IConfigApiReport = configObject.apiReport!; @@ -1007,8 +1007,8 @@ export class ExtractorConfig { ); } - if (apiReportConfig.tagsToInclude !== undefined) { - apiReportTagsToInclude = apiReportConfig.tagsToInclude; + if (apiReportConfig.tagsToReport !== undefined) { + tagsToReport = apiReportConfig.tagsToReport; } } @@ -1130,7 +1130,7 @@ export class ExtractorConfig { reportFolder, reportTempFolder, apiReportIncludeForgottenExports, - apiReportTagsToInclude, + tagsToReport, docModelEnabled, apiJsonFilePath, docModelIncludeForgottenExports, diff --git a/apps/api-extractor/src/api/IConfigFile.ts b/apps/api-extractor/src/api/IConfigFile.ts index 9a53e5e9bed..5c82da13eb1 100644 --- a/apps/api-extractor/src/api/IConfigFile.ts +++ b/apps/api-extractor/src/api/IConfigFile.ts @@ -142,7 +142,7 @@ export interface IConfigApiReport { /** * Specifies a list of {@link https://tsdoc.org/ | TSDoc} tags that should be reported in the API report file for - * items whose documentation includes them. + * items whose documentation contains them. * * @remarks * Tag names must begin with \"@\". @@ -156,7 +156,7 @@ export interface IConfigApiReport { * * @defaultValue `["@sealed", "\@virtual", "@override", "@eventProperty", "@deprecated"]` */ - tagsToInclude?: string[]; + tagsToReport?: string[]; } /** diff --git a/apps/api-extractor/src/generators/ApiReportGenerator.ts b/apps/api-extractor/src/generators/ApiReportGenerator.ts index 6cb8b32213a..e3a8273a28d 100644 --- a/apps/api-extractor/src/generators/ApiReportGenerator.ts +++ b/apps/api-extractor/src/generators/ApiReportGenerator.ts @@ -567,7 +567,7 @@ export class ApiReportGenerator { } // 2. Enumerate configured tags in the order they were specified - for (const tag of collector.extractorConfig.apiReportTagsToInclude) { + for (const tag of collector.extractorConfig.tagsToReport) { // Note that we check some tags specially. switch (tag) { case '@sealed': diff --git a/apps/api-extractor/src/schemas/api-extractor.schema.json b/apps/api-extractor/src/schemas/api-extractor.schema.json index db52b2026d3..f7ad8006c3e 100644 --- a/apps/api-extractor/src/schemas/api-extractor.schema.json +++ b/apps/api-extractor/src/schemas/api-extractor.schema.json @@ -108,8 +108,8 @@ "type": "boolean" }, - "tagsToInclude": { - "description": "Specifies a list of TSDoc tags that should be reported in the API report file for items whose documentation includes them. This can be used to include standard TSDoc tags or custom ones. Specified tag names must begin with \"@\". By default, the following tags are reported: [@sealed, @virtual, @override, @eventProperty, @deprecated]. Tags will appear in the order they are specified in this list. Note that an item's release tag will always reported; this behavior cannot be overridden.", + "tagsToReport": { + "description": "Specifies a list of TSDoc tags that should be reported in the API report file for items whose documentation contains them. This can be used to include standard TSDoc tags or custom ones. Specified tag names must begin with \"@\". By default, the following tags are reported: [@sealed, @virtual, @override, @eventProperty, @deprecated]. Tags will appear in the order they are specified in this list. Note that an item's release tag will always reported; this behavior cannot be overridden.", "type": "array", "items": { "type": "string" diff --git a/build-tests/api-extractor-lib3-test/config/api-extractor.json b/build-tests/api-extractor-lib3-test/config/api-extractor.json index 2d802372595..b49d893f7e2 100644 --- a/build-tests/api-extractor-lib3-test/config/api-extractor.json +++ b/build-tests/api-extractor-lib3-test/config/api-extractor.json @@ -5,7 +5,7 @@ "apiReport": { "enabled": true, - "tagsToInclude": ["@customBlockTag", "@customModifierTag"] + "tagsToReport": ["@customBlockTag", "@customModifierTag"] }, "docModel": { diff --git a/common/reviews/api/api-extractor.api.md b/common/reviews/api/api-extractor.api.md index 415aa5912df..88ff4a4f2b3 100644 --- a/common/reviews/api/api-extractor.api.md +++ b/common/reviews/api/api-extractor.api.md @@ -54,7 +54,6 @@ export class ExtractorConfig { readonly apiJsonFilePath: string; readonly apiReportEnabled: boolean; readonly apiReportIncludeForgottenExports: boolean; - readonly apiReportTagsToInclude: readonly string[]; readonly betaTrimmedFilePath: string; readonly bundledPackages: string[]; readonly docModelEnabled: boolean; @@ -88,6 +87,7 @@ export class ExtractorConfig { readonly reportTempFolder: string; readonly rollupEnabled: boolean; readonly skipLibCheck: boolean; + readonly tagsToReport: readonly string[]; readonly testMode: boolean; static tryLoadForFolder(options: IExtractorConfigLoadForFolderOptions): IExtractorConfigPrepareOptions | undefined; readonly tsconfigFilePath: string; @@ -187,7 +187,7 @@ export interface IConfigApiReport { reportFolder?: string; reportTempFolder?: string; reportVariants?: ApiReportVariant[]; - tagsToInclude?: string[]; + tagsToReport?: string[]; } // @public From 6dab01c0552ed3cec5bab37eb39a3aa7531d1bc2 Mon Sep 17 00:00:00 2001 From: Joshua Smithrud Date: Tue, 14 Jan 2025 01:30:29 +0000 Subject: [PATCH 6/9] refactor: Make the structure of `tagsToReport` easier to override --- apps/api-extractor/src/api/ExtractorConfig.ts | 25 +++--- apps/api-extractor/src/api/IConfigFile.ts | 8 +- .../src/generators/ApiReportGenerator.ts | 76 ++++++++++--------- .../src/schemas/api-extractor.schema.json | 11 ++- .../etc/api-documenter-test.api.md | 6 +- .../config/api-extractor.json | 8 +- .../dist/api-extractor-lib3-test.d.ts | 1 + .../etc/api-extractor-lib3-test.api.md | 2 +- .../api-extractor-lib3-test/src/index.ts | 1 + common/reviews/api/api-extractor.api.md | 4 +- 10 files changed, 77 insertions(+), 65 deletions(-) diff --git a/apps/api-extractor/src/api/ExtractorConfig.ts b/apps/api-extractor/src/api/ExtractorConfig.ts index 707e9240714..004e61523c2 100644 --- a/apps/api-extractor/src/api/ExtractorConfig.ts +++ b/apps/api-extractor/src/api/ExtractorConfig.ts @@ -183,13 +183,13 @@ const defaultApiReportVariants: readonly ApiReportVariant[] = ['complete']; * Also note that the order of tags in this list is significant, as it determines the order of tags in the report. * Any changes to this list should be considered breaking. */ -const defaultTagsToReport: readonly string[] = [ - '@sealed', - '@virtual', - '@override', - '@eventProperty', - '@deprecated' -]; +const defaultTagsToReport: Readonly> = { + '@sealed': true, + '@virtual': true, + '@override': true, + '@eventProperty': true, + '@deprecated': true +}; interface IExtractorConfigParameters { projectFolder: string; @@ -205,7 +205,7 @@ interface IExtractorConfigParameters { reportFolder: string; reportTempFolder: string; apiReportIncludeForgottenExports: boolean; - tagsToReport: readonly string[]; + tagsToReport: Readonly>; docModelEnabled: boolean; apiJsonFilePath: string; docModelIncludeForgottenExports: boolean; @@ -302,7 +302,7 @@ export class ExtractorConfig { /** {@inheritDoc IConfigApiReport.reportTempFolder} */ public readonly reportTempFolder: string; /** {@inheritDoc IConfigApiReport.tagsToReport} */ - public readonly tagsToReport: readonly string[]; + public readonly tagsToReport: Readonly>; /** * Gets the file path for the "complete" (default) report configuration, if one was specified. @@ -938,7 +938,7 @@ export class ExtractorConfig { let reportFolder: string = tokenContext.projectFolder; let reportTempFolder: string = tokenContext.projectFolder; const reportConfigs: IExtractorConfigApiReport[] = []; - let tagsToReport: readonly string[] = defaultTagsToReport; + let tagsToReport: Record<`@${string}`, boolean> = {}; if (apiReportEnabled) { // Undefined case checked above where we assign `apiReportEnabled` const apiReportConfig: IConfigApiReport = configObject.apiReport!; @@ -1008,7 +1008,10 @@ export class ExtractorConfig { } if (apiReportConfig.tagsToReport !== undefined) { - tagsToReport = apiReportConfig.tagsToReport; + tagsToReport = { + ...defaultTagsToReport, + ...apiReportConfig.tagsToReport + }; } } diff --git a/apps/api-extractor/src/api/IConfigFile.ts b/apps/api-extractor/src/api/IConfigFile.ts index 5c82da13eb1..ea3cd0a50de 100644 --- a/apps/api-extractor/src/api/IConfigFile.ts +++ b/apps/api-extractor/src/api/IConfigFile.ts @@ -145,18 +145,16 @@ export interface IConfigApiReport { * items whose documentation contains them. * * @remarks - * Tag names must begin with \"@\". + * Tag names must begin with `@`. * * This list may include standard TSDoc tags as well as custom ones. * TODO: document requirements around custom tags. * - * Tags will appear in the order they are specified in this list. - * * Note that an item's release tag will always reported; this behavior cannot be overridden. * - * @defaultValue `["@sealed", "\@virtual", "@override", "@eventProperty", "@deprecated"]` + * @defaultValue `@sealed`, `@virtual`, `@override`, `@eventProperty`, `deprecated` */ - tagsToReport?: string[]; + tagsToReport?: Readonly>; } /** diff --git a/apps/api-extractor/src/generators/ApiReportGenerator.ts b/apps/api-extractor/src/generators/ApiReportGenerator.ts index e3a8273a28d..46a12be8ba1 100644 --- a/apps/api-extractor/src/generators/ApiReportGenerator.ts +++ b/apps/api-extractor/src/generators/ApiReportGenerator.ts @@ -566,43 +566,45 @@ export class ApiReportGenerator { } } - // 2. Enumerate configured tags in the order they were specified - for (const tag of collector.extractorConfig.tagsToReport) { - // Note that we check some tags specially. - switch (tag) { - case '@sealed': - if (apiItemMetadata.isSealed) { - footerParts.push(tag); - } - break; - case '@virtual': - if (apiItemMetadata.isVirtual) { - footerParts.push(tag); - } - break; - case '@override': - if (apiItemMetadata.isOverride) { - footerParts.push(tag); - } - break; - case '@eventProperty': - if (apiItemMetadata.isEventProperty) { - footerParts.push(tag); - } - break; - case '@deprecated': - if (apiItemMetadata.tsdocComment?.deprecatedBlock) { - footerParts.push(tag); - } - break; - default: - // If the tag was not handled specially, check if it is present in the metadata. - if (apiItemMetadata.tsdocComment?.customBlocks.some((block) => block.blockTag.tagName === tag)) { - footerParts.push(tag); - } else if (apiItemMetadata.tsdocComment?.modifierTagSet.hasTagName(tag)) { - footerParts.push(tag); - } - break; + // 2. Enumerate configured tags, reporting standard system tags first and then other configured tags. + // Note that the ordering we handle the standard tags is important for backwards compatibility. + // Also note that we had special mechanisms for checking whether or not an item is documented with these tags, + // so they are checked specially. + const { + '@sealed': reportSealedTag, + '@virtual': reportVirtualTag, + '@override': reportOverrideTag, + '@eventProperty': reportEventPropertyTag, + '@deprecated': reportDeprecatedTag, + ...otherTagsToReport + } = collector.extractorConfig.tagsToReport; + + // 2.a Check for standard tags and report those that are both configured and present in the metadata. + if (reportSealedTag && apiItemMetadata.isSealed) { + footerParts.push('@sealed'); + } + if (reportVirtualTag && apiItemMetadata.isVirtual) { + footerParts.push('@virtual'); + } + if (reportOverrideTag && apiItemMetadata.isOverride) { + footerParts.push('@override'); + } + if (reportEventPropertyTag && apiItemMetadata.isEventProperty) { + footerParts.push('@eventProperty'); + } + if (reportDeprecatedTag && apiItemMetadata.tsdocComment?.deprecatedBlock) { + footerParts.push('@deprecated'); + } + + // 2.b Check for other configured tags and report those that are present in the tsdoc metadata. + for (const [tag, reportTag] of Object.entries(otherTagsToReport)) { + if (reportTag) { + // If the tag was not handled specially, check if it is present in the metadata. + if (apiItemMetadata.tsdocComment?.customBlocks.some((block) => block.blockTag.tagName === tag)) { + footerParts.push(tag); + } else if (apiItemMetadata.tsdocComment?.modifierTagSet.hasTagName(tag)) { + footerParts.push(tag); + } } } diff --git a/apps/api-extractor/src/schemas/api-extractor.schema.json b/apps/api-extractor/src/schemas/api-extractor.schema.json index f7ad8006c3e..fb6817cf6c9 100644 --- a/apps/api-extractor/src/schemas/api-extractor.schema.json +++ b/apps/api-extractor/src/schemas/api-extractor.schema.json @@ -110,10 +110,13 @@ "tagsToReport": { "description": "Specifies a list of TSDoc tags that should be reported in the API report file for items whose documentation contains them. This can be used to include standard TSDoc tags or custom ones. Specified tag names must begin with \"@\". By default, the following tags are reported: [@sealed, @virtual, @override, @eventProperty, @deprecated]. Tags will appear in the order they are specified in this list. Note that an item's release tag will always reported; this behavior cannot be overridden.", - "type": "array", - "items": { - "type": "string" - } + "type": "object", + "patternProperties": { + "^@[^\\s]*$": { + "type": "boolean" + } + }, + "additionalProperties": false } }, "required": ["enabled"], diff --git a/build-tests/api-documenter-test/etc/api-documenter-test.api.md b/build-tests/api-documenter-test/etc/api-documenter-test.api.md index 18bab020adb..a516aca6ecc 100644 --- a/build-tests/api-documenter-test/etc/api-documenter-test.api.md +++ b/build-tests/api-documenter-test/etc/api-documenter-test.api.md @@ -36,15 +36,13 @@ export class DocBaseClass { export class DocClass1 extends DocBaseClass implements IDocInterface1, IDocInterface2 { // @internal constructor(name: string); - // @deprecated (undocumented) + // (undocumented) deprecatedExample(): void; exampleFunction(a: string, b: string): string; exampleFunction(x: number): number; genericWithConstraintAndDefault(x: T): void; interestingEdgeCases(): void; - // @eventProperty malformedEvent: SystemEvent; - // @eventProperty readonly modifiedEvent: SystemEvent; protected static readonly multipleModifiersProperty: boolean; optionalParamFunction(x?: number): void; @@ -117,7 +115,7 @@ export interface IDocInterface1 { // @public (undocumented) export interface IDocInterface2 extends IDocInterface1 { - // @deprecated (undocumented) + // (undocumented) deprecatedExample(): void; } diff --git a/build-tests/api-extractor-lib3-test/config/api-extractor.json b/build-tests/api-extractor-lib3-test/config/api-extractor.json index b49d893f7e2..76705d69b97 100644 --- a/build-tests/api-extractor-lib3-test/config/api-extractor.json +++ b/build-tests/api-extractor-lib3-test/config/api-extractor.json @@ -5,7 +5,13 @@ "apiReport": { "enabled": true, - "tagsToReport": ["@customBlockTag", "@customModifierTag"] + "tagsToReport": { + // Disable reporting of `@virtual`, which is reported by default + "@virtual": false, + // Enable reporting of our custom TSDoc tags. + "@customBlockTag": true, + "@customModifierTag": true + } }, "docModel": { diff --git a/build-tests/api-extractor-lib3-test/dist/api-extractor-lib3-test.d.ts b/build-tests/api-extractor-lib3-test/dist/api-extractor-lib3-test.d.ts index 0d1c1557242..a65b3887b9c 100644 --- a/build-tests/api-extractor-lib3-test/dist/api-extractor-lib3-test.d.ts +++ b/build-tests/api-extractor-lib3-test/dist/api-extractor-lib3-test.d.ts @@ -19,6 +19,7 @@ export declare class Lib3Class { /** * I am a documented property! * @customBlockTag My docs include a custom block tag! + * @virtual @override */ prop: boolean; } diff --git a/build-tests/api-extractor-lib3-test/etc/api-extractor-lib3-test.api.md b/build-tests/api-extractor-lib3-test/etc/api-extractor-lib3-test.api.md index e2d17e2406c..5c538497301 100644 --- a/build-tests/api-extractor-lib3-test/etc/api-extractor-lib3-test.api.md +++ b/build-tests/api-extractor-lib3-test/etc/api-extractor-lib3-test.api.md @@ -10,7 +10,7 @@ export { Lib1Class } // @public @customModifierTag (undocumented) export class Lib3Class { - // @customBlockTag + // @override @customBlockTag prop: boolean; } diff --git a/build-tests/api-extractor-lib3-test/src/index.ts b/build-tests/api-extractor-lib3-test/src/index.ts index e934dd2bf18..577ebd8f710 100644 --- a/build-tests/api-extractor-lib3-test/src/index.ts +++ b/build-tests/api-extractor-lib3-test/src/index.ts @@ -20,6 +20,7 @@ export class Lib3Class { /** * I am a documented property! * @customBlockTag My docs include a custom block tag! + * @virtual @override */ prop: boolean; } diff --git a/common/reviews/api/api-extractor.api.md b/common/reviews/api/api-extractor.api.md index 88ff4a4f2b3..0fa12fadabe 100644 --- a/common/reviews/api/api-extractor.api.md +++ b/common/reviews/api/api-extractor.api.md @@ -87,7 +87,7 @@ export class ExtractorConfig { readonly reportTempFolder: string; readonly rollupEnabled: boolean; readonly skipLibCheck: boolean; - readonly tagsToReport: readonly string[]; + readonly tagsToReport: Readonly>; readonly testMode: boolean; static tryLoadForFolder(options: IExtractorConfigLoadForFolderOptions): IExtractorConfigPrepareOptions | undefined; readonly tsconfigFilePath: string; @@ -187,7 +187,7 @@ export interface IConfigApiReport { reportFolder?: string; reportTempFolder?: string; reportVariants?: ApiReportVariant[]; - tagsToReport?: string[]; + tagsToReport?: Readonly>; } // @public From bc7a0380456ac8f7d2619b09bb08f8b36daf5666 Mon Sep 17 00:00:00 2001 From: Joshua Smithrud Date: Tue, 14 Jan 2025 01:37:21 +0000 Subject: [PATCH 7/9] fix: Config handling --- apps/api-extractor/src/api/ExtractorConfig.ts | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/apps/api-extractor/src/api/ExtractorConfig.ts b/apps/api-extractor/src/api/ExtractorConfig.ts index 004e61523c2..e55fa31e748 100644 --- a/apps/api-extractor/src/api/ExtractorConfig.ts +++ b/apps/api-extractor/src/api/ExtractorConfig.ts @@ -1007,12 +1007,10 @@ export class ExtractorConfig { ); } - if (apiReportConfig.tagsToReport !== undefined) { - tagsToReport = { - ...defaultTagsToReport, - ...apiReportConfig.tagsToReport - }; - } + tagsToReport = { + ...defaultTagsToReport, + ...apiReportConfig.tagsToReport + }; } let docModelEnabled: boolean = false; From 7473f543189ea98cf6f5d9003ce816bfcca1b4f9 Mon Sep 17 00:00:00 2001 From: Joshua Smithrud Date: Tue, 14 Jan 2025 01:42:29 +0000 Subject: [PATCH 8/9] test: Update test scenario --- build-tests/api-documenter-test/config/api-extractor.json | 7 +++++-- .../api-documenter-test/etc/api-documenter-test.api.md | 8 +++++--- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/build-tests/api-documenter-test/config/api-extractor.json b/build-tests/api-documenter-test/config/api-extractor.json index 17b82fa03cc..3e2857a5472 100644 --- a/build-tests/api-documenter-test/config/api-extractor.json +++ b/build-tests/api-documenter-test/config/api-extractor.json @@ -1,12 +1,15 @@ { - "$schema": "https://developer.microsoft.com/json-schemas/api-extractor/v7/api-extractor.schema.json", + "$schema": "../../../apps/api-extractor/src/schemas/api-extractor.schema.json", "mainEntryPointFilePath": "/lib/index.d.ts", "newlineKind": "crlf", "apiReport": { - "enabled": true + "enabled": true, + "tagsToReport": { + "@myCustomTag": true + } }, "docModel": { diff --git a/build-tests/api-documenter-test/etc/api-documenter-test.api.md b/build-tests/api-documenter-test/etc/api-documenter-test.api.md index a516aca6ecc..9ed61a12c24 100644 --- a/build-tests/api-documenter-test/etc/api-documenter-test.api.md +++ b/build-tests/api-documenter-test/etc/api-documenter-test.api.md @@ -36,13 +36,15 @@ export class DocBaseClass { export class DocClass1 extends DocBaseClass implements IDocInterface1, IDocInterface2 { // @internal constructor(name: string); - // (undocumented) + // @deprecated (undocumented) deprecatedExample(): void; exampleFunction(a: string, b: string): string; exampleFunction(x: number): number; genericWithConstraintAndDefault(x: T): void; interestingEdgeCases(): void; + // @eventProperty malformedEvent: SystemEvent; + // @eventProperty readonly modifiedEvent: SystemEvent; protected static readonly multipleModifiersProperty: boolean; optionalParamFunction(x?: number): void; @@ -115,7 +117,7 @@ export interface IDocInterface1 { // @public (undocumented) export interface IDocInterface2 extends IDocInterface1 { - // (undocumented) + // @deprecated (undocumented) deprecatedExample(): void; } @@ -182,7 +184,7 @@ export namespace OuterNamespace { let nestedVariable: boolean; } -// @public +// @public @myCustomTag export class SystemEvent { addHandler(handler: () => void): void; } From da818e6482eacca1f69bd5475397df8532185d0f Mon Sep 17 00:00:00 2001 From: Joshua Smithrud <54606601+Josmithr@users.noreply.github.com> Date: Tue, 4 Feb 2025 10:47:44 -0800 Subject: [PATCH 9/9] docs: Fix comment --- apps/api-extractor/src/api/IConfigFile.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/api-extractor/src/api/IConfigFile.ts b/apps/api-extractor/src/api/IConfigFile.ts index ea3cd0a50de..efad12c4aa5 100644 --- a/apps/api-extractor/src/api/IConfigFile.ts +++ b/apps/api-extractor/src/api/IConfigFile.ts @@ -152,7 +152,7 @@ export interface IConfigApiReport { * * Note that an item's release tag will always reported; this behavior cannot be overridden. * - * @defaultValue `@sealed`, `@virtual`, `@override`, `@eventProperty`, `deprecated` + * @defaultValue `@sealed`, `@virtual`, `@override`, `@eventProperty`, `@deprecated` */ tagsToReport?: Readonly>; }