Skip to content

Commit 3e6239e

Browse files
committed
Add ApiItem.getMergedSiblings() API
1 parent 3f00dbf commit 3e6239e

3 files changed

Lines changed: 87 additions & 12 deletions

File tree

apps/api-extractor-model/src/items/ApiItem.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { ApiPackage } from '../model/ApiPackage';
77
import { ApiParameterListMixin } from '../mixins/ApiParameterListMixin';
88
import { DeserializerContext } from '../model/DeserializerContext';
99
import { InternalError } from '@microsoft/node-core-library';
10+
import { ApiItemContainerMixin } from '../mixins/ApiItemContainerMixin';
1011

1112
/**
1213
* 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 {
173174
return [];
174175
}
175176

177+
/**
178+
* If this item has a name (i.e. extends `ApiNameMixin`), then return all items that have the same parent
179+
* and the same name. Otherwise, return all items that have the same parent and the same `ApiItemKind`.
180+
*
181+
* @remarks
182+
* Examples: For a function, this would return all overloads for the function. For a constructor, this would
183+
* return all overloads for the constructor. For a merged declaration (e.g. a `namespace` and `enum` with the
184+
* same name), this would return both declarations. If this item does not have a parent, or if it is the only
185+
* item of its name/kind, then the result is an array containing only this item.
186+
*/
187+
public getMergedSiblings(): ReadonlyArray<ApiItem> {
188+
const parent: ApiItem | undefined = this._parent;
189+
if (parent && ApiItemContainerMixin.isBaseClassOf(parent)) {
190+
return parent._getMergedSiblingsForMember(this);
191+
}
192+
return [];
193+
}
194+
176195
/**
177196
* Returns the chain of ancestors, starting from the root of the tree, and ending with the this item.
178197
*/

apps/api-extractor-model/src/mixins/ApiItemContainerMixin.ts

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

4-
import { ApiItem, ApiItem_onParentChanged, IApiItemJson, IApiItemOptions, IApiItemConstructor } from '../items/ApiItem';
4+
import {
5+
ApiItem,
6+
ApiItem_onParentChanged,
7+
IApiItemJson,
8+
IApiItemOptions,
9+
IApiItemConstructor,
10+
ApiItemKind
11+
} from '../items/ApiItem';
512
import { ApiNameMixin } from './ApiNameMixin';
613
import { DeserializerContext } from '../model/DeserializerContext';
14+
import { InternalError } from '@microsoft/node-core-library';
715

816
/**
917
* Constructor options for {@link (ApiItemContainerMixin:interface)}.
@@ -21,6 +29,7 @@ const _members: unique symbol = Symbol('ApiItemContainerMixin._members');
2129
const _membersSorted: unique symbol = Symbol('ApiItemContainerMixin._membersSorted');
2230
const _membersByContainerKey: unique symbol = Symbol('ApiItemContainerMixin._membersByContainerKey');
2331
const _membersByName: unique symbol = Symbol('ApiItemContainerMixin._membersByName');
32+
const _membersByKind: unique symbol = Symbol('ApiItemContainerMixin._membersByKind');
2433

2534
/**
2635
* The mixin base class for API items that act as containers for other child items.
@@ -73,6 +82,12 @@ export interface ApiItemContainerMixin extends ApiItem {
7382
*/
7483
findMembersByName(name: string): ReadonlyArray<ApiItem>;
7584

