Skip to content

Commit

Permalink
Merge pull request #1536 from rbuckton/disambiguateFiles
Browse files Browse the repository at this point in the history
[api-documenter] Disambiguate output files with colliding names
  • Loading branch information
octogonz authored Sep 25, 2019
2 parents 036f4dc + 6ceb4bd commit 7864665
Show file tree
Hide file tree
Showing 16 changed files with 240 additions and 40 deletions.
19 changes: 7 additions & 12 deletions apps/api-documenter/src/documenters/ExperimentalYamlDocumenter.ts
Original file line number Diff line number Diff line change
@@ -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';

Expand Down Expand Up @@ -42,24 +41,20 @@ 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)) {
// Don't generate table of contents items for embedded definitions
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);
}
}
Expand Down
43 changes: 27 additions & 16 deletions apps/api-documenter/src/documenters/YamlDocumenter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,6 @@ export class YamlDocumenter {

private _apiItemsByCanonicalReference: Map<string, ApiItem>;
private _yamlReferences: IYamlReferences | undefined;

private _outputFolder: string;

public constructor(apiModel: ApiModel) {
Expand Down Expand Up @@ -232,25 +231,18 @@ 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)) {
// Don't generate table of contents items for embedded definitions
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);
Expand All @@ -271,6 +263,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 (apiItem.getMergedSiblings().length > 1) {
name += ` (${apiItem.kind})`;
}

return name;
}

protected _shouldEmbed(apiItemKind: ApiItemKind): boolean {
switch (apiItemKind) {
case ApiItemKind.Class:
Expand Down Expand Up @@ -591,7 +597,6 @@ export class YamlDocumenter {
*/
private _initApiItems(): void {
this._initApiItemsRecursive(this._apiModel);

}

/**
Expand Down Expand Up @@ -803,11 +808,17 @@ export class YamlDocumenter {
break;
}
}
return path.join(this._outputFolder, result + '.yml');

let disambiguator: string = '';
if (apiItem.getMergedSiblings().length > 1) {
disambiguator = `-${apiItem.kind.toLowerCase()}`;
}

return path.join(this._outputFolder, result + disambiguator + '.yml');
}

private _deleteOldOutputFiles(): void {
console.log('Deleting old output from ' + this._outputFolder);
FileSystem.ensureEmptyFolder(this._outputFolder);
}
}
}
19 changes: 19 additions & 0 deletions apps/api-extractor-model/src/items/ApiItem.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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<ApiItem> {
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.
*/
Expand Down
77 changes: 65 additions & 12 deletions apps/api-extractor-model/src/mixins/ApiItemContainerMixin.ts
Original file line number Diff line number Diff line change
@@ -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)}.
Expand All @@ -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.
Expand Down Expand Up @@ -73,6 +82,12 @@ export interface ApiItemContainerMixin extends ApiItem {
*/
findMembersByName(name: string): ReadonlyArray<ApiItem>;

/**
* For a given member of this container, return its `ApiItem.getMergedSiblings()` list.
* @internal
*/
_getMergedSiblingsForMember(memberApiItem: ApiItem): ReadonlyArray<ApiItem>;

/** @override */
serializeInto(jsonObject: Partial<IApiItemJson>): void;
}
Expand All @@ -92,8 +107,15 @@ export function ApiItemContainerMixin<TBaseClass extends IApiItemConstructor>(ba
public readonly [_members]: ApiItem[];
public [_membersSorted]: boolean;
public [_membersByContainerKey]: Map<string, ApiItem>;

// 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<string, ApiItem[]> | 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<string, ApiItem[]> | undefined; // key is ApiItemKind

/** @override */
public static onDeserializeInto(options: Partial<IApiItemContainerMixinOptions>,
context: DeserializerContext, jsonObject: IApiItemContainerJson): void {
Expand Down Expand Up @@ -142,6 +164,7 @@ export function ApiItemContainerMixin<TBaseClass extends IApiItemConstructor>(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);

Expand All @@ -153,25 +176,55 @@ export function ApiItemContainerMixin<TBaseClass extends IApiItemConstructor>(ba
}

public findMembersByName(name: string): ReadonlyArray<ApiItem> {
// Build the lookup on demand
this._ensureMemberMaps();
return this[_membersByName]!.get(name) || [];
}

/** @internal */
public _getMergedSiblingsForMember(memberApiItem: ApiItem): ReadonlyArray<ApiItem> {
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<string, ApiItem[]> = new Map<string, ApiItem[]>();
const membersByName: Map<string, ApiItem[]> = new Map<string, ApiItem[]>();
const membersByKind: Map<string, ApiItem[]> = new Map<string, ApiItem[]>();

for (const member of this[_members]) {
let map: Map<string, ApiItem[]> | Map<ApiItemKind, ApiItem[]>;
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 */
Expand Down
30 changes: 30 additions & 0 deletions build-tests/api-documenter-test/etc/api-documenter-test.api.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->

[Home](./index.md) &gt; [api-documenter-test](./api-documenter-test.md) &gt; [DocClassInterfaceMerge](./api-documenter-test.docclassinterfacemerge.md)

## DocClassInterfaceMerge interface

Interface that merges with class

<b>Signature:</b>

```typescript
export interface DocClassInterfaceMerge
```
Original file line number Diff line number Diff line change
Expand Up @@ -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. |

Expand All @@ -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. |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)'
Expand Down Expand Up @@ -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'
Expand Down
Loading

0 comments on commit 7864665

Please sign in to comment.