Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
05ea790
refactor: Remove helper and improve typing of another
Josmithr Sep 16, 2025
30cbc79
feat(api-markdown-documenter): Class and interface items now inherit …
Josmithr Sep 16, 2025
a6fb1c0
refactor: Add utilities dir
Josmithr Sep 16, 2025
8342b3d
refactor: Move API item utils into directory
Josmithr Sep 16, 2025
c330692
refactor: Move test module
Josmithr Sep 16, 2025
1f4253b
Merge branch 'api-markdown-documenter/reorganize-utilities' into api-…
Josmithr Sep 16, 2025
29e77e8
refactor: Extract type inheritance utilities into their own module
Josmithr Sep 16, 2025
987894d
docs: Add module comment
Josmithr Sep 16, 2025
9b56a70
docs: TODOs
Josmithr Sep 16, 2025
4b3062e
refactor: Simplify section creation
Josmithr Sep 16, 2025
14e5840
refactor: Don't export helpers
Josmithr Sep 16, 2025
3c14079
refactor: Add new table creation utils
Josmithr Sep 16, 2025
8315d09
refactor: Use new table creation utils
Josmithr Sep 16, 2025
ca8f5e5
refactor: Use new table creation utils
Josmithr Sep 16, 2025
b9ec5f1
fix: `align` handling
Josmithr Sep 16, 2025
17b7641
refactor: More consistent table handling
Josmithr Sep 16, 2025
5569cc9
refactor: Make table creation utilities more general
Josmithr Sep 16, 2025
c788295
refactor: Use new table creation utils
Josmithr Sep 16, 2025
29d2fbf
refactor: Use new table creation utils
Josmithr Sep 16, 2025
5026b75
refactor: Use new table creation utils
Josmithr Sep 16, 2025
ba389c5
refactor: Rename utility function
Josmithr Sep 16, 2025
69e5b9e
refactor: Don't export functions
Josmithr Sep 16, 2025
f56c2cd
refactor: Use new table creation utility
Josmithr Sep 16, 2025
a55eb7e
refactor: Rename utility function
Josmithr Sep 16, 2025
b732f78
refactor: Cleanup
Josmithr Sep 16, 2025
262071f
Merge branch 'main' into api-markdown-documenter/improve-table-helpers
Josmithr Sep 16, 2025
bf26110
Merge branch 'main' into api-markdown-documenter/inherit-members
Josmithr Sep 16, 2025
88b2168
Merge branch 'api-markdown-documenter/improve-table-helpers' into api…
Josmithr Sep 16, 2025
7ead26b
bump: Version for publishing
Josmithr Sep 16, 2025
b76f140
refactor: Fix typo
Josmithr Sep 16, 2025
1dfda98
fix: Don't generate empty cells
Josmithr Sep 16, 2025
9ea079f
Merge branch 'api-markdown-documenter/improve-table-helpers' into api…
Josmithr Sep 16, 2025
a936ea8
docs: Add comments
Josmithr Sep 16, 2025
8939d41
docs: Add property docs
Josmithr Sep 16, 2025
4a2f6cd
fix: Inheritance resolution
Josmithr Sep 17, 2025
94ead60
docs: TODO
Josmithr Sep 17, 2025
99f8928
fix: Don't inherit static members
Josmithr Sep 17, 2025
8796129
refactor: Add new utility type
Josmithr Sep 17, 2025
c4bcfe9
refactor: Extract function
Josmithr Sep 17, 2025
22835c6
Merge branch 'main' into api-markdown-documenter/inherit-members
Josmithr Sep 17, 2025
a8896cc
docs: TODOs
Josmithr Sep 17, 2025
9d2ac4b
refactor: Use explicit table helpers
Josmithr Sep 17, 2025
4b11b21
Merge branch 'main' into api-markdown-documenter/inherit-members
Josmithr Sep 17, 2025
e697a54
WIP: Display more information
Josmithr Sep 17, 2025
1e3c2f1
refactor: Rename property
Josmithr Sep 17, 2025
448ef69
refactor: Cleanup
Josmithr Sep 17, 2025
8a14cd2
refactor: Extract function
Josmithr Sep 17, 2025
4134f1e
Merge branch 'main' into api-markdown-documenter/inherit-members
Josmithr Sep 17, 2025
68fcc03
Merge branch 'main' into api-markdown-documenter/inherit-members
Josmithr Sep 18, 2025
fb120a5
fix: Table column labeling
Josmithr Sep 18, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import {
} from "@microsoft/api-extractor-model";
import type { DocSection } from "@microsoft/tsdoc";
import { toHtml } from "hast-util-to-html";
import type { Html, PhrasingContent, Table, TableCell } from "mdast";
import type { BlockContent, Html, PhrasingContent, Table, TableCell } from "mdast";
import { toHast } from "mdast-util-to-hast";

