Skip to content

Commit

Permalink
Add ApiItem.getMergedSiblings() API
Browse files Browse the repository at this point in the history
  • Loading branch information
octogonz committed Sep 25, 2019
1 parent 3f00dbf commit 3e6239e
Show file tree
Hide file tree
Showing 3 changed files with 87 additions and 12 deletions.
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
3 changes: 3 additions & 0 deletions common/reviews/api/api-extractor-model.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,7 @@ export class ApiItem {
readonly displayName: string;
getAssociatedPackage(): ApiPackage | undefined;
getHierarchy(): ReadonlyArray<ApiItem>;
getMergedSiblings(): ReadonlyArray<ApiItem>;
getScopedNameWithinPackage(): string;
// @virtual (undocumented)
getSortKey(): string;
Expand All @@ -270,6 +271,8 @@ export function ApiItemContainerMixin<TBaseClass extends IApiItemConstructor>(ba
export interface ApiItemContainerMixin extends ApiItem {
addMember(member: ApiItem): void;
findMembersByName(name: string): ReadonlyArray<ApiItem>;
// @internal
_getMergedSiblingsForMember(memberApiItem: ApiItem): ReadonlyArray<ApiItem>;
readonly members: ReadonlyArray<ApiItem>;
// @override (undocumented)
serializeInto(jsonObject: Partial<IApiItemJson>): void;
Expand Down

0 comments on commit 3e6239e

Please sign in to comment.