85+
/**
86+
* For a given member of this container, return its `ApiItem.getMergedSiblings()` list.
87+
* @internal
88+
*/
89+
_getMergedSiblingsForMember(memberApiItem: ApiItem): ReadonlyArray<ApiItem>;
90+
7691
/** @override */
7792
serializeInto(jsonObject: Partial<IApiItemJson>): void;
7893
}
@@ -92,8 +107,15 @@ export function ApiItemContainerMixin<TBaseClass extends IApiItemConstructor>(ba
92107
public readonly [_members]: ApiItem[];
93108
public [_membersSorted]: boolean;
94109
public [_membersByContainerKey]: Map<string, ApiItem>;
110+
111+
// For members of this container that extend ApiNameMixin, this stores the list of members with a given name.
112+
// Examples include merged declarations, overloaded functions, etc.
95113
public [_membersByName]: Map<string, ApiItem[]> | undefined;
96114

115+
// For members of this container that do NOT extend ApiNameMixin, this stores the list of members
116+
// that share a common ApiItemKind. Examples include overloaded constructors or index signatures.
117+
public [_membersByKind]: Map<string, ApiItem[]> | undefined; // key is ApiItemKind
118+
97119
/** @override */
98120
public static onDeserializeInto(options: Partial<IApiItemContainerMixinOptions>,
99121
context: DeserializerContext, jsonObject: IApiItemContainerJson): void {
@@ -142,6 +164,7 @@ export function ApiItemContainerMixin<TBaseClass extends IApiItemConstructor>(ba
142164

143165
this[_members].push(member);
144166
this[_membersByName] = undefined; // invalidate the lookup
167+
this[_membersByKind] = undefined; // invalidate the lookup
145168
this[_membersSorted] = false;
146169
this[_membersByContainerKey].set(member.containerKey, member);
147170

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

155178
public findMembersByName(name: string): ReadonlyArray<ApiItem> {
156-
// Build the lookup on demand
179+
this._ensureMemberMaps();
180+
return this[_membersByName]!.get(name) || [];
181+
}
182+
183+
/** @internal */
184+
public _getMergedSiblingsForMember(memberApiItem: ApiItem): ReadonlyArray<ApiItem> {
185+
this._ensureMemberMaps();
186+
let result: ApiItem[] | undefined;
187+
if (ApiNameMixin.isBaseClassOf(memberApiItem)) {
188+
result = this[_membersByName]!.get(memberApiItem.name);
189+
} else {
190+
result = this[_membersByKind]!.get(memberApiItem.kind);
191+
}
192+
if (!result) {
193+
throw new InternalError('Item was not found in the _membersByName/_membersByKind lookup');
194+
}
195+
return result;
196+
}
197+
198+
/** @internal */
199+
public _ensureMemberMaps(): void {
200+
// Build the _membersByName and _membersByKind tables if they don't already exist
157201
if (this[_membersByName] === undefined) {
158-
const map: Map<string, ApiItem[]> = new Map<string, ApiItem[]>();
202+
const membersByName: Map<string, ApiItem[]> = new Map<string, ApiItem[]>();
203+
const membersByKind: Map<string, ApiItem[]> = new Map<string, ApiItem[]>();
159204

160205
for (const member of this[_members]) {
206+
let map: Map<string, ApiItem[]> | Map<ApiItemKind, ApiItem[]>;
207+
let key: string | ApiItemKind;
208+
161209
if (ApiNameMixin.isBaseClassOf(member)) {
162-
let list: ApiItem[] | undefined = map.get(member.name);
163-
if (list === undefined) {
164-
list = [];
165-
map.set(member.name, list);
166-
}
167-
list.push(member);
210+
map = membersByName;
211+
key = member.name;
212+
} else {
213+
map = membersByKind;
214+
key = member.kind;
168215
}
216+
217+
let list: ApiItem[] | undefined = map.get(key);
218+
if (list === undefined) {
219+
list = [];
220+
map.set(key, list);
221+
}
222+
list.push(member);
169223
}
170224

171-
this[_membersByName] = map;
225+
this[_membersByName] = membersByName;
226+
this[_membersByKind] = membersByKind;
172227
}
173-
174-
return this[_membersByName]!.get(name) || [];
175228
}
176229

177230
/** @override */

common/reviews/api/api-extractor-model.api.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,7 @@ export class ApiItem {
248248
readonly displayName: string;
249249
getAssociatedPackage(): ApiPackage | undefined;
250250
getHierarchy(): ReadonlyArray<ApiItem>;
251+
getMergedSiblings(): ReadonlyArray<ApiItem>;
251252
getScopedNameWithinPackage(): string;
252253
// @virtual (undocumented)
253254
getSortKey(): string;
@@ -270,6 +271,8 @@ export function ApiItemContainerMixin<TBaseClass extends IApiItemConstructor>(ba
270271
export interface ApiItemContainerMixin extends ApiItem {
271272
addMember(member: ApiItem): void;
272273
findMembersByName(name: string): ReadonlyArray<ApiItem>;
274+
// @internal
275+
_getMergedSiblingsForMember(memberApiItem: ApiItem): ReadonlyArray<ApiItem>;
273276
readonly members: ReadonlyArray<ApiItem>;
274277
// @override (undocumented)
275278
serializeInto(jsonObject: Partial<IApiItemJson>): void;

0 commit comments

Comments
 (0)