import type { Section } from "../../mdast/index.js";
Expand Down Expand Up @@ -564,7 +564,7 @@ export function createPackagesTable(
* @param apiItem - The API item whose comment will be rendered in the cell.
* @param config - See {@link ApiItemTransformationConfiguration}.
*/
function createDescriptionCell(
export function createDescriptionCell(
apiItem: ApiItem,
config: ApiItemTransformationConfiguration,
): TableCell | undefined {
Expand All @@ -588,7 +588,7 @@ function createDescriptionCell(
* will point.
* @param config - See {@link ApiItemTransformationConfiguration}.
*/
function createNameCell(
export function createNameCell(
apiItem: ApiItem,
config: ApiItemTransformationConfiguration,
): TableCell {
Expand All @@ -605,7 +605,7 @@ function createNameCell(
* @param apiItem - The API item whose modifiers will be displayed in the cell.
* @param modifiersToOmit - List of modifiers to omit from the generated cell, even if they apply to the item.
*/
function createModifiersCell(
export function createModifiersCell(
apiItem: ApiItem,
modifiersToOmit?: ApiModifier[],
): TableCell | undefined {
Expand Down Expand Up @@ -635,7 +635,7 @@ function createModifiersCell(
* @param apiItem - The alert values to display.
* @param config - See {@link ApiItemTransformationConfiguration}.
*/
function createAlertsCell(alerts: string[]): TableCell | undefined {
export function createAlertsCell(alerts: string[]): TableCell | undefined {
const alertNodes: PhrasingContent[] = alerts.map((alert) => ({
type: "inlineCode",
value: alert,
Expand Down Expand Up @@ -716,7 +716,7 @@ function createTypeParameterSummaryCell(
* @param typeExcerpt - An excerpt describing the type to be displayed in the cell.
* @param config - See {@link ApiItemTransformationConfiguration}.
*/
function createTypeExcerptCell(
export function createTypeExcerptCell(
typeExcerpt: Excerpt,
config: ApiItemTransformationConfiguration,
): TableCell | undefined {
Expand Down Expand Up @@ -765,29 +765,41 @@ function getTableHeadingTitleForApiKind(itemKind: ApiItemKind): string {
* Notably, this optimizes away the generation of paragraph nodes around inner contents when there is only a
* single paragraph.
*/
function createTableCellFromTsdocSection(
export function createTableCellFromTsdocSection(
tsdocSection: DocSection,
contextApiItem: ApiItem,
config: ApiItemTransformationConfiguration,
): TableCell | undefined {
const transformed = transformTsdoc(tsdocSection, contextApiItem, config);
return createTableCellFromBlockContent(transformed);
}

if (transformed.length === 0) {
/**
* Transforms the contents of a TSDoc section node, and fine-tunes the output for use in a table cell.
*
* @remarks
* Notably, this optimizes away the generation of paragraph nodes around inner contents when there is only a
* single paragraph.
*/
function createTableCellFromBlockContent(
blockContent: readonly BlockContent[],
): TableCell | undefined {
if (blockContent.length === 0) {
return undefined;
}

// If the transformed contents consist of a single paragraph (common case), inline that paragraph's contents
// directly in the cell.
if (transformed.length === 1 && transformed[0].type === "paragraph") {
if (blockContent.length === 1 && blockContent[0].type === "paragraph") {
return {
type: "tableCell",
children: transformed[0].children,
children: blockContent[0].children,
};
}

// `mdast` does not allow block content in table cells, but we want to be able to include things like fenced code blocks, etc. in our table cells.
// To accommodate this, we convert the contents to HTML and put that inside the table cell.
const htmlTrees = transformed.map((node) => toHast(node));
const htmlTrees = blockContent.map((node) => toHast(node));
const htmlNodes: Html[] = htmlTrees.map((node) => ({
type: "html",
value: toHtml(node),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,6 @@

export * from "./Helpers.js";
export * from "./TableHelpers.js";
export { createTableFromItems } from "./TableCreation.js";

/* eslint-enable no-restricted-syntax */
Original file line number Diff line number Diff line change
@@ -0,0 +1,204 @@
/*!
* Copyright (c) Microsoft Corporation and contributors. All rights reserved.
* Licensed under the MIT License.
*/

import {
type ApiClass,
type ApiInterface,
type ApiItem,
ApiItemKind,
type HeritageType,
} from "@microsoft/api-extractor-model";

import {
getApiItemKind,
type ApiTypeLike,
isTypeLike,
isStatic,
} from "../../utilities/index.js";
import type { ApiItemTransformationConfiguration } from "../configuration/index.js";

import { filterItems } from "./ApiItemTransformUtilities.js";

/**
* API-Extractor does not directly provide a way to get inherited members of an API item.
* These utilities work around that limitation to provide a best-effort approximation of inherited members.
*
* If, in the future, API-Extractor provides a better way to get inherited members, these utilities should be updated or removed as appropriate.
*/

/**
* {@link TypeMember} base interface.
*/
export interface TypeMemberBase<TApiItem extends ApiItem = ApiItem> {
/**
* The kind of type member.
* "own" if the member is directly declared on the API item.
* "inherited" if the member is inherited from a base type.
*/
readonly kind: "own" | "inherited";

/**
* The API item that is the member.
*/
readonly item: TApiItem;
}

/**
* Represents a type member that is directly declared on the API item.
*/
export interface OwnTypeMember<TApiItem extends ApiItem = ApiItem>
extends TypeMemberBase<TApiItem> {
readonly kind: "own";

/**
* The member on some base type that this member overrides, if any.
* @remarks For example, if this member is a method that overrides a method on a base class, this would be that base method.
*/
readonly baseDefinition: ApiItem | undefined;
}

/**
* Represents a type member that is inherited from a base type.
*/
export interface InheritedTypeMember<TApiItem extends ApiItem = ApiItem>
extends TypeMemberBase<TApiItem> {
readonly kind: "inherited";

/**
* The API item from which this member is inherited.
* @remarks For example, if this member is a method inherited from a base class, this would be that base class.
*/
readonly baseDefinition: ApiItem;
}

/**
* A type member, which may be either directly declared on the API item or inherited from a base type.
*/
export type TypeMember<TApiItem extends ApiItem = ApiItem> =
| OwnTypeMember<TApiItem>
| InheritedTypeMember<TApiItem>;

/**
* Gets the members of the specified API item, including inherited members where applicable.
* @param apiItem - The API item being queried.
* @param config - See {@link ApiItemTransformationConfiguration}.
*/
export function getTypeMembers<TApiItem extends ApiTypeLike>(
apiItem: TApiItem,
config: ApiItemTransformationConfiguration,
): TypeMember[] {
// Get inherited members
const inheritedMembers: InheritedTypeMember[] = [];
if (apiItem.kind === ApiItemKind.Class) {
const apiClass = apiItem as ApiClass;

// Inherit members from `extends` clause
if (apiClass.extendsType !== undefined) {
inheritedMembers.push(...getInheritedMembers(apiClass, apiClass.extendsType, config));
}

// Inherit members from `implements` clauses
for (const implementsType of apiClass.implementsTypes) {
inheritedMembers.push(...getInheritedMembers(apiClass, implementsType, config));
}
} else if (apiItem.kind === ApiItemKind.Interface) {
const apiInterface = apiItem as ApiInterface;

// Inherit members from `extends` clauses
for (const extendsType of apiInterface.extendsTypes) {
inheritedMembers.push(...getInheritedMembers(apiInterface, extendsType, config));
}
} else {
// TODO: type-alias
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TODO: document this as a future change

}

const ownMemberItems = filterItems(apiItem.members, config);

const ownMembers: OwnTypeMember[] = [];
for (const ownMemberItem of ownMemberItems) {
const override = inheritedMembers.find(
// TODO: verify this is a sufficient check for API match
(inherited) => inherited.item.containerKey === ownMemberItem.containerKey,
);

// If this member overrides a base member, remove that base member from the inherited members list.
// We only want to display our override.
if (override) {
inheritedMembers.splice(inheritedMembers.indexOf(override), 1);
}

ownMembers.push({
kind: "own",
item: ownMemberItem,
baseDefinition: override?.item,
});
}

// TODO: document ordering
return [...inheritedMembers, ...ownMembers];
}

function getInheritedMembers(
apiItem: ApiItem,
extendsType: HeritageType,
config: ApiItemTransformationConfiguration,
): InheritedTypeMember[] {
const referencedItem = resolveHeritageTypeToItem(apiItem, extendsType, config);
if (referencedItem === undefined || !isTypeLike(referencedItem)) {
return [];
}
const referencedItemMembers: InheritedTypeMember[] = getTypeMembers(
referencedItem,
config,
).map((inherited) => ({
kind: "inherited",
item: inherited.item,
// If the item we're inheriting is itself inherited, preserve the original source.
// Otherwise, the source is the item we're inheriting from.
baseDefinition: inherited.kind === "inherited" ? inherited.baseDefinition : referencedItem,
}));

// Don't inherit constructors or static members
return referencedItemMembers.filter((inherited) => {
const itemKind = getApiItemKind(inherited.item);
if (itemKind === ApiItemKind.Constructor || itemKind === ApiItemKind.ConstructSignature) {
return false;
}
if (isStatic(inherited.item)) {
return false;
}
return true;
});
}

function resolveHeritageTypeToItem(
contextApiItem: ApiItem,
heritageType: HeritageType,
config: ApiItemTransformationConfiguration,
): ApiItem | undefined {
const excerpt = heritageType.excerpt;
if (excerpt.spannedTokens.length === 0) {
return undefined;
}

if (excerpt.spannedTokens.length > 1) {
// If there are multiple tokens, then the type expression is more complex than a single reference.
// This is a case we don't currently support.
return undefined;
}

const token = excerpt.spannedTokens[0];
if (token.kind !== "Reference" || token.canonicalReference === undefined) {
// If the single token is not a reference, then there is nothing to resolve.
return undefined;
}

const resolvedReference = config.apiModel.resolveDeclarationReference(
token.canonicalReference,
contextApiItem,
);

return resolvedReference.resolvedApiItem;
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,8 @@ export {
shouldItemBeIncluded,
} from "./ApiItemTransformUtilities.js";
export { createDocument, checkForDuplicateDocumentPaths } from "./DocumentUtilities.js";
export {
getTypeMembers,
type TypeMember,
} from "./InheritanceUtilities.js";
export { resolveSymbolicLink } from "./ReferenceUtilities.js";
Loading
Loading