Skip to content

Commit a432cab

Browse files
committed
Disambiguate output files with colliding names
1 parent 7b3488c commit a432cab

File tree

12 files changed

+171
-26
lines changed

12 files changed

+171
-26
lines changed

apps/api-documenter/src/documenters/ExperimentalYamlDocumenter.ts

Lines changed: 7 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
22
// See LICENSE in the project root for license information.
33

4-
import { PackageName } from '@microsoft/node-core-library';
54
import { DocComment, DocInlineTag } from '@microsoft/tsdoc';
65
import { ApiModel, ApiItem, ApiItemKind, ApiDocumentedItem } from '@microsoft/api-extractor-model';
76

@@ -42,24 +41,20 @@ export class ExperimentalYamlDocumenter extends YamlDocumenter {
4241
if (apiItem.kind === ApiItemKind.Namespace) {
4342
// Namespaces don't have nodes yet
4443
tocItem = {
45-
name: apiItem.displayName
44+
name: this._getTocItemName(apiItem)
4645
};
4746
} else {
4847
if (this._shouldEmbed(apiItem.kind)) {
4948
// Don't generate table of contents items for embedded definitions
5049
continue;
5150
}
5251

53-
if (apiItem.kind === ApiItemKind.Package) {
54-
tocItem = {
55-
name: PackageName.getUnscopedName(apiItem.displayName),
56-
uid: this._getUid(apiItem)
57-
};
58-
} else {
59-
tocItem = {
60-
name: apiItem.displayName,
61-
uid: this._getUid(apiItem)
62-
};
52+
tocItem = {
53+
name: this._getTocItemName(apiItem),
54+
uid: this._getUid(apiItem)
55+
};
56+
57+
if (apiItem.kind !== ApiItemKind.Package) {
6358
this._filterItem(apiItem, tocItem);
6459
}
6560
}

apps/api-documenter/src/documenters/YamlDocumenter.ts

Lines changed: 56 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -75,13 +75,16 @@ export class YamlDocumenter {
7575

7676
private _apiItemsByCanonicalReference: Map<string, ApiItem>;
7777
private _yamlReferences: IYamlReferences | undefined;
78+
// Keeps track of ApiItems whose names collide with one or more siblings.
79+
private _collisions: Set<ApiItem>;
7880

7981
private _outputFolder: string;
8082

8183
public constructor(apiModel: ApiModel) {
8284
this._apiModel = apiModel;
8385
this._markdownEmitter = new CustomMarkdownEmitter(this._apiModel);
8486
this._apiItemsByCanonicalReference = new Map<string, ApiItem>();
87+
this._collisions = new Set<ApiItem>();
8588

8689
this._initApiItems();
8790
}
@@ -232,25 +235,18 @@ export class YamlDocumenter {
232235
if (apiItem.kind === ApiItemKind.Namespace) {
233236
// Namespaces don't have nodes yet
234237
tocItem = {
235-
name: apiItem.displayName
238+
name: this._getTocItemName(apiItem)
236239
};
237240
} else {
238241
if (this._shouldEmbed(apiItem.kind)) {
239242
// Don't generate table of contents items for embedded definitions
240243
continue;
241244
}
242245

243-
if (apiItem.kind === ApiItemKind.Package) {
244-
tocItem = {
245-
name: PackageName.getUnscopedName(apiItem.displayName),
246-
uid: this._getUid(apiItem)
247-
};
248-
} else {
249-
tocItem = {
250-
name: apiItem.displayName,
251-
uid: this._getUid(apiItem)
252-
};
253-
}
246+
tocItem = {
247+
name: this._getTocItemName(apiItem),
248+
uid: this._getUid(apiItem)
249+
};
254250
}
255251

256252
tocItems.push(tocItem);
@@ -271,6 +267,20 @@ export class YamlDocumenter {
271267
return tocItems;
272268
}
273269

270+
/** @virtual */
271+
protected _getTocItemName(apiItem: ApiItem): string {
272+
let name: string = apiItem.displayName;
273+
if (apiItem.kind === ApiItemKind.Package) {
274+
name = PackageName.getUnscopedName(name);
275+
}
276+
277+
if (this._collisions.has(apiItem)) {
278+
name += ` (${apiItem.kind})`;
279+
}
280+
281+
return name;
282+
}
283+
274284
protected _shouldEmbed(apiItemKind: ApiItemKind): boolean {
275285
switch (apiItemKind) {
276286
case ApiItemKind.Class:
@@ -591,7 +601,6 @@ export class YamlDocumenter {
591601
*/
592602
private _initApiItems(): void {
593603
this._initApiItemsRecursive(this._apiModel);
594-
595604
}
596605

597606
/**
@@ -604,12 +613,39 @@ export class YamlDocumenter {
604613

605614
// Recurse container members
606615
if (ApiItemContainerMixin.isBaseClassOf(apiItem)) {
616+
const singletons: Map<string, ApiItem> = new Map<string, ApiItem>();
617+
const collidingNames: Set<string> = new Set<string>();
607618
for (const apiMember of apiItem.members) {
619+
this._trackCollisionsWithSiblings(apiMember, singletons, collidingNames);
608620
this._initApiItemsRecursive(apiMember);
609621
}
610622
}
611623
}
612624

625+
private _trackCollisionsWithSiblings(
626+
apiItem: ApiItem,
627+
singletons?: Map<string, ApiItem>,
628+
collidingNames?: Set<string>
629+
): void {
630+
if (singletons && collidingNames) {
631+
if (!collidingNames.has(apiItem.displayName)) {
632+
const collision: ApiItem | undefined = singletons.get(apiItem.displayName);
633+
if (!collision) {
634+
// No collision. Record this singleton entry.
635+
singletons.set(apiItem.displayName, apiItem);
636+
return;
637+
}
638+
// First collision. Record the colliding name.
639+
collidingNames.add(apiItem.displayName);
640+
singletons.delete(apiItem.displayName);
641+
// Record the initial entry.
642+
this._collisions.add(collision);
643+
}
644+
// Record the colliding entry.
645+
this._collisions.add(apiItem);
646+
}
647+
}
648+
613649
private _ensureYamlReferences(): IYamlReferences {
614650
if (!this._yamlReferences) {
615651
this._yamlReferences = {
@@ -803,7 +839,13 @@ export class YamlDocumenter {
803839
break;
804840
}
805841
}
806-
return path.join(this._outputFolder, result + '.yml');
842+
843+
let disambiguator: string = '';
844+
if (this._collisions.has(apiItem)) {
845+
disambiguator = `-${apiItem.kind.toLowerCase()}`;
846+
}
847+
848+
return path.join(this._outputFolder, result + disambiguator + '.yml');
807849
}
808850

809851
private _deleteOldOutputFiles(): void {

build-tests/api-documenter-test/etc/api-documenter-test.api.json

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -483,6 +483,36 @@
483483
}
484484
]
485485
},
486+
{
487+
"kind": "Class",
488+
"canonicalReference": "api-documenter-test!DocClassInterfaceMerge:class",
489+
"docComment": "/**\n * Class that merges with interface\n *\n * @public\n */\n",
490+
"excerptTokens": [
491+
{
492+
"kind": "Content",
493+
"text": "export declare class DocClassInterfaceMerge "
494+
}
495+
],
496+
"releaseTag": "Public",
497+
"name": "DocClassInterfaceMerge",
498+
"members": [],
499+
"implementsTokenRanges": []
500+
},
501+
{
502+
"kind": "Interface",
503+
"canonicalReference": "api-documenter-test!DocClassInterfaceMerge:interface",
504+
"docComment": "/**\n * Interface that merges with class\n *\n * @public\n */\n",
505+
"excerptTokens": [
506+
{
507+
"kind": "Content",
508+
"text": "export interface DocClassInterfaceMerge "
509+
}
510+
],
511+
"releaseTag": "Public",
512+
"name": "DocClassInterfaceMerge",
513+
"members": [],
514+
"extendsTokenRanges": []
515+
},
486516
{
487517
"kind": "Enum",
488518
"canonicalReference": "api-documenter-test!DocEnum:enum",

build-tests/api-documenter-test/etc/api-documenter-test.api.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,14 @@ export class DocClass1 extends DocBaseClass implements IDocInterface1, IDocInter
3131
tableExample(): void;
3232
}
3333

34+
// @public
35+
export class DocClassInterfaceMerge {
36+
}
37+
38+
// @public
39+
export interface DocClassInterfaceMerge {
40+
}
41+
3442
// @public
3543
export enum DocEnum {
3644
One = 1,
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
2+
3+
[Home](./index.md) &gt; [api-documenter-test](./api-documenter-test.md) &gt; [DocClassInterfaceMerge](./api-documenter-test.docclassinterfacemerge.md)
4+
5+
## DocClassInterfaceMerge interface
6+
7+
Interface that merges with class
8+
9+
<b>Signature:</b>
10+
11+
```typescript
12+
export interface DocClassInterfaceMerge
13+
```

build-tests/api-documenter-test/etc/markdown/api-documenter-test.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ This project tests various documentation generation scenarios and doc comment sy
1414
| --- | --- |
1515
| [DocBaseClass](./api-documenter-test.docbaseclass.md) | Example base class |
1616
| [DocClass1](./api-documenter-test.docclass1.md) | This is an example class. |
17+
| [DocClassInterfaceMerge](./api-documenter-test.docclassinterfacemerge.md) | Class that merges with interface |
1718
| [Generic](./api-documenter-test.generic.md) | Generic class. |
1819
| [SystemEvent](./api-documenter-test.systemevent.md) | A class used to exposed events. |
1920

@@ -34,6 +35,7 @@ This project tests various documentation generation scenarios and doc comment sy
3435

3536
| Interface | Description |
3637
| --- | --- |
38+
| [DocClassInterfaceMerge](./api-documenter-test.docclassinterfacemerge.md) | Interface that merges with class |
3739
| [IDocInterface1](./api-documenter-test.idocinterface1.md) | |
3840
| [IDocInterface2](./api-documenter-test.idocinterface2.md) | |
3941
| [IDocInterface3](./api-documenter-test.idocinterface3.md) | Some less common TypeScript declaration kinds. |

build-tests/api-documenter-test/etc/yaml/api-documenter-test.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ items:
1313
children:
1414
- 'api-documenter-test!DocBaseClass:class'
1515
- 'api-documenter-test!DocClass1:class'
16+
- 'api-documenter-test!DocClassInterfaceMerge:class'
17+
- 'api-documenter-test!DocClassInterfaceMerge:interface'
1618
- 'api-documenter-test!DocEnum:enum'
1719
- 'api-documenter-test!Generic:class'
1820
- 'api-documenter-test!globalFunction:function(1)'
@@ -78,6 +80,10 @@ references:
7880
name: DocBaseClass
7981
- uid: 'api-documenter-test!DocClass1:class'
8082
name: DocClass1
83+
- uid: 'api-documenter-test!DocClassInterfaceMerge:class'
84+
name: DocClassInterfaceMerge
85+
- uid: 'api-documenter-test!DocClassInterfaceMerge:interface'
86+
name: DocClassInterfaceMerge
8187
- uid: 'api-documenter-test!DocEnum:enum'
8288
name: DocEnum
8389
- uid: 'api-documenter-test!Generic:class'
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
### YamlMime:UniversalReference
2+
items:
3+
- uid: 'api-documenter-test!DocClassInterfaceMerge:class'
4+
summary: Class that merges with interface
5+
name: DocClassInterfaceMerge
6+
fullName: DocClassInterfaceMerge
7+
langs:
8+
- typeScript
9+
type: class
10+
package: api-documenter-test!
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
### YamlMime:UniversalReference
2+
items:
3+
- uid: 'api-documenter-test!DocClassInterfaceMerge:interface'
4+
summary: Interface that merges with class
5+
name: DocClassInterfaceMerge
6+
fullName: DocClassInterfaceMerge
7+
langs:
8+
- typeScript
9+
type: interface
10+
package: api-documenter-test!

build-tests/api-documenter-test/etc/yaml/toc.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,10 @@ items:
3939
items:
4040
- name: InjectedCustomItem
4141
uid: customUrl
42+
- name: DocClassInterfaceMerge (Class)
43+
uid: 'api-documenter-test!DocClassInterfaceMerge:class'
44+
- name: DocClassInterfaceMerge (Interface)
45+
uid: 'api-documenter-test!DocClassInterfaceMerge:interface'
4246
- name: DocEnum
4347
uid: 'api-documenter-test!DocEnum:enum'
4448
- name: Generic

0 commit comments

Comments
 (0)