From a432cab46f5015552d67f2ab9fc4178f3e62ab0e Mon Sep 17 00:00:00 2001 From: Ron Buckton Date: Sat, 21 Sep 2019 13:51:58 -0700 Subject: [PATCH 1/5] Disambiguate output files with colliding names --- .../documenters/ExperimentalYamlDocumenter.ts | 19 ++--- .../src/documenters/YamlDocumenter.ts | 70 +++++++++++++++---- .../etc/api-documenter-test.api.json | 30 ++++++++ .../etc/api-documenter-test.api.md | 8 +++ ...-documenter-test.docclassinterfacemerge.md | 13 ++++ .../etc/markdown/api-documenter-test.md | 2 + .../etc/yaml/api-documenter-test.yml | 6 ++ .../docclassinterfacemerge-class.yml | 10 +++ .../docclassinterfacemerge-interface.yml | 10 +++ .../api-documenter-test/etc/yaml/toc.yml | 4 ++ .../api-documenter-test/src/DocClass1.ts | 14 ++++ .../disambiguateFiles_2019-09-21-20-52.json | 11 +++ 12 files changed, 171 insertions(+), 26 deletions(-) create mode 100644 build-tests/api-documenter-test/etc/markdown/api-documenter-test.docclassinterfacemerge.md create mode 100644 build-tests/api-documenter-test/etc/yaml/api-documenter-test/docclassinterfacemerge-class.yml create mode 100644 build-tests/api-documenter-test/etc/yaml/api-documenter-test/docclassinterfacemerge-interface.yml create mode 100644 common/changes/@microsoft/api-documenter/disambiguateFiles_2019-09-21-20-52.json diff --git a/apps/api-documenter/src/documenters/ExperimentalYamlDocumenter.ts b/apps/api-documenter/src/documenters/ExperimentalYamlDocumenter.ts index 1b1edbff7cd..4412ca1e8c3 100644 --- a/apps/api-documenter/src/documenters/ExperimentalYamlDocumenter.ts +++ b/apps/api-documenter/src/documenters/ExperimentalYamlDocumenter.ts @@ -1,7 +1,6 @@ // Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. // See LICENSE in the project root for license information. -import { PackageName } from '@microsoft/node-core-library'; import { DocComment, DocInlineTag } from '@microsoft/tsdoc'; import { ApiModel, ApiItem, ApiItemKind, ApiDocumentedItem } from '@microsoft/api-extractor-model'; @@ -42,7 +41,7 @@ export class ExperimentalYamlDocumenter extends YamlDocumenter { if (apiItem.kind === ApiItemKind.Namespace) { // Namespaces don't have nodes yet tocItem = { - name: apiItem.displayName + name: this._getTocItemName(apiItem) }; } else { if (this._shouldEmbed(apiItem.kind)) { @@ -50,16 +49,12 @@ export class ExperimentalYamlDocumenter extends YamlDocumenter { continue; } - if (apiItem.kind === ApiItemKind.Package) { - tocItem = { - name: PackageName.getUnscopedName(apiItem.displayName), - uid: this._getUid(apiItem) - }; - } else { - tocItem = { - name: apiItem.displayName, - uid: this._getUid(apiItem) - }; + tocItem = { + name: this._getTocItemName(apiItem), + uid: this._getUid(apiItem) + }; + + if (apiItem.kind !== ApiItemKind.Package) { this._filterItem(apiItem, tocItem); } } diff --git a/apps/api-documenter/src/documenters/YamlDocumenter.ts b/apps/api-documenter/src/documenters/YamlDocumenter.ts index be33d59dd6a..50174709ede 100644 --- a/apps/api-documenter/src/documenters/YamlDocumenter.ts +++ b/apps/api-documenter/src/documenters/YamlDocumenter.ts @@ -75,6 +75,8 @@ export class YamlDocumenter { private _apiItemsByCanonicalReference: Map; private _yamlReferences: IYamlReferences | undefined; + // Keeps track of ApiItems whose names collide with one or more siblings. + private _collisions: Set; private _outputFolder: string; @@ -82,6 +84,7 @@ export class YamlDocumenter { this._apiModel = apiModel; this._markdownEmitter = new CustomMarkdownEmitter(this._apiModel); this._apiItemsByCanonicalReference = new Map(); + this._collisions = new Set(); this._initApiItems(); } @@ -232,7 +235,7 @@ export class YamlDocumenter { if (apiItem.kind === ApiItemKind.Namespace) { // Namespaces don't have nodes yet tocItem = { - name: apiItem.displayName + name: this._getTocItemName(apiItem) }; } else { if (this._shouldEmbed(apiItem.kind)) { @@ -240,17 +243,10 @@ export class YamlDocumenter { continue; } - if (apiItem.kind === ApiItemKind.Package) { - tocItem = { - name: PackageName.getUnscopedName(apiItem.displayName), - uid: this._getUid(apiItem) - }; - } else { - tocItem = { - name: apiItem.displayName, - uid: this._getUid(apiItem) - }; - } + tocItem = { + name: this._getTocItemName(apiItem), + uid: this._getUid(apiItem) + }; } tocItems.push(tocItem); @@ -271,6 +267,20 @@ export class YamlDocumenter { return tocItems; } + /** @virtual */ + protected _getTocItemName(apiItem: ApiItem): string { + let name: string = apiItem.displayName; + if (apiItem.kind === ApiItemKind.Package) { + name = PackageName.getUnscopedName(name); + } + + if (this._collisions.has(apiItem)) { + name += ` (${apiItem.kind})`; + } + + return name; + } + protected _shouldEmbed(apiItemKind: ApiItemKind): boolean { switch (apiItemKind) { case ApiItemKind.Class: @@ -591,7 +601,6 @@ export class YamlDocumenter { */ private _initApiItems(): void { this._initApiItemsRecursive(this._apiModel); - } /** @@ -604,12 +613,39 @@ export class YamlDocumenter { // Recurse container members if (ApiItemContainerMixin.isBaseClassOf(apiItem)) { + const singletons: Map = new Map(); + const collidingNames: Set = new Set(); for (const apiMember of apiItem.members) { + this._trackCollisionsWithSiblings(apiMember, singletons, collidingNames); this._initApiItemsRecursive(apiMember); } } } + private _trackCollisionsWithSiblings( + apiItem: ApiItem, + singletons?: Map, + collidingNames?: Set + ): void { + if (singletons && collidingNames) { + if (!collidingNames.has(apiItem.displayName)) { + const collision: ApiItem | undefined = singletons.get(apiItem.displayName); + if (!collision) { + // No collision. Record this singleton entry. + singletons.set(apiItem.displayName, apiItem); + return; + } + // First collision. Record the colliding name. + collidingNames.add(apiItem.displayName); + singletons.delete(apiItem.displayName); + // Record the initial entry. + this._collisions.add(collision); + } + // Record the colliding entry. + this._collisions.add(apiItem); + } + } + private _ensureYamlReferences(): IYamlReferences { if (!this._yamlReferences) { this._yamlReferences = { @@ -803,7 +839,13 @@ export class YamlDocumenter { break; } } - return path.join(this._outputFolder, result + '.yml'); + + let disambiguator: string = ''; + if (this._collisions.has(apiItem)) { + disambiguator = `-${apiItem.kind.toLowerCase()}`; + } + + return path.join(this._outputFolder, result + disambiguator + '.yml'); } private _deleteOldOutputFiles(): void { diff --git a/build-tests/api-documenter-test/etc/api-documenter-test.api.json b/build-tests/api-documenter-test/etc/api-documenter-test.api.json index a6ccde1dff2..3590068c968 100644 --- a/build-tests/api-documenter-test/etc/api-documenter-test.api.json +++ b/build-tests/api-documenter-test/etc/api-documenter-test.api.json @@ -483,6 +483,36 @@ } ] }, + { + "kind": "Class", + "canonicalReference": "api-documenter-test!DocClassInterfaceMerge:class", + "docComment": "/**\n * Class that merges with interface\n *\n * @public\n */\n", + "excerptTokens": [ + { + "kind": "Content", + "text": "export declare class DocClassInterfaceMerge " + } + ], + "releaseTag": "Public", + "name": "DocClassInterfaceMerge", + "members": [], + "implementsTokenRanges": [] + }, + { + "kind": "Interface", + "canonicalReference": "api-documenter-test!DocClassInterfaceMerge:interface", + "docComment": "/**\n * Interface that merges with class\n *\n * @public\n */\n", + "excerptTokens": [ + { + "kind": "Content", + "text": "export interface DocClassInterfaceMerge " + } + ], + "releaseTag": "Public", + "name": "DocClassInterfaceMerge", + "members": [], + "extendsTokenRanges": [] + }, { "kind": "Enum", "canonicalReference": "api-documenter-test!DocEnum:enum", 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 c529a151a7d..fa8f9f82dc7 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 @@ -31,6 +31,14 @@ export class DocClass1 extends DocBaseClass implements IDocInterface1, IDocInter tableExample(): void; } +// @public +export class DocClassInterfaceMerge { +} + +// @public +export interface DocClassInterfaceMerge { +} + // @public export enum DocEnum { One = 1, diff --git a/build-tests/api-documenter-test/etc/markdown/api-documenter-test.docclassinterfacemerge.md b/build-tests/api-documenter-test/etc/markdown/api-documenter-test.docclassinterfacemerge.md new file mode 100644 index 00000000000..50644c01913 --- /dev/null +++ b/build-tests/api-documenter-test/etc/markdown/api-documenter-test.docclassinterfacemerge.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [api-documenter-test](./api-documenter-test.md) > [DocClassInterfaceMerge](./api-documenter-test.docclassinterfacemerge.md) + +## DocClassInterfaceMerge interface + +Interface that merges with class + +Signature: + +```typescript +export interface DocClassInterfaceMerge +``` diff --git a/build-tests/api-documenter-test/etc/markdown/api-documenter-test.md b/build-tests/api-documenter-test/etc/markdown/api-documenter-test.md index 1bf11d1b13f..d59a347ee9a 100644 --- a/build-tests/api-documenter-test/etc/markdown/api-documenter-test.md +++ b/build-tests/api-documenter-test/etc/markdown/api-documenter-test.md @@ -14,6 +14,7 @@ This project tests various documentation generation scenarios and doc comment sy | --- | --- | | [DocBaseClass](./api-documenter-test.docbaseclass.md) | Example base class | | [DocClass1](./api-documenter-test.docclass1.md) | This is an example class. | +| [DocClassInterfaceMerge](./api-documenter-test.docclassinterfacemerge.md) | Class that merges with interface | | [Generic](./api-documenter-test.generic.md) | Generic class. | | [SystemEvent](./api-documenter-test.systemevent.md) | A class used to exposed events. | @@ -34,6 +35,7 @@ This project tests various documentation generation scenarios and doc comment sy | Interface | Description | | --- | --- | +| [DocClassInterfaceMerge](./api-documenter-test.docclassinterfacemerge.md) | Interface that merges with class | | [IDocInterface1](./api-documenter-test.idocinterface1.md) | | | [IDocInterface2](./api-documenter-test.idocinterface2.md) | | | [IDocInterface3](./api-documenter-test.idocinterface3.md) | Some less common TypeScript declaration kinds. | diff --git a/build-tests/api-documenter-test/etc/yaml/api-documenter-test.yml b/build-tests/api-documenter-test/etc/yaml/api-documenter-test.yml index aa9f937378a..12fdd60f6f3 100644 --- a/build-tests/api-documenter-test/etc/yaml/api-documenter-test.yml +++ b/build-tests/api-documenter-test/etc/yaml/api-documenter-test.yml @@ -13,6 +13,8 @@ items: children: - 'api-documenter-test!DocBaseClass:class' - 'api-documenter-test!DocClass1:class' + - 'api-documenter-test!DocClassInterfaceMerge:class' + - 'api-documenter-test!DocClassInterfaceMerge:interface' - 'api-documenter-test!DocEnum:enum' - 'api-documenter-test!Generic:class' - 'api-documenter-test!globalFunction:function(1)' @@ -78,6 +80,10 @@ references: name: DocBaseClass - uid: 'api-documenter-test!DocClass1:class' name: DocClass1 + - uid: 'api-documenter-test!DocClassInterfaceMerge:class' + name: DocClassInterfaceMerge + - uid: 'api-documenter-test!DocClassInterfaceMerge:interface' + name: DocClassInterfaceMerge - uid: 'api-documenter-test!DocEnum:enum' name: DocEnum - uid: 'api-documenter-test!Generic:class' diff --git a/build-tests/api-documenter-test/etc/yaml/api-documenter-test/docclassinterfacemerge-class.yml b/build-tests/api-documenter-test/etc/yaml/api-documenter-test/docclassinterfacemerge-class.yml new file mode 100644 index 00000000000..8185a4946ff --- /dev/null +++ b/build-tests/api-documenter-test/etc/yaml/api-documenter-test/docclassinterfacemerge-class.yml @@ -0,0 +1,10 @@ +### YamlMime:UniversalReference +items: + - uid: 'api-documenter-test!DocClassInterfaceMerge:class' + summary: Class that merges with interface + name: DocClassInterfaceMerge + fullName: DocClassInterfaceMerge + langs: + - typeScript + type: class + package: api-documenter-test! diff --git a/build-tests/api-documenter-test/etc/yaml/api-documenter-test/docclassinterfacemerge-interface.yml b/build-tests/api-documenter-test/etc/yaml/api-documenter-test/docclassinterfacemerge-interface.yml new file mode 100644 index 00000000000..7f02423093f --- /dev/null +++ b/build-tests/api-documenter-test/etc/yaml/api-documenter-test/docclassinterfacemerge-interface.yml @@ -0,0 +1,10 @@ +### YamlMime:UniversalReference +items: + - uid: 'api-documenter-test!DocClassInterfaceMerge:interface' + summary: Interface that merges with class + name: DocClassInterfaceMerge + fullName: DocClassInterfaceMerge + langs: + - typeScript + type: interface + package: api-documenter-test! diff --git a/build-tests/api-documenter-test/etc/yaml/toc.yml b/build-tests/api-documenter-test/etc/yaml/toc.yml index bd84a4770d0..376c3df8b77 100644 --- a/build-tests/api-documenter-test/etc/yaml/toc.yml +++ b/build-tests/api-documenter-test/etc/yaml/toc.yml @@ -39,6 +39,10 @@ items: items: - name: InjectedCustomItem uid: customUrl + - name: DocClassInterfaceMerge (Class) + uid: 'api-documenter-test!DocClassInterfaceMerge:class' + - name: DocClassInterfaceMerge (Interface) + uid: 'api-documenter-test!DocClassInterfaceMerge:interface' - name: DocEnum uid: 'api-documenter-test!DocEnum:enum' - name: Generic diff --git a/build-tests/api-documenter-test/src/DocClass1.ts b/build-tests/api-documenter-test/src/DocClass1.ts index a660de56552..a20df5740f8 100644 --- a/build-tests/api-documenter-test/src/DocClass1.ts +++ b/build-tests/api-documenter-test/src/DocClass1.ts @@ -276,4 +276,18 @@ export interface IDocInterface6 { intersectionProperty: IDocInterface1 & IDocInterface2; typeReferenceProperty: Generic; genericReferenceMethod(x: T): T; +} + +/** + * Class that merges with interface + * @public + */ +export class DocClassInterfaceMerge { +} + +/** + * Interface that merges with class + * @public + */ +export interface DocClassInterfaceMerge { } \ No newline at end of file diff --git a/common/changes/@microsoft/api-documenter/disambiguateFiles_2019-09-21-20-52.json b/common/changes/@microsoft/api-documenter/disambiguateFiles_2019-09-21-20-52.json new file mode 100644 index 00000000000..b854f00e2a5 --- /dev/null +++ b/common/changes/@microsoft/api-documenter/disambiguateFiles_2019-09-21-20-52.json @@ -0,0 +1,11 @@ +{ + "changes": [ + { + "packageName": "@microsoft/api-documenter", + "comment": "Disambiguate output files for items with colliding names", + "type": "patch" + } + ], + "packageName": "@microsoft/api-documenter", + "email": "ron.buckton@microsoft.com" +} \ No newline at end of file From 3f00dbfbe839dc17b03d9044fb57f0323c2c22f1 Mon Sep 17 00:00:00 2001 From: Pete Gonzalez <4673363+octogonz@users.noreply.github.com> Date: Mon, 23 Sep 2019 23:10:41 -0700 Subject: [PATCH 2/5] singletons and collidingNames did not need to be optional parameters --- .../src/documenters/YamlDocumenter.ts | 36 +++++++++---------- 1 file changed, 17 insertions(+), 19 deletions(-) diff --git a/apps/api-documenter/src/documenters/YamlDocumenter.ts b/apps/api-documenter/src/documenters/YamlDocumenter.ts index 50174709ede..5d45d73931e 100644 --- a/apps/api-documenter/src/documenters/YamlDocumenter.ts +++ b/apps/api-documenter/src/documenters/YamlDocumenter.ts @@ -624,26 +624,24 @@ export class YamlDocumenter { private _trackCollisionsWithSiblings( apiItem: ApiItem, - singletons?: Map, - collidingNames?: Set + singletons: Map, + collidingNames: Set ): void { - if (singletons && collidingNames) { - if (!collidingNames.has(apiItem.displayName)) { - const collision: ApiItem | undefined = singletons.get(apiItem.displayName); - if (!collision) { - // No collision. Record this singleton entry. - singletons.set(apiItem.displayName, apiItem); - return; - } - // First collision. Record the colliding name. - collidingNames.add(apiItem.displayName); - singletons.delete(apiItem.displayName); - // Record the initial entry. - this._collisions.add(collision); - } - // Record the colliding entry. - this._collisions.add(apiItem); - } + if (!collidingNames.has(apiItem.displayName)) { + const collision: ApiItem | undefined = singletons.get(apiItem.displayName); + if (!collision) { + // No collision. Record this singleton entry. + singletons.set(apiItem.displayName, apiItem); + return; + } + // First collision. Record the colliding name. + collidingNames.add(apiItem.displayName); + singletons.delete(apiItem.displayName); + // Record the initial entry. + this._collisions.add(collision); + } + // Record the colliding entry. + this._collisions.add(apiItem); } private _ensureYamlReferences(): IYamlReferences { From 3e6239eeb74b4b12afb54f3fd196e26387593034 Mon Sep 17 00:00:00 2001 From: Pete Gonzalez <4673363+octogonz@users.noreply.github.com> Date: Tue, 24 Sep 2019 22:09:52 -0700 Subject: [PATCH 3/5] Add ApiItem.getMergedSiblings() API --- apps/api-extractor-model/src/items/ApiItem.ts | 19 +++++ .../src/mixins/ApiItemContainerMixin.ts | 77 ++++++++++++++++--- common/reviews/api/api-extractor-model.api.md | 3 + 3 files changed, 87 insertions(+), 12 deletions(-) diff --git a/apps/api-extractor-model/src/items/ApiItem.ts b/apps/api-extractor-model/src/items/ApiItem.ts index b70605354d2..7daf8b75630 100644 --- a/apps/api-extractor-model/src/items/ApiItem.ts +++ b/apps/api-extractor-model/src/items/ApiItem.ts @@ -7,6 +7,7 @@ import { ApiPackage } from '../model/ApiPackage'; import { ApiParameterListMixin } from '../mixins/ApiParameterListMixin'; import { DeserializerContext } from '../model/DeserializerContext'; import { InternalError } from '@microsoft/node-core-library'; +import { ApiItemContainerMixin } from '../mixins/ApiItemContainerMixin'; /** * The type returned by the {@link ApiItem.kind} property, which can be used to easily distinguish subclasses of @@ -173,6 +174,24 @@ export class ApiItem { return []; } + /** + * If this item has a name (i.e. extends `ApiNameMixin`), then return all items that have the same parent + * and the same name. Otherwise, return all items that have the same parent and the same `ApiItemKind`. + * + * @remarks + * Examples: For a function, this would return all overloads for the function. For a constructor, this would + * return all overloads for the constructor. For a merged declaration (e.g. a `namespace` and `enum` with the + * same name), this would return both declarations. If this item does not have a parent, or if it is the only + * item of its name/kind, then the result is an array containing only this item. + */ + public getMergedSiblings(): ReadonlyArray { + const parent: ApiItem | undefined = this._parent; + if (parent && ApiItemContainerMixin.isBaseClassOf(parent)) { + return parent._getMergedSiblingsForMember(this); + } + return []; + } + /** * Returns the chain of ancestors, starting from the root of the tree, and ending with the this item. */ diff --git a/apps/api-extractor-model/src/mixins/ApiItemContainerMixin.ts b/apps/api-extractor-model/src/mixins/ApiItemContainerMixin.ts index b234d6136ac..576d3e052f8 100644 --- a/apps/api-extractor-model/src/mixins/ApiItemContainerMixin.ts +++ b/apps/api-extractor-model/src/mixins/ApiItemContainerMixin.ts @@ -1,9 +1,17 @@ // Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. // See LICENSE in the project root for license information.s -import { ApiItem, ApiItem_onParentChanged, IApiItemJson, IApiItemOptions, IApiItemConstructor } from '../items/ApiItem'; +import { + ApiItem, + ApiItem_onParentChanged, + IApiItemJson, + IApiItemOptions, + IApiItemConstructor, + ApiItemKind +} from '../items/ApiItem'; import { ApiNameMixin } from './ApiNameMixin'; import { DeserializerContext } from '../model/DeserializerContext'; +import { InternalError } from '@microsoft/node-core-library'; /** * Constructor options for {@link (ApiItemContainerMixin:interface)}. @@ -21,6 +29,7 @@ const _members: unique symbol = Symbol('ApiItemContainerMixin._members'); const _membersSorted: unique symbol = Symbol('ApiItemContainerMixin._membersSorted'); const _membersByContainerKey: unique symbol = Symbol('ApiItemContainerMixin._membersByContainerKey'); const _membersByName: unique symbol = Symbol('ApiItemContainerMixin._membersByName'); +const _membersByKind: unique symbol = Symbol('ApiItemContainerMixin._membersByKind'); /** * The mixin base class for API items that act as containers for other child items. @@ -73,6 +82,12 @@ export interface ApiItemContainerMixin extends ApiItem { */ findMembersByName(name: string): ReadonlyArray; + /** + * For a given member of this container, return its `ApiItem.getMergedSiblings()` list. + * @internal + */ + _getMergedSiblingsForMember(memberApiItem: ApiItem): ReadonlyArray; + /** @override */ serializeInto(jsonObject: Partial): void; } @@ -92,8 +107,15 @@ export function ApiItemContainerMixin(ba public readonly [_members]: ApiItem[]; public [_membersSorted]: boolean; public [_membersByContainerKey]: Map; + + // For members of this container that extend ApiNameMixin, this stores the list of members with a given name. + // Examples include merged declarations, overloaded functions, etc. public [_membersByName]: Map | undefined; + // For members of this container that do NOT extend ApiNameMixin, this stores the list of members + // that share a common ApiItemKind. Examples include overloaded constructors or index signatures. + public [_membersByKind]: Map | undefined; // key is ApiItemKind + /** @override */ public static onDeserializeInto(options: Partial, context: DeserializerContext, jsonObject: IApiItemContainerJson): void { @@ -142,6 +164,7 @@ export function ApiItemContainerMixin(ba this[_members].push(member); this[_membersByName] = undefined; // invalidate the lookup + this[_membersByKind] = undefined; // invalidate the lookup this[_membersSorted] = false; this[_membersByContainerKey].set(member.containerKey, member); @@ -153,25 +176,55 @@ export function ApiItemContainerMixin(ba } public findMembersByName(name: string): ReadonlyArray { - // Build the lookup on demand + this._ensureMemberMaps(); + return this[_membersByName]!.get(name) || []; + } + + /** @internal */ + public _getMergedSiblingsForMember(memberApiItem: ApiItem): ReadonlyArray { + this._ensureMemberMaps(); + let result: ApiItem[] | undefined; + if (ApiNameMixin.isBaseClassOf(memberApiItem)) { + result = this[_membersByName]!.get(memberApiItem.name); + } else { + result = this[_membersByKind]!.get(memberApiItem.kind); + } + if (!result) { + throw new InternalError('Item was not found in the _membersByName/_membersByKind lookup'); + } + return result; + } + + /** @internal */ + public _ensureMemberMaps(): void { + // Build the _membersByName and _membersByKind tables if they don't already exist if (this[_membersByName] === undefined) { - const map: Map = new Map(); + const membersByName: Map = new Map(); + const membersByKind: Map = new Map(); for (const member of this[_members]) { + let map: Map | Map; + let key: string | ApiItemKind; + if (ApiNameMixin.isBaseClassOf(member)) { - let list: ApiItem[] | undefined = map.get(member.name); - if (list === undefined) { - list = []; - map.set(member.name, list); - } - list.push(member); + map = membersByName; + key = member.name; + } else { + map = membersByKind; + key = member.kind; } + + let list: ApiItem[] | undefined = map.get(key); + if (list === undefined) { + list = []; + map.set(key, list); + } + list.push(member); } - this[_membersByName] = map; + this[_membersByName] = membersByName; + this[_membersByKind] = membersByKind; } - - return this[_membersByName]!.get(name) || []; } /** @override */ diff --git a/common/reviews/api/api-extractor-model.api.md b/common/reviews/api/api-extractor-model.api.md index 2e4ac027605..ad0dfa0913e 100644 --- a/common/reviews/api/api-extractor-model.api.md +++ b/common/reviews/api/api-extractor-model.api.md @@ -248,6 +248,7 @@ export class ApiItem { readonly displayName: string; getAssociatedPackage(): ApiPackage | undefined; getHierarchy(): ReadonlyArray; + getMergedSiblings(): ReadonlyArray; getScopedNameWithinPackage(): string; // @virtual (undocumented) getSortKey(): string; @@ -270,6 +271,8 @@ export function ApiItemContainerMixin(ba export interface ApiItemContainerMixin extends ApiItem { addMember(member: ApiItem): void; findMembersByName(name: string): ReadonlyArray; + // @internal + _getMergedSiblingsForMember(memberApiItem: ApiItem): ReadonlyArray; readonly members: ReadonlyArray; // @override (undocumented) serializeInto(jsonObject: Partial): void; From 0e27f356fe77dc62df02a25c1622a7becfb2fd80 Mon Sep 17 00:00:00 2001 From: Pete Gonzalez <4673363+octogonz@users.noreply.github.com> Date: Tue, 24 Sep 2019 22:10:18 -0700 Subject: [PATCH 4/5] Update YamlDocumenter to use ApiItem.getMergedSiblings() API --- .../src/documenters/YamlDocumenter.ts | 35 ++----------------- 1 file changed, 3 insertions(+), 32 deletions(-) diff --git a/apps/api-documenter/src/documenters/YamlDocumenter.ts b/apps/api-documenter/src/documenters/YamlDocumenter.ts index 5d45d73931e..b91f829a2b2 100644 --- a/apps/api-documenter/src/documenters/YamlDocumenter.ts +++ b/apps/api-documenter/src/documenters/YamlDocumenter.ts @@ -75,16 +75,12 @@ export class YamlDocumenter { private _apiItemsByCanonicalReference: Map; private _yamlReferences: IYamlReferences | undefined; - // Keeps track of ApiItems whose names collide with one or more siblings. - private _collisions: Set; - private _outputFolder: string; public constructor(apiModel: ApiModel) { this._apiModel = apiModel; this._markdownEmitter = new CustomMarkdownEmitter(this._apiModel); this._apiItemsByCanonicalReference = new Map(); - this._collisions = new Set(); this._initApiItems(); } @@ -274,7 +270,7 @@ export class YamlDocumenter { name = PackageName.getUnscopedName(name); } - if (this._collisions.has(apiItem)) { + if (apiItem.getMergedSiblings().length > 1) { name += ` (${apiItem.kind})`; } @@ -613,37 +609,12 @@ export class YamlDocumenter { // Recurse container members if (ApiItemContainerMixin.isBaseClassOf(apiItem)) { - const singletons: Map = new Map(); - const collidingNames: Set = new Set(); for (const apiMember of apiItem.members) { - this._trackCollisionsWithSiblings(apiMember, singletons, collidingNames); this._initApiItemsRecursive(apiMember); } } } - private _trackCollisionsWithSiblings( - apiItem: ApiItem, - singletons: Map, - collidingNames: Set - ): void { - if (!collidingNames.has(apiItem.displayName)) { - const collision: ApiItem | undefined = singletons.get(apiItem.displayName); - if (!collision) { - // No collision. Record this singleton entry. - singletons.set(apiItem.displayName, apiItem); - return; - } - // First collision. Record the colliding name. - collidingNames.add(apiItem.displayName); - singletons.delete(apiItem.displayName); - // Record the initial entry. - this._collisions.add(collision); - } - // Record the colliding entry. - this._collisions.add(apiItem); - } - private _ensureYamlReferences(): IYamlReferences { if (!this._yamlReferences) { this._yamlReferences = { @@ -839,7 +810,7 @@ export class YamlDocumenter { } let disambiguator: string = ''; - if (this._collisions.has(apiItem)) { + if (apiItem.getMergedSiblings().length > 1) { disambiguator = `-${apiItem.kind.toLowerCase()}`; } @@ -850,4 +821,4 @@ export class YamlDocumenter { console.log('Deleting old output from ' + this._outputFolder); FileSystem.ensureEmptyFolder(this._outputFolder); } -} \ No newline at end of file +} From 6ceb4bdeeac65cdc48d78197d1cbea273e170099 Mon Sep 17 00:00:00 2001 From: Pete Gonzalez <4673363+octogonz@users.noreply.github.com> Date: Tue, 24 Sep 2019 22:11:16 -0700 Subject: [PATCH 5/5] rush change --- .../disambiguateFiles_2019-09-25-05-10.json | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 common/changes/@microsoft/api-extractor-model/disambiguateFiles_2019-09-25-05-10.json diff --git a/common/changes/@microsoft/api-extractor-model/disambiguateFiles_2019-09-25-05-10.json b/common/changes/@microsoft/api-extractor-model/disambiguateFiles_2019-09-25-05-10.json new file mode 100644 index 00000000000..df8871f3074 --- /dev/null +++ b/common/changes/@microsoft/api-extractor-model/disambiguateFiles_2019-09-25-05-10.json @@ -0,0 +1,11 @@ +{ + "changes": [ + { + "packageName": "@microsoft/api-extractor-model", + "comment": "Add ApiItem.getMergedSiblings() API", + "type": "minor" + } + ], + "packageName": "@microsoft/api-extractor-model", + "email": "4673363+octogonz@users.noreply.github.com" +} \ No newline at end of file