build: package api-extractor and -model (#9920)

* fix(ExceptText): don't display import("d..-types/v10"). in return type

* Squashed 'packages/api-extractor-model/' content from commit 39ecb196c

git-subtree-dir: packages/api-extractor-model
git-subtree-split: 39ecb196ca210bdf84ba6c9cadb1bb93571849d7

* Squashed 'packages/api-extractor/' content from commit 341ad6c51

git-subtree-dir: packages/api-extractor
git-subtree-split: 341ad6c51b01656d4f73b74ad4bdb3095f9262c4

* feat(api-extractor): add api-extractor and -model

* fix: package.json docs script

* fix(SourcLink): use <> instead of function syntax

* fix: make packages private

* fix: rest params showing in docs, added labels

* fix: missed two files

* fix: cpy-cli & pnpm-lock

* fix: increase icon size

* fix: icon size again
This commit is contained in:
Qjuh
2023-11-07 21:53:36 +01:00
committed by GitHub
parent 95c0b1a59f
commit 5c0fad3b2d
251 changed files with 36017 additions and 296 deletions

6
.github/labeler.yml vendored
View File

@@ -4,6 +4,12 @@ apps:guide:
apps:website:
- apps/website/*
- apps/website/**/*
packages:api-extractor:
- packages/api-extractor/*
- packages/api-extractor/**/*
packages:api-extractor-model:
- packages/api-extractor-model/*
- packages/api-extractor-model/**/*
packages:brokers:
- packages/brokers/*
- packages/brokers/**/*

4
.github/labels.yml vendored
View File

@@ -52,6 +52,10 @@
color: e4e669
- name: need repro
color: c66037
- name: packages:api-extractor
color: fbca04
- name: packages:api-extractor-model
color: fbca04
- name: packages:brokers
color: fbca04
- name: packages:builders

1
.npmrc
View File

@@ -3,5 +3,4 @@ resolution-mode=highest
public-hoist-pattern[]=*eslint*
public-hoist-pattern[]=*prettier*
public-hoist-pattern[]=*@rushstack/node-core-library*
public-hoist-pattern[]=*@microsoft/api-extractor-model*
public-hoist-pattern[]=*jju*

View File

@@ -222,7 +222,7 @@
/**
* (REQUIRED) Whether to generate the .d.ts rollup file.
*/
"enabled": true,
"enabled": false,
/**
* Specifies the output path for a .d.ts rollup file to be generated without any trimming.

View File

@@ -8,7 +8,7 @@ export default withBundleAnalyzer({
reactStrictMode: true,
experimental: {
typedRoutes: true,
serverComponentsExternalPackages: ['@rushstack/node-core-library', '@microsoft/api-extractor-model', 'jju'],
serverComponentsExternalPackages: ['@rushstack/node-core-library', '@discordjs/api-extractor-model', 'jju'],
},
images: {
dangerouslyAllowSVG: true,

View File

@@ -50,7 +50,7 @@
"@discordjs/api-extractor-utils": "workspace:^",
"@discordjs/scripts": "workspace:^",
"@discordjs/ui": "workspace:^",
"@microsoft/api-extractor-model": "^7.28.2",
"@discordjs/api-extractor-model": "workspace:^",
"@microsoft/tsdoc": "^0.14.2",
"@microsoft/tsdoc-config": "0.16.2",
"@planetscale/database": "^1.11.0",

View File

@@ -1,6 +1,6 @@
/* eslint-disable react/no-unknown-property */
import type { ApiItemKind } from '@microsoft/api-extractor-model';
import type { ApiItemKind } from '@discordjs/api-extractor-model';
import { ImageResponse } from '@vercel/og';
import type { NextRequest } from 'next/server';

View File

@@ -1,4 +1,3 @@
import { tryResolveSummaryText } from '@discordjs/scripts';
import type {
ApiClass,
ApiDeclaredItem,
@@ -13,8 +12,9 @@ import type {
ApiTypeAlias,
ApiVariable,
ApiFunction,
} from '@microsoft/api-extractor-model';
import { ApiItemKind, ApiModel } from '@microsoft/api-extractor-model';
} from '@discordjs/api-extractor-model';
import { ApiItemKind, ApiModel } from '@discordjs/api-extractor-model';
import { tryResolveSummaryText } from '@discordjs/scripts';
import type { Metadata } from 'next';
import { notFound } from 'next/navigation';
import { fetchModelJSON } from '~/app/docAPI';

View File

@@ -1,5 +1,5 @@
import type { ApiFunction, ApiItem } from '@microsoft/api-extractor-model';
import { ApiModel } from '@microsoft/api-extractor-model';
import type { ApiFunction, ApiItem } from '@discordjs/api-extractor-model';
import { ApiModel } from '@discordjs/api-extractor-model';
import dynamic from 'next/dynamic';
import { notFound } from 'next/navigation';
import type { PropsWithChildren } from 'react';

View File

@@ -1,5 +1,5 @@
import type { ApiDocumentedItem } from '@microsoft/api-extractor-model';
import { ApiAbstractMixin, ApiProtectedMixin, ApiReadonlyMixin, ApiStaticMixin } from '@microsoft/api-extractor-model';
import type { ApiDocumentedItem } from '@discordjs/api-extractor-model';
import { ApiAbstractMixin, ApiProtectedMixin, ApiReadonlyMixin, ApiStaticMixin } from '@discordjs/api-extractor-model';
import type { PropsWithChildren } from 'react';
export enum BadgeColor {

View File

@@ -1,6 +1,6 @@
'use client';
import type { ApiItemKind } from '@microsoft/api-extractor-model';
import type { ApiItemKind } from '@discordjs/api-extractor-model';
import { VscArrowRight } from '@react-icons/all-files/vsc/VscArrowRight';
import { VscSymbolClass } from '@react-icons/all-files/vsc/VscSymbolClass';
import { VscSymbolEnum } from '@react-icons/all-files/vsc/VscSymbolEnum';

View File

@@ -1,5 +1,6 @@
import type { ReactNode } from 'react';
import { Anchor } from './Anchor';
import { SourceLink } from './documentation/SourceLink';
export interface CodeListingProps {
/**
@@ -14,15 +15,26 @@ export interface CodeListingProps {
* The href of this heading.
*/
readonly href?: string | undefined;
/**
* The line in the source code where this part is declared
*/
readonly sourceLine?: number | undefined;
/**
* The URL of the source code of this code part
*/
readonly sourceURL?: string | undefined;
}
export function CodeHeading({ href, className, children }: CodeListingProps) {
export function CodeHeading({ href, className, children, sourceURL, sourceLine }: CodeListingProps) {
return (
<div className="flex flex-row place-items-center justify-between gap-1">
<div
className={`flex flex-row flex-wrap place-items-center gap-1 break-all font-mono text-lg font-bold ${className}`}
>
{href ? <Anchor href={href} /> : null}
{children}
</div>
{sourceURL ? <SourceLink className="text-2xl" sourceLine={sourceLine} sourceURL={sourceURL} /> : null}
</div>
);
}

View File

@@ -1,5 +1,5 @@
import type { ApiModel, Excerpt } from '@microsoft/api-extractor-model';
import { ExcerptTokenKind } from '@microsoft/api-extractor-model';
import type { ApiModel, Excerpt } from '@discordjs/api-extractor-model';
import { ExcerptTokenKind } from '@discordjs/api-extractor-model';
import { DISCORD_API_TYPES_DOCS_URL } from '~/util/constants';
import { ItemLink } from './ItemLink';
import { resolveItemURI } from './documentation/util';
@@ -60,7 +60,7 @@ export function ExcerptText({ model, excerpt }: ExcerptTextProps) {
);
}
return token.text;
return token.text.replace(/import\("discord-api-types(?:\/v\d+)?"\)\./, '');
})}
</span>
);

View File

@@ -1,4 +1,4 @@
import type { ApiDeclaredItem } from '@microsoft/api-extractor-model';
import type { ApiDeclaredItem } from '@discordjs/api-extractor-model';
import { ItemLink } from './ItemLink';
import { resolveItemURI } from './documentation/util';

View File

@@ -1,4 +1,4 @@
import type { ApiDocumentedItem, ApiParameterListMixin } from '@microsoft/api-extractor-model';
import type { ApiDocumentedItem, ApiParameterListMixin } from '@discordjs/api-extractor-model';
import { useMemo } from 'react';
import { resolveParameters } from '~/util/model';
import { ExcerptText } from './ExcerptText';
@@ -16,7 +16,7 @@ export function ParameterTable({ item }: { readonly item: ApiDocumentedItem & Ap
const rows = useMemo(
() =>
params.map((param) => ({
Name: param.name,
Name: param.isRest ? `...${param.name}` : param.name,
Type: <ExcerptText excerpt={param.parameterTypeExcerpt} model={item.getAssociatedModel()!} />,
Optional: param.isOptional ? 'Yes' : 'No',
Description: param.description ? <TSDoc item={item} tsdoc={param.description} /> : 'None',

View File

@@ -3,7 +3,7 @@ import type {
ApiItemContainerMixin,
ApiProperty,
ApiPropertySignature,
} from '@microsoft/api-extractor-model';
} from '@discordjs/api-extractor-model';
import type { PropsWithChildren } from 'react';
import { Badges } from './Badges';
import { CodeHeading } from './CodeHeading';
@@ -25,7 +25,11 @@ export function Property({
<div className="flex flex-col scroll-mt-30 gap-4" id={item.displayName}>
<div className="flex flex-col gap-2 md:-ml-9">
<Badges item={item} />
<CodeHeading href={`#${item.displayName}`}>
<CodeHeading
href={`#${item.displayName}`}
sourceURL={item.sourceLocation.fileUrl}
sourceLine={item.sourceLocation.fileLine}
>
{`${item.displayName}${item.isOptional ? '?' : ''}`}
<span>:</span>
{item.propertyTypeExcerpt.text ? (

View File

@@ -4,8 +4,8 @@ import type {
ApiItemContainerMixin,
ApiProperty,
ApiPropertySignature,
} from '@microsoft/api-extractor-model';
import { ApiItemKind } from '@microsoft/api-extractor-model';
} from '@discordjs/api-extractor-model';
import { ApiItemKind } from '@discordjs/api-extractor-model';
import { Fragment, useMemo } from 'react';
import { resolveMembers } from '~/util/members';
import { Property } from './Property';

View File

@@ -1,6 +1,6 @@
'use client';
import type { ApiItemKind } from '@microsoft/api-extractor-model';
import type { ApiItemKind } from '@discordjs/api-extractor-model';
import { VscSymbolClass } from '@react-icons/all-files/vsc/VscSymbolClass';
import { VscSymbolEnum } from '@react-icons/all-files/vsc/VscSymbolEnum';
import { VscSymbolInterface } from '@react-icons/all-files/vsc/VscSymbolInterface';

View File

@@ -1,4 +1,4 @@
import type { ApiModel, Excerpt } from '@microsoft/api-extractor-model';
import type { ApiModel, Excerpt } from '@discordjs/api-extractor-model';
import { ExcerptText } from './ExcerptText';
export function SignatureText({ excerpt, model }: { readonly excerpt: Excerpt; readonly model: ApiModel }) {

View File

@@ -1,4 +1,4 @@
import type { ApiTypeParameterListMixin } from '@microsoft/api-extractor-model';
import type { ApiTypeParameterListMixin } from '@discordjs/api-extractor-model';
import { useMemo } from 'react';
import { ExcerptText } from './ExcerptText';
import { Table } from './Table';

View File

@@ -1,11 +1,11 @@
import { ApiItemKind } from '@microsoft/api-extractor-model';
import { VscFileCode } from '@react-icons/all-files/vsc/VscFileCode';
import { ApiItemKind } from '@discordjs/api-extractor-model';
import { VscSymbolClass } from '@react-icons/all-files/vsc/VscSymbolClass';
import { VscSymbolEnum } from '@react-icons/all-files/vsc/VscSymbolEnum';
import { VscSymbolInterface } from '@react-icons/all-files/vsc/VscSymbolInterface';
import { VscSymbolMethod } from '@react-icons/all-files/vsc/VscSymbolMethod';
import { VscSymbolVariable } from '@react-icons/all-files/vsc/VscSymbolVariable';
import type { PropsWithChildren } from 'react';
import { SourceLink } from './SourceLink';
function generateIcon(kind: ApiItemKind) {
switch (kind) {
@@ -30,7 +30,13 @@ export function Header({
kind,
name,
sourceURL,
}: PropsWithChildren<{ readonly kind: ApiItemKind; readonly name: string; readonly sourceURL?: string | undefined }>) {
sourceLine,
}: PropsWithChildren<{
readonly kind: ApiItemKind;
readonly name: string;
readonly sourceLine?: number | undefined;
readonly sourceURL?: string | undefined;
}>) {
return (
<div className="flex flex-col">
<h2 className="flex flex-row place-items-center justify-between gap-2 break-all text-2xl font-bold">
@@ -38,11 +44,7 @@ export function Header({
<span>{generateIcon(kind)}</span>
{name}
</span>
{sourceURL ? (
<a className="text-blurple" href={sourceURL} rel="external noopener noreferrer" target="_blank">
<VscFileCode />
</a>
) : null}
{sourceURL ? <SourceLink sourceLine={sourceLine} sourceURL={sourceURL} /> : null}
</h2>
</div>
);

View File

@@ -1,5 +1,5 @@
import type { ApiClass, ApiInterface, Excerpt } from '@microsoft/api-extractor-model';
import { ApiItemKind } from '@microsoft/api-extractor-model';
import type { ApiClass, ApiInterface, Excerpt } from '@discordjs/api-extractor-model';
import { ApiItemKind } from '@discordjs/api-extractor-model';
import { ExcerptText } from '../ExcerptText';
export function HierarchyText({

View File

@@ -1,4 +1,4 @@
import type { ApiDeclaredItem, ApiItemContainerMixin } from '@microsoft/api-extractor-model';
import type { ApiDeclaredItem, ApiItemContainerMixin } from '@discordjs/api-extractor-model';
import { MethodsSection } from './section/MethodsSection';
import { PropertiesSection } from './section/PropertiesSection';
import { hasProperties, hasMethods } from './util';

View File

@@ -1,4 +1,4 @@
import type { ApiDeclaredItem } from '@microsoft/api-extractor-model';
import type { ApiDeclaredItem } from '@discordjs/api-extractor-model';
import { SyntaxHighlighter } from '../SyntaxHighlighter';
import { Header } from './Header';
import { SummarySection } from './section/SummarySection';
@@ -10,7 +10,12 @@ export interface ObjectHeaderProps {
export function ObjectHeader({ item }: ObjectHeaderProps) {
return (
<>
<Header kind={item.kind} name={item.displayName} sourceURL={item.sourceLocation.fileUrl} />
<Header
kind={item.kind}
name={item.displayName}
sourceURL={item.sourceLocation.fileUrl}
sourceLine={item.sourceLocation.fileLine}
/>
{/* @ts-expect-error async component */}
<SyntaxHighlighter code={item.excerpt.text} />
<SummarySection item={item} />

View File

@@ -0,0 +1,22 @@
import { VscFileCode } from '@react-icons/all-files/vsc/VscFileCode';
export function SourceLink({
className,
sourceURL,
sourceLine,
}: {
readonly className?: string | undefined;
readonly sourceLine?: number | undefined;
readonly sourceURL?: string | undefined;
}) {
return (
<a
className={` text-blurple ${className}`}
href={sourceLine ? `${sourceURL}#L${sourceLine}` : sourceURL}
rel="external noopener noreferrer"
target="_blank"
>
<VscFileCode />
</a>
);
}

View File

@@ -1,4 +1,4 @@
import type { ApiConstructor } from '@microsoft/api-extractor-model';
import type { ApiConstructor } from '@discordjs/api-extractor-model';
import { VscSymbolMethod } from '@react-icons/all-files/vsc/VscSymbolMethod';
import { CodeHeading } from '~/components/CodeHeading';
import { ParameterTable } from '../../ParameterTable';
@@ -10,7 +10,10 @@ export function ConstructorSection({ item }: { readonly item: ApiConstructor })
return (
<DocumentationSection icon={<VscSymbolMethod size={20} />} padded title="Constructor">
<div className="flex flex-col gap-2">
<CodeHeading>{`constructor(${parametersString(item)})`}</CodeHeading>
<CodeHeading
sourceURL={item.sourceLocation.fileUrl}
sourceLine={item.sourceLocation.fileLine}
>{`constructor(${parametersString(item)})`}</CodeHeading>
{item.tsdocComment ? <TSDoc item={item} tsdoc={item.tsdocComment} /> : null}
<ParameterTable item={item} />
</div>

View File

@@ -4,8 +4,8 @@ import type {
ApiItemContainerMixin,
ApiMethod,
ApiMethodSignature,
} from '@microsoft/api-extractor-model';
import { ApiItemKind } from '@microsoft/api-extractor-model';
} from '@discordjs/api-extractor-model';
import { ApiItemKind } from '@discordjs/api-extractor-model';
import { VscSymbolMethod } from '@react-icons/all-files/vsc/VscSymbolMethod';
import { useMemo, Fragment } from 'react';
import { resolveMembers } from '~/util/members';

View File

@@ -1,4 +1,4 @@
import type { ApiDocumentedItem, ApiParameterListMixin } from '@microsoft/api-extractor-model';
import type { ApiDocumentedItem, ApiParameterListMixin } from '@discordjs/api-extractor-model';
import { VscSymbolParameter } from '@react-icons/all-files/vsc/VscSymbolParameter';
import { ParameterTable } from '../../ParameterTable';
import { DocumentationSection } from './DocumentationSection';

View File

@@ -1,4 +1,4 @@
import type { ApiItemContainerMixin } from '@microsoft/api-extractor-model';
import type { ApiItemContainerMixin } from '@discordjs/api-extractor-model';
import { VscSymbolProperty } from '@react-icons/all-files/vsc/VscSymbolProperty';
import { PropertyList } from '../../PropertyList';
import { DocumentationSection } from './DocumentationSection';

View File

@@ -1,4 +1,4 @@
import type { ApiDeclaredItem } from '@microsoft/api-extractor-model';
import type { ApiDeclaredItem } from '@discordjs/api-extractor-model';
import { VscListSelection } from '@react-icons/all-files/vsc/VscListSelection';
import { TSDoc } from '../tsdoc/TSDoc';
import { DocumentationSection } from './DocumentationSection';

View File

@@ -1,4 +1,4 @@
import type { ApiTypeParameterListMixin } from '@microsoft/api-extractor-model';
import type { ApiTypeParameterListMixin } from '@discordjs/api-extractor-model';
import { VscSymbolParameter } from '@react-icons/all-files/vsc/VscSymbolParameter';
import { TypeParamTable } from '../../TypeParamTable';
import { DocumentationSection } from './DocumentationSection';

View File

@@ -1,4 +1,4 @@
import type { ApiItem } from '@microsoft/api-extractor-model';
import type { ApiItem } from '@discordjs/api-extractor-model';
import type { DocComment, DocFencedCode, DocLinkTag, DocNode, DocNodeContainer, DocPlainText } from '@microsoft/tsdoc';
import { DocNodeKind, StandardTags } from '@microsoft/tsdoc';
import type { Route } from 'next';

View File

@@ -1,4 +1,4 @@
import { ApiItemKind } from '@microsoft/api-extractor-model';
import { ApiItemKind } from '@discordjs/api-extractor-model';
import type {
ApiItem,
ApiItemContainerMixin,
@@ -8,7 +8,7 @@ import type {
ApiPropertySignature,
ApiDocumentedItem,
ApiParameterListMixin,
} from '@microsoft/api-extractor-model';
} from '@discordjs/api-extractor-model';
import { METHOD_SEPARATOR, OVERLOAD_SEPARATOR } from '~/util/constants';
import { resolveMembers } from '~/util/members';
import { resolveParameters } from '~/util/model';
@@ -63,9 +63,9 @@ export function serializeMembers(clazz: ApiItemContainerMixin): TableOfContentsS
export function parametersString(item: ApiDocumentedItem & ApiParameterListMixin) {
return resolveParameters(item).reduce((prev, cur, index) => {
if (index === 0) {
return `${prev}${cur.isOptional ? `${cur.name}?` : cur.name}`;
return `${prev}${cur.isRest ? '...' : ''}${cur.isOptional ? `${cur.name}?` : cur.name}`;
}
return `${prev}, ${cur.isOptional ? `${cur.name}?` : cur.name}`;
return `${prev}, ${cur.isRest ? '...' : ''}${cur.isOptional ? `${cur.name}?` : cur.name}`;
}, '');
}

View File

@@ -1,5 +1,5 @@
import type { ApiClass, ApiConstructor } from '@microsoft/api-extractor-model';
import { ApiItemKind } from '@microsoft/api-extractor-model';
import type { ApiClass, ApiConstructor } from '@discordjs/api-extractor-model';
import { ApiItemKind } from '@discordjs/api-extractor-model';
// import { Outline } from '../Outline';
import { Badges } from '../Badges';
import { Documentation } from '../documentation/Documentation';

View File

@@ -1,4 +1,4 @@
import type { ApiInterface } from '@microsoft/api-extractor-model';
import type { ApiInterface } from '@discordjs/api-extractor-model';
// import { Outline } from '../Outline';
import { Documentation } from '../documentation/Documentation';
import { HierarchyText } from '../documentation/HierarchyText';

View File

@@ -1,4 +1,4 @@
import type { ApiTypeAlias } from '@microsoft/api-extractor-model';
import type { ApiTypeAlias } from '@discordjs/api-extractor-model';
import { SyntaxHighlighter } from '../SyntaxHighlighter';
import { Documentation } from '../documentation/Documentation';
import { Header } from '../documentation/Header';
@@ -7,7 +7,12 @@ import { SummarySection } from '../documentation/section/SummarySection';
export function TypeAlias({ item }: { readonly item: ApiTypeAlias }) {
return (
<Documentation>
<Header kind={item.kind} name={item.displayName} sourceURL={item.sourceLocation.fileUrl} />
<Header
kind={item.kind}
name={item.displayName}
sourceURL={item.sourceLocation.fileUrl}
sourceLine={item.sourceLocation.fileLine}
/>
{/* @ts-expect-error async component */}
<SyntaxHighlighter code={item.excerpt.text} />
<SummarySection item={item} />

View File

@@ -1,4 +1,4 @@
import type { ApiVariable } from '@microsoft/api-extractor-model';
import type { ApiVariable } from '@discordjs/api-extractor-model';
import { Documentation } from '../documentation/Documentation';
import { ObjectHeader } from '../documentation/ObjectHeader';

View File

@@ -1,4 +1,4 @@
import type { ApiEnum } from '@microsoft/api-extractor-model';
import type { ApiEnum } from '@discordjs/api-extractor-model';
import { VscSymbolEnum } from '@react-icons/all-files/vsc/VscSymbolEnum';
import { Panel } from '../../Panel';
import { Documentation } from '../../documentation/Documentation';

View File

@@ -1,4 +1,4 @@
import type { ApiEnumMember } from '@microsoft/api-extractor-model';
import type { ApiEnumMember } from '@discordjs/api-extractor-model';
import { CodeHeading } from '~/components/CodeHeading';
import { SignatureText } from '../../SignatureText';
import { TSDoc } from '../../documentation/tsdoc/TSDoc';
@@ -6,7 +6,12 @@ import { TSDoc } from '../../documentation/tsdoc/TSDoc';
export function EnumMember({ member }: { readonly member: ApiEnumMember }) {
return (
<div className="flex flex-col scroll-mt-30" id={member.displayName}>
<CodeHeading className="md:-ml-8.5" href={`#${member.displayName}`}>
<CodeHeading
className="md:-ml-8.5"
href={`#${member.displayName}`}
sourceURL={member.sourceLocation.fileUrl}
sourceLine={member.sourceLocation.fileLine}
>
{member.name}
<span>=</span>
{member.initializerExcerpt ? (

View File

@@ -1,4 +1,4 @@
import type { ApiFunction } from '@microsoft/api-extractor-model';
import type { ApiFunction } from '@discordjs/api-extractor-model';
import dynamic from 'next/dynamic';
import { Header } from '../../documentation/Header';
import { FunctionBody } from './FunctionBody';
@@ -6,7 +6,14 @@ import { FunctionBody } from './FunctionBody';
const OverloadSwitcher = dynamic(async () => import('../../OverloadSwitcher'));
export function Function({ item }: { readonly item: ApiFunction }) {
const header = <Header kind={item.kind} name={item.name} sourceURL={item.sourceLocation.fileUrl} />;
const header = (
<Header
kind={item.kind}
name={item.name}
sourceURL={item.sourceLocation.fileUrl}
sourceLine={item.sourceLocation.fileLine}
/>
);
if (item.getMergedSiblings().length > 1) {
const overloads = item

View File

@@ -1,4 +1,4 @@
import type { ApiFunction } from '@microsoft/api-extractor-model';
import type { ApiFunction } from '@discordjs/api-extractor-model';
import { SyntaxHighlighter } from '../../SyntaxHighlighter';
import { Documentation } from '../../documentation/Documentation';
import { ParameterSection } from '../../documentation/section/ParametersSection';

View File

@@ -3,7 +3,7 @@ import type {
ApiItemContainerMixin,
ApiMethod,
ApiMethodSignature,
} from '@microsoft/api-extractor-model';
} from '@discordjs/api-extractor-model';
import dynamic from 'next/dynamic';
import { Fragment } from 'react';
import { MethodDocumentation } from './MethodDocumentation';

View File

@@ -3,7 +3,7 @@ import type {
ApiItemContainerMixin,
ApiMethod,
ApiMethodSignature,
} from '@microsoft/api-extractor-model';
} from '@discordjs/api-extractor-model';
import { InheritanceText } from '../../InheritanceText';
import { ParameterTable } from '../../ParameterTable';
import { TSDoc } from '../../documentation/tsdoc/TSDoc';

View File

@@ -1,4 +1,4 @@
import type { ApiMethod, ApiMethodSignature } from '@microsoft/api-extractor-model';
import type { ApiMethod, ApiMethodSignature } from '@discordjs/api-extractor-model';
import { useMemo } from 'react';
import { Badges } from '~/components/Badges';
import { CodeHeading } from '~/components/CodeHeading';
@@ -15,7 +15,11 @@ export function MethodHeader({ method }: { readonly method: ApiMethod | ApiMetho
<div className="flex flex-col scroll-mt-30" id={key}>
<div className="flex flex-col gap-2 md:-ml-9">
<Badges item={method} />
<CodeHeading href={`#${key}`}>
<CodeHeading
href={`#${key}`}
sourceLine={method.sourceLocation.fileLine}
sourceURL={method.sourceLocation.fileUrl}
>
{`${method.name}(${parametersString(method)})`}
<span>:</span>
<ExcerptText excerpt={method.returnTypeExcerpt} model={method.getAssociatedModel()!} />

View File

@@ -1,7 +1,7 @@
'use client';
import type { ApiItem } from '@discordjs/api-extractor-model';
import type { ApiItemJSON } from '@discordjs/api-extractor-utils';
import type { ApiItem } from '@microsoft/api-extractor-model';
import { createContext, useContext, useMemo, useState } from 'react';
import type { PropsWithChildren, Dispatch, SetStateAction } from 'react';

View File

@@ -1,5 +1,5 @@
import type { ApiModel, ApiPackage } from '@microsoft/api-extractor-model';
import { ApiItem } from '@microsoft/api-extractor-model';
import type { ApiModel, ApiPackage } from '@discordjs/api-extractor-model';
import { ApiItem } from '@discordjs/api-extractor-model';
import { TSDocConfiguration } from '@microsoft/tsdoc';
import { TSDocConfigFile } from '@microsoft/tsdoc-config';

View File

@@ -1,4 +1,4 @@
import { ApiModel, ApiFunction } from '@microsoft/api-extractor-model';
import { ApiModel, ApiFunction } from '@discordjs/api-extractor-model';
import { notFound } from 'next/navigation';
import { fetchModelJSON } from '~/app/docAPI';
import { addPackageToModel } from './addPackageToModel';

View File

@@ -1,4 +1,4 @@
import type { ApiItem, ApiItemContainerMixin } from '@microsoft/api-extractor-model';
import type { ApiItem, ApiItemContainerMixin } from '@discordjs/api-extractor-model';
/**
* Resolves all inherited members (including merged members) of a given parent.

View File

@@ -4,7 +4,7 @@ import type {
ApiModel,
ApiParameterListMixin,
Excerpt,
} from '@microsoft/api-extractor-model';
} from '@discordjs/api-extractor-model';
import type { DocSection } from '@microsoft/tsdoc';
export function findMemberByKey(model: ApiModel, packageName: string, containerKey: string) {
@@ -24,6 +24,7 @@ export function findMember(model: ApiModel, packageName: string, memberName: str
interface ResolvedParameter {
description?: DocSection | undefined;
isOptional: boolean;
isRest: boolean;
name: string;
parameterTypeExcerpt: Excerpt;
}
@@ -45,6 +46,7 @@ export function resolveParameters(item: ApiDocumentedItem & ApiParameterListMixi
name: param.tsdocParamBlock?.parameterName ?? tsdocAnalog?.parameterName ?? param.name,
description: param.tsdocParamBlock?.content ?? tsdocAnalog?.content,
isOptional: param.isOptional,
isRest: param.isRest,
parameterTypeExcerpt: param.parameterTypeExcerpt,
};
});

View File

@@ -93,6 +93,24 @@ export default [
files: [`packages/voice/**/*${commonFiles}`],
rules: { 'no-restricted-globals': 0 },
},
{
files: [`packages/api-extractor-model/**/*${commonFiles}`],
rules: {
'@typescript-eslint/no-namespace': 0,
'no-prototype-builtins': 0,
'consistent-this': 0,
'unicorn/no-this-assignment': 0,
'@typescript-eslint/no-this-alias': 0,
},
},
{
files: [`packages/api-extractor/**/*${commonFiles}`],
rules: {
'consistent-this': 0,
'unicorn/no-this-assignment': 0,
'@typescript-eslint/no-this-alias': 0,
},
},
reactRuleset,
nextRuleset,
edgeRuleset,

23
packages/api-extractor-model/.gitignore vendored Normal file
View File

@@ -0,0 +1,23 @@
# Packages
node_modules
# Log files
logs
*.log
npm-debug.log*
# Runtime data
pids
*.pid
*.seed
# Env
.env
# Dist
dist
# Miscellaneous
.turbo
.tmp
coverage

View File

@@ -0,0 +1 @@
module.exports = require('../../.lintstagedrc.json');

View File

@@ -0,0 +1,30 @@
# THIS IS A STANDARD TEMPLATE FOR .npmignore FILES IN THIS REPO.
# Ignore all files by default, to avoid accidentally publishing unintended files.
*
# Use negative patterns to bring back the specific things we want to publish.
!/bin/**
!/lib/**
!/lib-*/**
!/dist/**
!ThirdPartyNotice.txt
# Ignore certain patterns that should not get published.
/dist/*.stats.*
/lib/**/test/
/lib-*/**/test/
*.test.js
# NOTE: These don't need to be specified, because NPM includes them automatically.
#
# package.json
# README (and its variants)
# CHANGELOG (and its variants)
# LICENSE / LICENCE
#--------------------------------------------
# DO NOT MODIFY THE TEMPLATE ABOVE THIS LINE
#--------------------------------------------
# (Add your project-specific overrides here)

View File

@@ -0,0 +1,5 @@
.turbo
coverage
dist
CHANGELOG.md
tsup.config.bundled*

View File

@@ -0,0 +1 @@
module.exports = require('../../.prettierrc.json');

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,965 @@
# Change Log - @microsoft/api-extractor-model
This log was last generated on Thu, 28 Sep 2023 20:53:16 GMT and should not be manually modified.
## 7.28.2
Thu, 28 Sep 2023 20:53:16 GMT
_Version update only_
## 7.28.1
Tue, 26 Sep 2023 09:30:33 GMT
### Patches
- Update type-only imports to include the type modifier.
## 7.28.0
Fri, 15 Sep 2023 00:36:58 GMT
### Minor changes
- Update @types/node from 14 to 18
## 7.27.6
Tue, 08 Aug 2023 07:10:40 GMT
_Version update only_
## 7.27.5
Wed, 19 Jul 2023 00:20:32 GMT
_Version update only_
## 7.27.4
Thu, 06 Jul 2023 00:16:20 GMT
_Version update only_
## 7.27.3
Thu, 15 Jun 2023 00:21:01 GMT
_Version update only_
## 7.27.2
Wed, 07 Jun 2023 22:45:16 GMT
_Version update only_
## 7.27.1
Mon, 29 May 2023 15:21:15 GMT
_Version update only_
## 7.27.0
Mon, 22 May 2023 06:34:32 GMT
### Minor changes
- Upgrade the TypeScript dependency to ~5.0.4
## 7.26.9
Fri, 12 May 2023 00:23:05 GMT
_Version update only_
## 7.26.8
Thu, 04 May 2023 00:20:28 GMT
### Patches
- Fix a mistake in the documentation for ApiParameterListMixin.overloadIndex
## 7.26.7
Mon, 01 May 2023 15:23:20 GMT
_Version update only_
## 7.26.6
Sat, 29 Apr 2023 00:23:03 GMT
_Version update only_
## 7.26.5
Thu, 27 Apr 2023 17:18:43 GMT
_Version update only_
## 7.26.4
Fri, 10 Feb 2023 01:18:51 GMT
_Version update only_
## 7.26.3
Sun, 05 Feb 2023 03:02:02 GMT
_Version update only_
## 7.26.2
Wed, 01 Feb 2023 02:16:34 GMT
_Version update only_
## 7.26.1
Mon, 30 Jan 2023 16:22:30 GMT
_Version update only_
## 7.26.0
Wed, 25 Jan 2023 07:26:55 GMT
### Minor changes
- Add new .api.json field `isAbstract` to track `abstract` modifier in ApiClass, ApiMethod, and ApiProperty via ApiAbstractMixin (GitHub #3661)
## 7.25.3
Fri, 09 Dec 2022 16:18:28 GMT
_Version update only_
## 7.25.2
Wed, 26 Oct 2022 00:16:16 GMT
### Patches
- Update the @microsoft/tsdoc dependency version to 0.14.2.
## 7.25.1
Thu, 13 Oct 2022 00:20:15 GMT
_Version update only_
## 7.25.0
Tue, 11 Oct 2022 23:49:12 GMT
### Minor changes
- Add a new fileUrlPath property to relevant API items and serialize this to the .api.json. Additionally, add a SourceFile helper class for constructing file URLs from these paths and the projectFolderUrl.
## 7.24.4
Mon, 10 Oct 2022 15:23:44 GMT
_Version update only_
## 7.24.3
Thu, 29 Sep 2022 07:13:06 GMT
_Version update only_
## 7.24.2
Wed, 21 Sep 2022 20:21:10 GMT
_Version update only_
## 7.24.1
Thu, 15 Sep 2022 00:18:52 GMT
_Version update only_
## 7.24.0
Fri, 02 Sep 2022 17:48:42 GMT
### Minor changes
- Add new ApiExportedMixin mixin class for determining whether an API item is exported or not
## 7.23.3
Wed, 24 Aug 2022 03:01:22 GMT
_Version update only_
## 7.23.2
Wed, 24 Aug 2022 00:14:38 GMT
### Patches
- Remove use of LegacyAdapters.sortStable
## 7.23.1
Fri, 19 Aug 2022 00:17:19 GMT
_Version update only_
## 7.23.0
Wed, 03 Aug 2022 18:40:35 GMT
### Minor changes
- Upgrade TypeScript dependency to 4.7
## 7.22.2
Mon, 01 Aug 2022 02:45:32 GMT
_Version update only_
## 7.22.1
Thu, 21 Jul 2022 23:30:27 GMT
### Patches
- Improve IFindApiItemMessage and fix two small bugs with ApiItemContainerMixin.findMembersWithInheritance()
## 7.22.0
Thu, 21 Jul 2022 00:16:14 GMT
### Minor changes
- Add a new ApiItemContainerMixin.findMembersWithInheritance() method for finding an item's inherited members
## 7.21.0
Thu, 30 Jun 2022 04:48:53 GMT
### Minor changes
- Update model to reflect that index signatures can also be readonly
## 7.20.3
Tue, 28 Jun 2022 22:47:13 GMT
_Version update only_
## 7.20.2
Tue, 28 Jun 2022 00:23:32 GMT
_Version update only_
## 7.20.1
Mon, 27 Jun 2022 18:43:09 GMT
_Version update only_
## 7.20.0
Sat, 25 Jun 2022 21:00:40 GMT
### Minor changes
- Add a new initializerTokenRange field to ApiProperty and ApiVariable items.
## 7.19.1
Sat, 25 Jun 2022 01:54:29 GMT
_Version update only_
## 7.19.0
Fri, 24 Jun 2022 07:16:47 GMT
### Minor changes
- Added new configuration for ItemContainerMixin member ordering
## 7.18.2
Fri, 17 Jun 2022 09:17:54 GMT
_Version update only_
## 7.18.1
Fri, 17 Jun 2022 00:16:18 GMT
_Version update only_
## 7.18.0
Tue, 07 Jun 2022 09:37:04 GMT
### Minor changes
- Add an "isReadonly" field to ApiProperty, ApiPropertySignature, and ApiVariable
- Add an "isProtected" field to ApiConstructor, ApiMethod, and ApiProperty
## 7.17.3
Tue, 10 May 2022 01:20:43 GMT
_Version update only_
## 7.17.2
Sat, 23 Apr 2022 02:13:07 GMT
_Version update only_
## 7.17.1
Fri, 15 Apr 2022 00:12:36 GMT
_Version update only_
## 7.17.0
Wed, 13 Apr 2022 15:12:40 GMT
### Minor changes
- Add a new isOptional property to TypeParameters deserialized from the .api.json file with api-extractor-model.
## 7.16.2
Tue, 12 Apr 2022 02:58:32 GMT
### Patches
- Update TSDoc dependencies.
## 7.16.1
Sat, 09 Apr 2022 02:24:26 GMT
### Patches
- Rename the "master" branch to "main".
- Update a path in the README.
## 7.16.0
Thu, 31 Mar 2022 02:06:05 GMT
### Minor changes
- Updated api-extractor-model to store whether a parameter is optional.
## 7.15.4
Tue, 15 Mar 2022 19:15:53 GMT
_Version update only_
## 7.15.3
Wed, 05 Jan 2022 16:07:47 GMT
_Version update only_
## 7.15.2
Mon, 27 Dec 2021 16:10:40 GMT
_Version update only_
## 7.15.1
Thu, 09 Dec 2021 20:34:41 GMT
_Version update only_
## 7.15.0
Thu, 09 Dec 2021 00:21:54 GMT
### Minor changes
- Replace const enums with conventional enums to allow for compatibility with JavaScript consumers.
## 7.14.0
Wed, 08 Dec 2021 16:14:05 GMT
### Minor changes
- Update to TypeScript 4.5
## 7.13.18
Mon, 06 Dec 2021 16:08:33 GMT
_Version update only_
## 7.13.17
Fri, 03 Dec 2021 03:05:22 GMT
_Version update only_
## 7.13.16
Sat, 06 Nov 2021 00:09:13 GMT
_Version update only_
## 7.13.15
Fri, 05 Nov 2021 15:09:18 GMT
_Version update only_
## 7.13.14
Wed, 27 Oct 2021 00:08:15 GMT
### Patches
- Update the package.json repository field to include the directory property.
## 7.13.13
Wed, 13 Oct 2021 15:09:54 GMT
_Version update only_
## 7.13.12
Fri, 08 Oct 2021 08:08:34 GMT
_Version update only_
## 7.13.11
Thu, 07 Oct 2021 07:13:35 GMT
_Version update only_
## 7.13.10
Tue, 05 Oct 2021 15:08:38 GMT
_Version update only_
## 7.13.9
Fri, 24 Sep 2021 00:09:29 GMT
_Version update only_
## 7.13.8
Thu, 23 Sep 2021 00:10:40 GMT
### Patches
- Upgrade the `@types/node` dependency to version to version 12.
## 7.13.7
Tue, 14 Sep 2021 01:17:04 GMT
_Version update only_
## 7.13.6
Mon, 13 Sep 2021 15:07:06 GMT
_Version update only_
## 7.13.5
Wed, 11 Aug 2021 00:07:21 GMT
_Version update only_
## 7.13.4
Mon, 12 Jul 2021 23:08:26 GMT
_Version update only_
## 7.13.3
Fri, 04 Jun 2021 19:59:53 GMT
_Version update only_
## 7.13.2
Wed, 19 May 2021 00:11:39 GMT
_Version update only_
## 7.13.1
Mon, 03 May 2021 15:10:29 GMT
_Version update only_
## 7.13.0
Tue, 20 Apr 2021 04:59:51 GMT
### Minor changes
- The .api.json file format now stores the TSDoc configuration used for parsing doc comments
## 7.12.5
Mon, 12 Apr 2021 15:10:28 GMT
_Version update only_
## 7.12.4
Thu, 08 Apr 2021 06:05:31 GMT
### Patches
- Fix minor typo in README.md
## 7.12.3
Tue, 06 Apr 2021 15:14:22 GMT
_Version update only_
## 7.12.2
Fri, 05 Feb 2021 16:10:42 GMT
_Version update only_
## 7.12.1
Thu, 10 Dec 2020 23:25:49 GMT
### Patches
- Enable support for @decorator
## 7.12.0
Wed, 18 Nov 2020 08:19:54 GMT
### Minor changes
- Introduce an ApiOptionalMixin base class for representing optional properties and methods
## 7.11.0
Wed, 18 Nov 2020 06:21:57 GMT
### Minor changes
- Update .api.json file format to store a new field "isOptional" for documenting optional properties
## 7.10.10
Wed, 11 Nov 2020 01:08:59 GMT
_Version update only_
## 7.10.9
Tue, 10 Nov 2020 23:13:12 GMT
_Version update only_
## 7.10.8
Fri, 30 Oct 2020 06:38:39 GMT
_Version update only_
## 7.10.7
Fri, 30 Oct 2020 00:10:14 GMT
_Version update only_
## 7.10.6
Thu, 29 Oct 2020 06:14:19 GMT
### Patches
- Fix .d.ts error when the library is imported by a project using TypeScript 4.0
## 7.10.5
Wed, 28 Oct 2020 01:18:03 GMT
_Version update only_
## 7.10.4
Tue, 27 Oct 2020 15:10:14 GMT
_Version update only_
## 7.10.3
Tue, 06 Oct 2020 00:24:06 GMT
_Version update only_
## 7.10.2
Mon, 05 Oct 2020 22:36:57 GMT
_Version update only_
## 7.10.1
Wed, 30 Sep 2020 18:39:17 GMT
### Patches
- Update to build with @rushstack/heft-node-rig
## 7.10.0
Wed, 30 Sep 2020 06:53:53 GMT
### Minor changes
- Upgrade compiler; the API now requires TypeScript 3.9 or newer
### Patches
- Update README.md
## 7.9.7
Tue, 22 Sep 2020 05:45:57 GMT
_Version update only_
## 7.9.6
Tue, 22 Sep 2020 01:45:31 GMT
_Version update only_
## 7.9.5
Tue, 22 Sep 2020 00:08:53 GMT
_Version update only_
## 7.9.4
Sat, 19 Sep 2020 04:37:27 GMT
_Version update only_
## 7.9.3
Sat, 19 Sep 2020 03:33:07 GMT
_Version update only_
## 7.9.2
Fri, 18 Sep 2020 22:57:24 GMT
_Version update only_
## 7.9.1
Fri, 18 Sep 2020 21:49:54 GMT
_Version update only_
## 7.9.0
Sun, 13 Sep 2020 01:53:20 GMT
### Minor changes
- Add support for system selectors in declaration references
## 7.8.22
Fri, 11 Sep 2020 02:13:35 GMT
_Version update only_
## 7.8.21
Mon, 07 Sep 2020 07:37:37 GMT
_Version update only_
## 7.8.20
Sat, 05 Sep 2020 18:56:34 GMT
### Patches
- Fix "Converting circular structure to JSON" error (GitHub #2152)
## 7.8.19
Thu, 27 Aug 2020 11:27:06 GMT
_Version update only_
## 7.8.18
Mon, 24 Aug 2020 07:35:20 GMT
_Version update only_
## 7.8.17
Sat, 22 Aug 2020 05:55:43 GMT
_Version update only_
## 7.8.16
Tue, 18 Aug 2020 23:59:42 GMT
_Version update only_
## 7.8.15
Mon, 17 Aug 2020 04:53:23 GMT
_Version update only_
## 7.8.14
Wed, 12 Aug 2020 00:10:05 GMT
### Patches
- Updated project to build with Heft
## 7.8.13
Wed, 05 Aug 2020 18:27:33 GMT
_Version update only_
## 7.8.12
Fri, 03 Jul 2020 15:09:04 GMT
_Version update only_
## 7.8.11
Thu, 25 Jun 2020 06:43:35 GMT
_Version update only_
## 7.8.10
Wed, 24 Jun 2020 09:50:48 GMT
_Version update only_
## 7.8.9
Wed, 24 Jun 2020 09:04:28 GMT
_Version update only_
## 7.8.8
Wed, 10 Jun 2020 20:48:30 GMT
_Version update only_
## 7.8.7
Sat, 30 May 2020 02:59:54 GMT
_Version update only_
## 7.8.6
Thu, 28 May 2020 05:59:02 GMT
_Version update only_
## 7.8.5
Wed, 27 May 2020 05:15:11 GMT
_Version update only_
## 7.8.4
Tue, 26 May 2020 23:00:25 GMT
_Version update only_
## 7.8.3
Fri, 22 May 2020 15:08:43 GMT
_Version update only_
## 7.8.2
Thu, 21 May 2020 23:09:44 GMT
_Version update only_
## 7.8.1
Thu, 21 May 2020 15:42:00 GMT
_Version update only_
## 7.8.0
Wed, 06 May 2020 08:23:45 GMT
### Minor changes
- Enable canonicalReference to ApiItem lookup
## 7.7.11
Wed, 08 Apr 2020 04:07:33 GMT
_Version update only_
## 7.7.10
Sat, 28 Mar 2020 00:37:16 GMT
### Patches
- Upgrade to TSdoc 0.12.19
## 7.7.9
Wed, 18 Mar 2020 15:07:47 GMT
### Patches
- Upgrade cyclic dependencies
## 7.7.8
Tue, 17 Mar 2020 23:55:58 GMT
### Patches
- Replace dependencies whose NPM scope was renamed from `@microsoft` to `@rushstack`
## 7.7.7
Tue, 28 Jan 2020 02:23:44 GMT
_Version update only_
## 7.7.6
Thu, 23 Jan 2020 01:07:56 GMT
_Version update only_
## 7.7.5
Tue, 21 Jan 2020 21:56:14 GMT
_Version update only_
## 7.7.4
Sun, 19 Jan 2020 02:26:52 GMT
### Patches
- Upgrade Node typings to Node 10
## 7.7.3
Fri, 17 Jan 2020 01:08:23 GMT
_Version update only_
## 7.7.2
Thu, 09 Jan 2020 06:44:13 GMT
_Version update only_
## 7.7.1
Wed, 08 Jan 2020 00:11:31 GMT
_Version update only_
## 7.7.0
Tue, 03 Dec 2019 03:17:43 GMT
### Minor changes
- Improve declaration reference syntax to allow linking to overloaded functions/methods
## 7.6.0
Sun, 24 Nov 2019 00:54:04 GMT
### Minor changes
- Added support for `@throws`
## 7.5.6
Fri, 15 Nov 2019 04:50:50 GMT
_Version update only_
## 7.5.5
Mon, 11 Nov 2019 16:07:56 GMT
_Version update only_
## 7.5.4
Tue, 05 Nov 2019 06:49:28 GMT
### Patches
- Fix an issue where API reports sometimes were ordered differently depending on the version of NodeJS (GitHub #1552)
## 7.5.3
Tue, 05 Nov 2019 01:08:39 GMT
### Patches
- Clarified an error message
## 7.5.2
Tue, 22 Oct 2019 06:24:44 GMT
### Patches
- Refactor some code as part of migration from TSLint to ESLint
## 7.5.1
Sun, 29 Sep 2019 23:56:29 GMT
### Patches
- Update repository URL
## 7.5.0
Wed, 25 Sep 2019 15:15:31 GMT
### Minor changes
- Add ApiItem.getMergedSiblings() API
## 7.4.2
Mon, 23 Sep 2019 15:14:55 GMT
### Patches
- Remove unnecessary dependency on @types/node
## 7.4.1
Tue, 10 Sep 2019 22:32:23 GMT
### Patches
- Update documentation
## 7.4.0
Tue, 10 Sep 2019 20:38:33 GMT
### Minor changes
- Add 'canonicalReference' to ExcerptToken
## 7.3.4
Wed, 04 Sep 2019 18:28:06 GMT
_Version update only_
## 7.3.3
Wed, 04 Sep 2019 15:15:37 GMT
### Patches
- Update TSDoc dependency to 0.12.14
## 7.3.2
Thu, 08 Aug 2019 15:14:17 GMT
_Version update only_
## 7.3.1
Thu, 08 Aug 2019 00:49:05 GMT
### Patches
- (Experimental) Add ApiExtractor.canonicalReference which is a beta implementation of the revised TSDoc declaration reference notation
## 7.3.0
Mon, 22 Jul 2019 19:13:10 GMT
### Minor changes
- Rename `ApiItem.canonicalReference` to `.containerKey`; rename `ApiItemContainerMixin.tryGetMember()` to `.tryGetMemberByKey()`; rename `Api___.getCanonicalReference()` to `.getContainerKey()`
## 7.2.0
Tue, 11 Jun 2019 00:48:06 GMT
### Minor changes
- Add API support for type parameters and type alias types
### Patches
- Improve the .api.json deserializer to validate the schema version and support backwards compatibility
## 7.1.3
Wed, 05 Jun 2019 19:12:34 GMT
### Patches
- Fix an issue where TSDoc index selectors (ApiParameterListMixin.overloadIndex) started from 0, whereas TSDoc requires a nonzero number
## 7.1.2
Tue, 04 Jun 2019 05:51:53 GMT
### Patches
- Fix an issue where ApiConstructor inherited from ApiStaticMixin, but TypeScript constructors cannot be static
## 7.1.1
Mon, 27 May 2019 04:13:44 GMT
### Patches
- Make the strings returned by ApiItem.displayName less verbose
- Improve formatting of the strings returned by ApiItem.getScopedNameWithinPackage()
## 7.1.0
Tue, 16 Apr 2019 11:01:37 GMT
### Minor changes
- Initial stable release of API Extractor 7
## 7.0.28
Wed, 20 Mar 2019 19:14:49 GMT
_Version update only_
## 7.0.27
Mon, 18 Mar 2019 04:28:43 GMT
### Patches
- Add helper functions for ReleaseTag
- Export IApiItemConstructor to eliminate the ae-forgotten-export warning
## 7.0.26
Wed, 13 Mar 2019 19:13:14 GMT
### Patches
- Refactor code to move the IndentedWriter API from api-extractor-model to api-documenter
## 7.0.25
Wed, 13 Mar 2019 01:14:05 GMT
### Patches
- Upgrade TSDoc
## 7.0.24
Mon, 11 Mar 2019 16:13:36 GMT
### Patches
- Initial setup of new package @microsoft/api-extractor-model

View File

@@ -0,0 +1,24 @@
@microsoft/api-extractor
Copyright (c) Microsoft Corporation. All rights reserved.
MIT License
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@@ -0,0 +1,67 @@
# @microsoft/api-extractor-model
Use this library to read and write \*.api.json files as defined by the [API Extractor](https://api-extractor.com/) tool.
These files are used to generate a documentation website for your TypeScript package. The files store the
API signatures and doc comments that were extracted from your package.
API documentation for this package: https://rushstack.io/pages/api/api-extractor-model/
## Example Usage
The following code sample shows how to load `example.api.json`, which would be generated by API Extractor
when it analyzes a hypothetical NPM package called `example`:
```ts
import { ApiModel, ApiPackage } from '@discordjs/api-extractor-model';
const apiModel: ApiModel = new ApiModel();
const apiPackage: ApiPackage = apiModel.loadPackage('example.api.json');
for (const member of apiPackage.members) {
console.log(member.displayName);
}
```
The `ApiModel` is acts as a container for various packages that are loaded and operated on as a group.
For example, a documentation tool may need to resolve `@link` references across different packages.
In this case we would load the various packages into the `ApiModel`, and then use
the `ApiModel.resolveDeclarationReference()` to resolve the `@link` targets.
The data structure forms a tree of various classes that start with the `Api` prefix. The nesting hierarchy
might look like this:
```
- ApiModel
- ApiPackage
- ApiEntryPoint
- ApiClass
- ApiMethod
- ApiProperty
- ApiEnum
- ApiEnumMember
- ApiInterface
- ApiMethodSignature
- ApiPropertySignature
- ApiNamespace
- (ApiClass, ApiEnum, ApiInterface, ...)
```
You can use the `ApiItem.members` property to traverse this tree.
Note that the non-abstract classes (e.g. `ApiClass`, `ApiEnum`, `ApiInterface`, etc.) use
TypeScript "mixin" functions (e.g. `ApiDeclaredItem`, `ApiItemContainerMixin`, etc.) to add various
features that cannot be represented as a normal inheritance chain (since TypeScript does not allow a child class
to extend more than one base class). The "mixin" is a TypeScript merged declaration with three components:
the function that generates a subclass, an interface that describes the members of the subclass, and
a namespace containing static members of the class.
> For a complete project that uses these APIs to generate an API reference web site,
> see the [@microsoft/api-documenter](https://www.npmjs.com/package/@microsoft/api-documenter) source code.
## Links
- [CHANGELOG.md](https://github.com/microsoft/rushstack/blob/main/libraries/api-extractor-model/CHANGELOG.md) - Find
out what's new in the latest version
- [API Reference](https://rushstack.io/pages/api/api-extractor-model/)
API Extractor is part of the [Rush Stack](https://rushstack.io/) family of projects.

View File

@@ -0,0 +1,20 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/api-extractor/v7/api-extractor.schema.json",
"mainEntryPointFilePath": "<projectFolder>/lib/index.d.ts",
"apiReport": {
"enabled": true,
"reportFolder": "../../../common/reviews/api"
},
"docModel": {
"enabled": true,
"apiJsonFilePath": "../../../common/temp/api/<unscopedPackageName>.api.json"
},
"dtsRollup": {
"enabled": true,
"untrimmedFilePath": "<projectFolder>/dist/rollup.d.ts"
}
}

View File

@@ -0,0 +1,11 @@
{
"extends": "@rushstack/heft-node-rig/profiles/default/config/jest.config.json",
// Enable code coverage for Jest
"collectCoverage": true,
"coverageDirectory": "<rootDir>/coverage",
"coverageReporters": ["cobertura", "html"],
// Use v8 coverage provider to avoid Babel
"coverageProvider": "v8"
}

View File

@@ -0,0 +1,49 @@
{
"name": "@discordjs/api-extractor-model",
"version": "7.28.2",
"description": "A helper library for loading and saving the .api.json files created by API Extractor",
"private": true,
"repository": {
"type": "git",
"url": "https://github.com/discordjs/discord.js.git",
"directory": "packages/api-extractor-model"
},
"homepage": "https://discord.js.org",
"license": "MIT",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
"scripts": {
"build": "tsc --noEmit && tsup",
"lint": "prettier --check . && cross-env TIMING=1 eslint --format=pretty src",
"format": "prettier --write . && cross-env TIMING=1 eslint --fix --format=pretty src"
},
"exports": {
".": {
"require": {
"types": "./dist/index.d.ts",
"default": "./dist/index.js"
},
"import": {
"types": "./dist/index.d.mts",
"default": "./dist/index.mjs"
}
}
},
"dependencies": {
"@microsoft/tsdoc": "0.14.2",
"@microsoft/tsdoc-config": "0.16.2",
"@rushstack/node-core-library": "3.61.0"
},
"devDependencies": {
"@types/node": "^18.18.8",
"@types/jest": "^29.5.7",
"cross-env": "^7.0.3",
"eslint": "^8.53.0",
"eslint-config-neon": "^0.1.57",
"eslint-formatter-pretty": "^5.0.0",
"jest": "^29.7.0",
"prettier": "^3.0.3",
"tsup": "^7.2.0",
"turbo": "^1.10.16"
}
}

View File

@@ -0,0 +1,68 @@
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
// See LICENSE in the project root for license information.
import { TSDocConfiguration, TSDocTagDefinition, TSDocTagSyntaxKind, StandardTags } from '@microsoft/tsdoc';
/**
* @internal
* @deprecated - tsdoc configuration is now constructed from tsdoc.json files associated with each package.
*/
export class AedocDefinitions {
public static readonly betaDocumentation: TSDocTagDefinition = new TSDocTagDefinition({
tagName: '@betaDocumentation',
syntaxKind: TSDocTagSyntaxKind.ModifierTag,
});
public static readonly internalRemarks: TSDocTagDefinition = new TSDocTagDefinition({
tagName: '@internalRemarks',
syntaxKind: TSDocTagSyntaxKind.BlockTag,
});
public static readonly preapprovedTag: TSDocTagDefinition = new TSDocTagDefinition({
tagName: '@preapproved',
syntaxKind: TSDocTagSyntaxKind.ModifierTag,
});
public static get tsdocConfiguration(): TSDocConfiguration {
if (!AedocDefinitions._tsdocConfiguration) {
const configuration: TSDocConfiguration = new TSDocConfiguration();
configuration.addTagDefinitions(
[AedocDefinitions.betaDocumentation, AedocDefinitions.internalRemarks, AedocDefinitions.preapprovedTag],
true,
);
configuration.setSupportForTags(
[
StandardTags.alpha,
StandardTags.beta,
StandardTags.decorator,
StandardTags.defaultValue,
StandardTags.deprecated,
StandardTags.eventProperty,
StandardTags.example,
StandardTags.inheritDoc,
StandardTags.internal,
StandardTags.link,
StandardTags.override,
StandardTags.packageDocumentation,
StandardTags.param,
StandardTags.privateRemarks,
StandardTags.public,
StandardTags.readonly,
StandardTags.remarks,
StandardTags.returns,
StandardTags.sealed,
StandardTags.throws,
StandardTags.virtual,
],
true,
);
AedocDefinitions._tsdocConfiguration = configuration;
}
return AedocDefinitions._tsdocConfiguration;
}
private static _tsdocConfiguration: TSDocConfiguration | undefined;
}

View File

@@ -0,0 +1,88 @@
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
// See LICENSE in the project root for license information.
/**
* A "release tag" is a custom TSDoc tag that is applied to an API to communicate the level of support
* provided for third-party developers.
*
* @remarks
*
* The four release tags are: `@internal`, `@alpha`, `@beta`, and `@public`. They are applied to API items such
* as classes, member functions, enums, etc. The release tag applies recursively to members of a container
* (e.g. class or interface). For example, if a class is marked as `@beta`, then all of its members automatically
* have this status; you DON'T need add the `@beta` tag to each member function. However, you could add
* `@internal` to a member function to give it a different release status.
* @public
*/
export enum ReleaseTag {
/**
* No release tag was specified in the AEDoc summary.
*/
None = 0,
/**
* Indicates that an API item is meant only for usage by other NPM packages from the same
* maintainer. Third parties should never use "internal" APIs. (To emphasize this, their
* names are prefixed by underscores.)
*/
Internal = 1,
/**
* Indicates that an API item is eventually intended to be public, but currently is in an
* early stage of development. Third parties should not use "alpha" APIs.
*/
Alpha = 2,
/**
* Indicates that an API item has been released in an experimental state. Third parties are
* encouraged to try it and provide feedback. However, a "beta" API should NOT be used
* in production.
*/
Beta = 3,
/**
* Indicates that an API item has been officially released. It is part of the supported
* contract (e.g. SemVer) for a package.
*/
Public = 4,
}
/**
* Helper functions for working with the `ReleaseTag` enum.
*
* @public
*/
// export namespace ReleaseTag {
/**
* Returns the TSDoc tag name for a `ReleaseTag` value.
*
* @remarks
* For example, `getTagName(ReleaseTag.Internal)` would return the string `@internal`.
*/
export function getTagName(releaseTag: ReleaseTag): string {
switch (releaseTag) {
case ReleaseTag.None:
return '(none)';
case ReleaseTag.Internal:
return '@internal';
case ReleaseTag.Alpha:
return '@alpha';
case ReleaseTag.Beta:
return '@beta';
case ReleaseTag.Public:
return '@public';
default:
throw new Error('Unsupported release tag');
}
}
/**
* Compares two `ReleaseTag` values. Their values must not be `ReleaseTag.None`.
*
* @returns 0 if `a` and `b` are equal, a positive number if `a` is more public than `b`,
* and a negative number if `a` is less public than `b`.
* @remarks
* For example, `compareReleaseTag(ReleaseTag.Beta, ReleaseTag.Alpha)` will return a positive
* number because beta is more public than alpha.
*/
export function compare(a: ReleaseTag, b: ReleaseTag): number {
return a - b;
}
// }

View File

@@ -0,0 +1,84 @@
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
// See LICENSE in the project root for license information.
/**
* Use this library to read and write *.api.json files as defined by the
* {@link https://api-extractor.com/ | API Extractor} tool. These files are used to generate a documentation
* website for your TypeScript package. The files store the API signatures and doc comments that were extracted
* from your package.
*
* @packageDocumentation
*/
export { AedocDefinitions } from './aedoc/AedocDefinitions.js';
export { ReleaseTag, compare as releaseTagCompare, getTagName as releaseTagGetTagName } from './aedoc/ReleaseTag.js';
// items
export { type IApiDeclaredItemOptions, ApiDeclaredItem } from './items/ApiDeclaredItem.js';
export { type IApiDocumentedItemOptions, ApiDocumentedItem } from './items/ApiDocumentedItem.js';
export { ApiItemKind, type IApiItemOptions, ApiItem, type IApiItemConstructor } from './items/ApiItem.js';
export { type IApiPropertyItemOptions, ApiPropertyItem } from './items/ApiPropertyItem.js';
// mixins
export {
type IApiParameterListMixinOptions,
type IApiParameterOptions,
ApiParameterListMixin,
} from './mixins/ApiParameterListMixin.js';
export {
type IApiTypeParameterOptions,
type IApiTypeParameterListMixinOptions,
ApiTypeParameterListMixin,
} from './mixins/ApiTypeParameterListMixin.js';
export { type IApiAbstractMixinOptions, ApiAbstractMixin } from './mixins/ApiAbstractMixin.js';
export { type IApiItemContainerMixinOptions, ApiItemContainerMixin } from './mixins/ApiItemContainerMixin.js';
export { type IApiProtectedMixinOptions, ApiProtectedMixin } from './mixins/ApiProtectedMixin.js';
export { type IApiReleaseTagMixinOptions, ApiReleaseTagMixin } from './mixins/ApiReleaseTagMixin.js';
export { type IApiReturnTypeMixinOptions, ApiReturnTypeMixin } from './mixins/ApiReturnTypeMixin.js';
export { type IApiStaticMixinOptions, ApiStaticMixin } from './mixins/ApiStaticMixin.js';
export { type IApiNameMixinOptions, ApiNameMixin } from './mixins/ApiNameMixin.js';
export { type IApiOptionalMixinOptions, ApiOptionalMixin } from './mixins/ApiOptionalMixin.js';
export { type IApiReadonlyMixinOptions, ApiReadonlyMixin } from './mixins/ApiReadonlyMixin.js';
export { type IApiInitializerMixinOptions, ApiInitializerMixin } from './mixins/ApiInitializerMixin.js';
export { type IApiExportedMixinOptions, ApiExportedMixin } from './mixins/ApiExportedMixin.js';
export {
type IFindApiItemsResult,
type IFindApiItemsMessage,
FindApiItemsMessageId,
} from './mixins/IFindApiItemsResult.js';
export {
ExcerptTokenKind,
type IExcerptTokenRange,
type IExcerptToken,
ExcerptToken,
Excerpt,
} from './mixins/Excerpt.js';
export type { Constructor, PropertiesOf } from './mixins/Mixin.js';
// model
export { type IApiCallSignatureOptions, ApiCallSignature } from './model/ApiCallSignature.js';
export { type IApiClassOptions, ApiClass } from './model/ApiClass.js';
export { type IApiConstructorOptions, ApiConstructor } from './model/ApiConstructor.js';
export { type IApiConstructSignatureOptions, ApiConstructSignature } from './model/ApiConstructSignature.js';
export { type IApiEntryPointOptions, ApiEntryPoint } from './model/ApiEntryPoint.js';
export { type IApiEnumOptions, ApiEnum } from './model/ApiEnum.js';
export { type IApiEnumMemberOptions, ApiEnumMember, EnumMemberOrder } from './model/ApiEnumMember.js';
export { type IApiFunctionOptions, ApiFunction } from './model/ApiFunction.js';
export { type IApiIndexSignatureOptions, ApiIndexSignature } from './model/ApiIndexSignature.js';
export { type IApiInterfaceOptions, ApiInterface } from './model/ApiInterface.js';
export { type IApiMethodOptions, ApiMethod } from './model/ApiMethod.js';
export { type IApiMethodSignatureOptions, ApiMethodSignature } from './model/ApiMethodSignature.js';
export { ApiModel } from './model/ApiModel.js';
export { type IApiNamespaceOptions, ApiNamespace } from './model/ApiNamespace.js';
export { type IApiPackageOptions, ApiPackage, type IApiPackageSaveOptions } from './model/ApiPackage.js';
export { type IParameterOptions, Parameter } from './model/Parameter.js';
export { type IApiPropertyOptions, ApiProperty } from './model/ApiProperty.js';
export { type IApiPropertySignatureOptions, ApiPropertySignature } from './model/ApiPropertySignature.js';
export { type IApiTypeAliasOptions, ApiTypeAlias } from './model/ApiTypeAlias.js';
export { type ITypeParameterOptions, TypeParameter } from './model/TypeParameter.js';
export { type IApiVariableOptions, ApiVariable } from './model/ApiVariable.js';
export type { IResolveDeclarationReferenceResult } from './model/ModelReferenceResolver.js';
export { HeritageType } from './model/HeritageType.js';
export { type ISourceLocationOptions, SourceLocation } from './model/SourceLocation.js';
export { Navigation, Meaning } from './items/ApiItem.js';

View File

@@ -0,0 +1,225 @@
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
// See LICENSE in the project root for license information.
import { DeclarationReference } from '@microsoft/tsdoc/lib-commonjs/beta/DeclarationReference.js';
import { Excerpt, ExcerptToken, type IExcerptTokenRange, type IExcerptToken } from '../mixins/Excerpt.js';
import type { DeserializerContext } from '../model/DeserializerContext.js';
import { SourceLocation } from '../model/SourceLocation.js';
import { ApiDocumentedItem, type IApiDocumentedItemJson, type IApiDocumentedItemOptions } from './ApiDocumentedItem.js';
import type { ApiItem } from './ApiItem.js';
/**
* Constructor options for {@link ApiDeclaredItem}.
*
* @public
*/
export interface IApiDeclaredItemOptions extends IApiDocumentedItemOptions {
excerptTokens: IExcerptToken[];
fileColumn?: number | undefined;
fileLine?: number | undefined;
fileUrlPath?: string | undefined;
}
export interface IApiDeclaredItemJson extends IApiDocumentedItemJson {
excerptTokens: IExcerptToken[];
fileColumn?: number;
fileLine?: number;
fileUrlPath?: string | undefined;
}
/**
* The base class for API items that have an associated source code excerpt containing a TypeScript declaration.
*
* @remarks
*
* This is part of the {@link ApiModel} hierarchy of classes, which are serializable representations of
* API declarations.
*
* Most `ApiItem` subclasses have declarations and thus extend `ApiDeclaredItem`. Counterexamples include
* `ApiModel` and `ApiPackage`, which do not have any corresponding TypeScript source code.
* @public
*/
export class ApiDeclaredItem extends ApiDocumentedItem {
private readonly _excerptTokens: ExcerptToken[];
private readonly _excerpt: Excerpt;
private readonly _fileUrlPath?: string | undefined;
private readonly _fileLine?: number | undefined;
private readonly _fileColumn?: number | undefined;
private _sourceLocation?: SourceLocation;
public constructor(options: IApiDeclaredItemOptions) {
super(options);
this._excerptTokens = options.excerptTokens.map((token) => {
const canonicalReference: DeclarationReference | undefined =
token.canonicalReference === undefined ? undefined : DeclarationReference.parse(token.canonicalReference);
return new ExcerptToken(token.kind, token.text, canonicalReference);
});
this._excerpt = new Excerpt(this.excerptTokens, { startIndex: 0, endIndex: this.excerptTokens.length });
this._fileUrlPath = options.fileUrlPath;
this._fileLine = options.fileLine;
this._fileColumn = options.fileColumn;
}
/**
* @override
*/
public static override onDeserializeInto(
options: Partial<IApiDeclaredItemOptions>,
context: DeserializerContext,
jsonObject: IApiDeclaredItemJson,
): void {
super.onDeserializeInto(options, context, jsonObject);
options.excerptTokens = jsonObject.excerptTokens;
options.fileUrlPath = jsonObject.fileUrlPath;
options.fileLine = jsonObject.fileLine;
options.fileColumn = jsonObject.fileColumn;
}
/**
* The source code excerpt where the API item is declared.
*/
public get excerpt(): Excerpt {
return this._excerpt;
}
/**
* The individual source code tokens that comprise the main excerpt.
*/
public get excerptTokens(): readonly ExcerptToken[] {
return this._excerptTokens;
}
/**
* The file URL path relative to the `projectFolder` and `projectFolderURL` fields
* as defined in the `api-extractor.json` config. Is `undefined` if the path is
* the same as the parent API item's.
*/
public get fileUrlPath(): string | undefined {
return this._fileUrlPath;
}
/**
* The line in the `fileUrlPath` where the API item is declared.
*/
public get fileLine(): number | undefined {
return this._fileLine;
}
/**
* The column in the `fileUrlPath` where the API item is declared.
*/
public get fileColumn(): number | undefined {
return this._fileColumn;
}
/**
* Returns the source location where the API item is declared.
*/
public get sourceLocation(): SourceLocation {
if (!this._sourceLocation) {
this._sourceLocation = this._buildSourceLocation();
}
return this._sourceLocation;
}
/**
* If the API item has certain important modifier tags such as `@sealed`, `@virtual`, or `@override`,
* this prepends them as a doc comment above the excerpt.
*/
public getExcerptWithModifiers(): string {
const excerpt: string = this.excerpt.text;
const modifierTags: string[] = [];
if (excerpt.length > 0 && this instanceof ApiDocumentedItem) {
if (this.tsdocComment) {
if (this.tsdocComment.modifierTagSet.isSealed()) {
modifierTags.push('@sealed');
}
if (this.tsdocComment.modifierTagSet.isVirtual()) {
modifierTags.push('@virtual');
}
if (this.tsdocComment.modifierTagSet.isOverride()) {
modifierTags.push('@override');
}
}
if (modifierTags.length > 0) {
return '/** ' + modifierTags.join(' ') + ' */\n' + excerpt;
}
}
return excerpt;
}
/**
* @override
*/
public override serializeInto(jsonObject: Partial<IApiDeclaredItemJson>): void {
super.serializeInto(jsonObject);
jsonObject.excerptTokens = this.excerptTokens.map((x) => {
const excerptToken: IExcerptToken = { kind: x.kind, text: x.text };
if (x.canonicalReference !== undefined) {
excerptToken.canonicalReference = x.canonicalReference.toString();
}
return excerptToken;
});
// Only serialize this API item's file URL path if it exists and it's different from its parent's
// (a little optimization to keep the doc model succinct).
if (
this.fileUrlPath &&
(!(this.parent instanceof ApiDeclaredItem) || this.fileUrlPath !== this.parent.fileUrlPath)
) {
jsonObject.fileUrlPath = this.fileUrlPath;
}
if (this.fileLine) {
jsonObject.fileLine = this.fileLine;
}
if (this.fileColumn) {
jsonObject.fileColumn = this.fileColumn;
}
}
/**
* Constructs a new {@link Excerpt} corresponding to the provided token range.
*/
public buildExcerpt(tokenRange: IExcerptTokenRange): Excerpt {
return new Excerpt(this.excerptTokens, tokenRange);
}
/**
* Builds the cached object used by the `sourceLocation` property.
*/
private _buildSourceLocation(): SourceLocation {
const projectFolderUrl: string | undefined = this.getAssociatedPackage()?.projectFolderUrl;
let fileUrlPath: string | undefined;
for (let current: ApiItem | undefined = this; current !== undefined; current = current.parent) {
if (current instanceof ApiDeclaredItem && current.fileUrlPath) {
fileUrlPath = current.fileUrlPath;
break;
}
}
return new SourceLocation({
projectFolderUrl,
fileUrlPath,
sourceFileColumn: this.fileColumn,
sourceFileLine: this.fileLine,
});
}
}

View File

@@ -0,0 +1,78 @@
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
// See LICENSE in the project root for license information.
import * as tsdoc from '@microsoft/tsdoc';
import type { DeserializerContext } from '../model/DeserializerContext.js';
import { ApiItem, type IApiItemOptions, type IApiItemJson } from './ApiItem.js';
/**
* Constructor options for {@link ApiDocumentedItem}.
*
* @public
*/
export interface IApiDocumentedItemOptions extends IApiItemOptions {
docComment: tsdoc.DocComment | undefined;
}
export interface IApiDocumentedItemJson extends IApiItemJson {
docComment: string;
}
/**
* An abstract base class for API declarations that can have an associated TSDoc comment.
*
* @remarks
*
* This is part of the {@link ApiModel} hierarchy of classes, which are serializable representations of
* API declarations.
* @public
*/
export class ApiDocumentedItem extends ApiItem {
private readonly _tsdocComment: tsdoc.DocComment | undefined;
public constructor(options: IApiDocumentedItemOptions) {
super(options);
this._tsdocComment = options.docComment;
}
/**
* @override
*/
public static override onDeserializeInto(
options: Partial<IApiDocumentedItemOptions>,
context: DeserializerContext,
jsonObject: IApiItemJson,
): void {
super.onDeserializeInto(options, context, jsonObject);
const documentedJson: IApiDocumentedItemJson = jsonObject as IApiDocumentedItemJson;
if (documentedJson.docComment) {
const tsdocParser: tsdoc.TSDocParser = new tsdoc.TSDocParser(context.tsdocConfiguration);
// NOTE: For now, we ignore TSDoc errors found in a serialized .api.json file.
// Normally these errors would have already been reported by API Extractor during analysis.
// However, they could also arise if the JSON file was edited manually, or if the file was saved
// using a different release of the software that used an incompatible syntax.
const parserContext: tsdoc.ParserContext = tsdocParser.parseString(documentedJson.docComment);
options.docComment = parserContext.docComment;
}
}
public get tsdocComment(): tsdoc.DocComment | undefined {
return this._tsdocComment;
}
/**
* @override
*/
public override serializeInto(jsonObject: Partial<IApiDocumentedItemJson>): void {
super.serializeInto(jsonObject);
if (this.tsdocComment === undefined) {
jsonObject.docComment = '';
} else {
jsonObject.docComment = this.tsdocComment.emitAsTsdoc();
}
}
}

View File

@@ -0,0 +1,367 @@
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
// See LICENSE in the project root for license information.
import type { DeclarationReference } from '@microsoft/tsdoc/lib-commonjs/beta/DeclarationReference.js';
import { InternalError } from '@rushstack/node-core-library';
import { ApiItemContainerMixin } from '../mixins/ApiItemContainerMixin.js';
import { ApiParameterListMixin } from '../mixins/ApiParameterListMixin.js';
import type { Constructor, PropertiesOf } from '../mixins/Mixin.js';
import type { ApiModel } from '../model/ApiModel.js';
import type { ApiPackage } from '../model/ApiPackage.js';
import type { DeserializerContext } from '../model/DeserializerContext.js';
/**
* The type returned by the {@link ApiItem.kind} property, which can be used to easily distinguish subclasses of
* {@link ApiItem}.
*
* @public
*/
export enum ApiItemKind {
CallSignature = 'CallSignature',
Class = 'Class',
ConstructSignature = 'ConstructSignature',
Constructor = 'Constructor',
EntryPoint = 'EntryPoint',
Enum = 'Enum',
EnumMember = 'EnumMember',
Function = 'Function',
IndexSignature = 'IndexSignature',
Interface = 'Interface',
Method = 'Method',
MethodSignature = 'MethodSignature',
Model = 'Model',
Namespace = 'Namespace',
None = 'None',
Package = 'Package',
Property = 'Property',
PropertySignature = 'PropertySignature',
TypeAlias = 'TypeAlias',
Variable = 'Variable',
}
/**
* Indicates the symbol table from which to resolve the next symbol component.
*
* @beta
*/
export enum Navigation {
Exports = '.',
Locals = '~',
Members = '#',
}
/**
* @beta
*/
export enum Meaning {
CallSignature = 'call',
Class = 'class',
ComplexType = 'complex',
ConstructSignature = 'new',
Constructor = 'constructor',
Enum = 'enum',
Event = 'event',
Function = 'function',
IndexSignature = 'index',
Interface = 'interface',
Member = 'member',
Namespace = 'namespace',
TypeAlias = 'type',
Variable = 'var',
}
/**
* Constructor options for {@link ApiItem}.
*
* @public
*/
// eslint-disable-next-line @typescript-eslint/no-empty-interface
export interface IApiItemOptions {}
export interface IApiItemJson {
canonicalReference: string;
kind: ApiItemKind;
}
// PRIVATE - Allows ApiItemContainerMixin to assign the parent.
//
export const apiItem_onParentChanged: unique symbol = Symbol('ApiItem._onAddToContainer');
/**
* The abstract base class for all members of an `ApiModel` object.
*
* @remarks
* This is part of the {@link ApiModel} hierarchy of classes, which are serializable representations of
* API declarations.
* @public
*/
export class ApiItem {
private _canonicalReference: DeclarationReference | undefined;
private _parent: ApiItem | undefined;
// eslint-disable-next-line @typescript-eslint/no-useless-constructor
public constructor(_options: IApiItemOptions) {
// ("options" is not used here, but part of the inheritance pattern)
}
public static deserialize(jsonObject: IApiItemJson, context: DeserializerContext): ApiItem {
// The Deserializer class is coupled with a ton of other classes, so we delay loading it
// to avoid ES5 circular imports.
// eslint-disable-next-line @typescript-eslint/consistent-type-imports, @typescript-eslint/no-require-imports, @typescript-eslint/no-var-requires
const deserializerModule: typeof import('../model/Deserializer') = require('../model/Deserializer');
return deserializerModule.Deserializer.deserialize(context, jsonObject);
}
/**
* @virtual
*/
public static onDeserializeInto(
_options: Partial<IApiItemOptions>,
_context: DeserializerContext,
_jsonObject: IApiItemJson,
): void {
// (implemented by subclasses)
}
/**
* @virtual
*/
public serializeInto(jsonObject: Partial<IApiItemJson>): void {
jsonObject.kind = this.kind;
jsonObject.canonicalReference = this.canonicalReference.toString();
}
/**
* Identifies the subclass of the `ApiItem` base class.
*
* @virtual
*/
public get kind(): ApiItemKind {
throw new Error('ApiItem.kind was not implemented by the child class');
}
/**
* Warning: This API is used internally by API extractor but is not yet ready for general usage.
*
* @remarks
*
* Returns a `DeclarationReference` object using the experimental new declaration reference notation.
* @beta
*/
public get canonicalReference(): DeclarationReference {
if (!this._canonicalReference) {
try {
this._canonicalReference = this.buildCanonicalReference();
} catch (error) {
const name: string = this.getScopedNameWithinPackage() || this.displayName;
throw new InternalError(`Error building canonical reference for ${name}:\n` + (error as Error).message);
}
}
return this._canonicalReference;
}
/**
* Returns a string key that can be used to efficiently retrieve an `ApiItem` from an `ApiItemContainerMixin`.
* The key is unique within the container. Its format is undocumented and may change at any time.
*
* @remarks
* Use the `getContainerKey()` static member to construct the key. Each subclass has a different implementation
* of this function, according to the aspects that are important for identifying it.
* @virtual
*/
public get containerKey(): string {
throw new InternalError('ApiItem.containerKey was not implemented by the child class');
}
/**
* Returns a name for this object that can be used in diagnostic messages, for example.
*
* @remarks
* For an object that inherits ApiNameMixin, this will return the declared name (e.g. the name of a TypeScript
* function). Otherwise, it will return a string such as "(call signature)" or "(model)".
* @virtual
*/
public get displayName(): string {
switch (this.kind) {
case ApiItemKind.CallSignature:
return '(call)';
case ApiItemKind.Constructor:
return '(constructor)';
case ApiItemKind.ConstructSignature:
return '(new)';
case ApiItemKind.IndexSignature:
return '(indexer)';
case ApiItemKind.Model:
return '(model)';
default:
return '(???)'; // All other types should inherit ApiNameMixin which will override this property
}
}
/**
* If this item was added to a ApiItemContainerMixin item, then this returns the container item.
* If this is an Parameter that was added to a method or function, then this returns the function item.
* Otherwise, it returns undefined.
*
* @virtual
*/
public get parent(): ApiItem | undefined {
return this._parent;
}
/**
* This property supports a visitor pattern for walking the tree.
* For items with ApiItemContainerMixin, it returns the contained items, sorted alphabetically.
* Otherwise it returns an empty array.
*
* @virtual
*/
public get members(): readonly 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(): readonly 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.
*/
public getHierarchy(): readonly ApiItem[] {
const hierarchy: ApiItem[] = [];
for (let current: ApiItem | undefined = this; current !== undefined; current = current.parent) {
hierarchy.push(current);
}
hierarchy.reverse();
return hierarchy;
}
/**
* This returns a scoped name such as `"Namespace1.Namespace2.MyClass.myMember()"`. It does not include the
* package name or entry point.
*
* @remarks
* If called on an ApiEntrypoint, ApiPackage, or ApiModel item, the result is an empty string.
*/
public getScopedNameWithinPackage(): string {
const reversedParts: string[] = [];
for (let current: ApiItem | undefined = this; current !== undefined; current = current.parent) {
if (
current.kind === ApiItemKind.Model ||
current.kind === ApiItemKind.Package ||
current.kind === ApiItemKind.EntryPoint
) {
break;
}
if (reversedParts.length === 0) {
switch (current.kind) {
case ApiItemKind.CallSignature:
case ApiItemKind.ConstructSignature:
case ApiItemKind.Constructor:
case ApiItemKind.IndexSignature:
// These functional forms don't have a proper name, so we don't append the "()" suffix
break;
default:
if (ApiParameterListMixin.isBaseClassOf(current)) {
reversedParts.push('()');
}
}
} else {
reversedParts.push('.');
}
reversedParts.push(current.displayName);
}
return reversedParts.reverse().join('');
}
/**
* If this item is an ApiPackage or has an ApiPackage as one of its parents, then that object is returned.
* Otherwise undefined is returned.
*/
public getAssociatedPackage(): ApiPackage | undefined {
for (let current: ApiItem | undefined = this; current !== undefined; current = current.parent) {
if (current.kind === ApiItemKind.Package) {
return current as ApiPackage;
}
}
return undefined;
}
/**
* If this item is an ApiModel or has an ApiModel as one of its parents, then that object is returned.
* Otherwise undefined is returned.
*/
public getAssociatedModel(): ApiModel | undefined {
for (let current: ApiItem | undefined = this; current !== undefined; current = current.parent) {
if (current.kind === ApiItemKind.Model) {
return current as ApiModel;
}
}
return undefined;
}
/**
* A text string whose value determines the sort order that is automatically applied by the
* {@link (ApiItemContainerMixin:interface)} class.
*
* @remarks
* The value of this string is undocumented and may change at any time.
* If {@link (ApiItemContainerMixin:interface).preserveMemberOrder} is enabled for the `ApiItem`'s parent,
* then no sorting is performed, and this key is not used.
* @virtual
*/
public getSortKey(): string {
return this.containerKey;
}
/**
* PRIVATE
*
* @privateRemarks
* Allows ApiItemContainerMixin to assign the parent when the item is added to a container.
* @internal
*/
public [apiItem_onParentChanged](parent: ApiItem | undefined): void {
this._parent = parent;
this._canonicalReference = undefined;
}
/**
* Builds the cached object used by the `canonicalReference` property.
*
* @virtual
*/
protected buildCanonicalReference(): DeclarationReference {
throw new InternalError('ApiItem.canonicalReference was not implemented by the child class');
}
}
/**
* This abstraction is used by the mixin pattern.
* It describes a class type that inherits from {@link ApiItem}.
*
* @public
*/
export interface IApiItemConstructor extends Constructor<ApiItem>, PropertiesOf<typeof ApiItem> {}

View File

@@ -0,0 +1,88 @@
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
// See LICENSE in the project root for license information.
import { type IApiNameMixinOptions, ApiNameMixin } from '../mixins/ApiNameMixin.js';
import { ApiOptionalMixin, type IApiOptionalMixinOptions } from '../mixins/ApiOptionalMixin.js';
import { ApiReadonlyMixin, type IApiReadonlyMixinOptions } from '../mixins/ApiReadonlyMixin.js';
import { ApiReleaseTagMixin, type IApiReleaseTagMixinOptions } from '../mixins/ApiReleaseTagMixin.js';
import type { Excerpt, IExcerptTokenRange } from '../mixins/Excerpt.js';
import type { DeserializerContext } from '../model/DeserializerContext.js';
import { type IApiDeclaredItemOptions, ApiDeclaredItem, type IApiDeclaredItemJson } from './ApiDeclaredItem.js';
/**
* Constructor options for {@link ApiPropertyItem}.
*
* @public
*/
export interface IApiPropertyItemOptions
extends IApiNameMixinOptions,
IApiReleaseTagMixinOptions,
IApiOptionalMixinOptions,
IApiReadonlyMixinOptions,
IApiDeclaredItemOptions {
propertyTypeTokenRange: IExcerptTokenRange;
}
export interface IApiPropertyItemJson extends IApiDeclaredItemJson {
propertyTypeTokenRange: IExcerptTokenRange;
}
/**
* The abstract base class for {@link ApiProperty} and {@link ApiPropertySignature}.
*
* @public
*/
export class ApiPropertyItem extends ApiNameMixin(
ApiReleaseTagMixin(ApiOptionalMixin(ApiReadonlyMixin(ApiDeclaredItem))),
) {
/**
* An {@link Excerpt} that describes the type of the property.
*/
public readonly propertyTypeExcerpt: Excerpt;
public constructor(options: IApiPropertyItemOptions) {
super(options);
this.propertyTypeExcerpt = this.buildExcerpt(options.propertyTypeTokenRange);
}
/**
* @override
*/
public static override onDeserializeInto(
options: Partial<IApiPropertyItemOptions>,
context: DeserializerContext,
jsonObject: IApiPropertyItemJson,
): void {
super.onDeserializeInto(options, context, jsonObject);
options.propertyTypeTokenRange = jsonObject.propertyTypeTokenRange;
}
/**
* Returns true if this property should be documented as an event.
*
* @remarks
* The `@eventProperty` TSDoc modifier can be added to readonly properties to indicate that they return an
* event object that event handlers can be attached to. The event-handling API is implementation-defined, but
* typically the return type would be a class with members such as `addHandler()` and `removeHandler()`.
* The documentation should display such properties under an "Events" heading instead of the
* usual "Properties" heading.
*/
public get isEventProperty(): boolean {
if (this.tsdocComment) {
return this.tsdocComment.modifierTagSet.isEventProperty();
}
return false;
}
/**
* @override
*/
public override serializeInto(jsonObject: Partial<IApiPropertyItemJson>): void {
super.serializeInto(jsonObject);
jsonObject.propertyTypeTokenRange = this.propertyTypeExcerpt.tokenRange;
}
}

View File

@@ -0,0 +1,115 @@
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
// See LICENSE in the project root for license information.
import type { ApiItem, IApiItemJson, IApiItemConstructor, IApiItemOptions } from '../items/ApiItem.js';
import type { DeserializerContext } from '../model/DeserializerContext.js';
/**
* Constructor options for {@link (ApiAbstractMixin:interface)}.
*
* @public
*/
export interface IApiAbstractMixinOptions extends IApiItemOptions {
isAbstract: boolean;
}
export interface IApiAbstractMixinJson extends IApiItemJson {
isAbstract: boolean;
}
const _isAbstract: unique symbol = Symbol('ApiAbstractMixin._isAbstract');
/**
* The mixin base class for API items that have an abstract modifier.
*
* @remarks
*
* This is part of the {@link ApiModel} hierarchy of classes, which are serializable representations of
* API declarations. The non-abstract classes (e.g. `ApiClass`, `ApiEnum`, `ApiInterface`, etc.) use
* TypeScript "mixin" functions (e.g. `ApiDeclaredItem`, `ApiItemContainerMixin`, etc.) to add various
* features that cannot be represented as a normal inheritance chain (since TypeScript does not allow a child class
* to extend more than one base class). The "mixin" is a TypeScript merged declaration with three components:
* the function that generates a subclass, an interface that describes the members of the subclass, and
* a namespace containing static members of the class.
* @public
*/
export interface ApiAbstractMixin extends ApiItem {
/**
* Indicates that the API item's value has an 'abstract' modifier.
*/
readonly isAbstract: boolean;
serializeInto(jsonObject: Partial<IApiItemJson>): void;
}
/**
* Mixin function for {@link (ApiAbstractMixin:interface)}.
*
* @param baseClass - The base class to be extended
* @returns A child class that extends baseClass, adding the {@link (ApiAbstractMixin:interface)}
* functionality.
* @public
*/
export function ApiAbstractMixin<TBaseClass extends IApiItemConstructor>(
baseClass: TBaseClass,
): TBaseClass & (new (...args: any[]) => ApiAbstractMixin) {
class MixedClass extends baseClass implements ApiAbstractMixin {
public [_isAbstract]: boolean;
public constructor(...args: any[]) {
super(...args);
const options: IApiAbstractMixinOptions = args[0];
this[_isAbstract] = options.isAbstract;
}
/**
* @override
*/
public static override onDeserializeInto(
options: Partial<IApiAbstractMixinOptions>,
context: DeserializerContext,
jsonObject: IApiAbstractMixinJson,
): void {
baseClass.onDeserializeInto(options, context, jsonObject);
options.isAbstract = jsonObject.isAbstract || false;
}
public get isAbstract(): boolean {
return this[_isAbstract];
}
/**
* @override
*/
public override serializeInto(jsonObject: Partial<IApiAbstractMixinJson>): void {
super.serializeInto(jsonObject);
jsonObject.isAbstract = this.isAbstract;
}
}
return MixedClass;
}
/**
* Static members for {@link (ApiAbstractMixin:interface)}.
*
* @public
*/
export namespace ApiAbstractMixin {
/**
* A type guard that tests whether the specified `ApiItem` subclass extends the `ApiAbstractMixin` mixin.
*
* @remarks
*
* The JavaScript `instanceof` operator cannot be used to test for mixin inheritance, because each invocation of
* the mixin function produces a different subclass. (This could be mitigated by `Symbol.hasInstance`, however
* the TypeScript type system cannot invoke a runtime test.)
*/
export function isBaseClassOf(apiItem: ApiItem): apiItem is ApiAbstractMixin {
return apiItem.hasOwnProperty(_isAbstract);
}
}

View File

@@ -0,0 +1,143 @@
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
// See LICENSE in the project root for license information.
import { DeclarationReference } from '@microsoft/tsdoc/lib-commonjs/beta/DeclarationReference.js';
import type { ApiItem, IApiItemJson, IApiItemConstructor, IApiItemOptions } from '../items/ApiItem.js';
import { Navigation } from '../items/ApiItem.js';
import type { DeserializerContext } from '../model/DeserializerContext.js';
/**
* Constructor options for {@link (IApiExportedMixinOptions:interface)}.
*
* @public
*/
export interface IApiExportedMixinOptions extends IApiItemOptions {
isExported: boolean;
}
export interface IApiExportedMixinJson extends IApiItemJson {
isExported: boolean;
}
const _isExported: unique symbol = Symbol('ApiExportedMixin._isExported');
/**
* The mixin base class for API items that can be exported.
*
* @remarks
*
* This is part of the {@link ApiModel} hierarchy of classes, which are serializable representations of
* API declarations. The non-abstract classes (e.g. `ApiClass`, `ApiEnum`, `ApiInterface`, etc.) use
* TypeScript "mixin" functions (e.g. `ApiDeclaredItem`, `ApiItemContainerMixin`, etc.) to add various
* features that cannot be represented as a normal inheritance chain (since TypeScript does not allow a child class
* to extend more than one base class). The "mixin" is a TypeScript merged declaration with three components:
* the function that generates a subclass, an interface that describes the members of the subclass, and
* a namespace containing static members of the class.
* @public
*/
export interface ApiExportedMixin extends ApiItem {
/**
* Whether the declaration is exported from its parent item container (i.e. either an `ApiEntryPoint` or an
* `ApiNamespace`).
*
* @remarks
* Suppose `index.ts` is your entry point:
*
* ```ts
* // index.ts
*
* export class A {}
* class B {}
*
* namespace n {
* export class C {}
* class D {}
* }
*
* // file.ts
* export class E {}
* ```
*
* Classes `A` and `C` are both exported, while classes `B`, `D`, and `E` are not. `E` is exported from its
* local file, but not from its parent item container (i.e. the entry point).
*/
readonly isExported: boolean;
/**
* @override
*/
serializeInto(jsonObject: Partial<IApiItemJson>): void;
}
/**
* Mixin function for {@link (ApiExportedMixin:interface)}.
*
* @param baseClass - The base class to be extended
* @returns A child class that extends baseClass, adding the {@link (ApiExportedMixin:interface)} functionality.
* @public
*/
export function ApiExportedMixin<TBaseClass extends IApiItemConstructor>(
baseClass: TBaseClass,
): TBaseClass & (new (...args: any[]) => ApiExportedMixin) {
class MixedClass extends baseClass implements ApiExportedMixin {
public [_isExported]: boolean;
public constructor(...args: any[]) {
super(...args);
const options: IApiExportedMixinOptions = args[0];
this[_isExported] = options.isExported;
}
/**
* @override
*/
public static override onDeserializeInto(
options: Partial<IApiExportedMixinOptions>,
context: DeserializerContext,
jsonObject: IApiExportedMixinJson,
): void {
baseClass.onDeserializeInto(options, context, jsonObject);
const declarationReference: DeclarationReference = DeclarationReference.parse(jsonObject.canonicalReference);
options.isExported = declarationReference.navigation === (Navigation.Exports as any); // ambient const enums suck...
}
public get isExported(): boolean {
return this[_isExported];
}
/**
* The `isExported` property is intentionally not serialized because the information is already present
* in the item's `canonicalReference`.
*
* @override
*/
public override serializeInto(jsonObject: Partial<IApiExportedMixinJson>): void {
super.serializeInto(jsonObject);
}
}
return MixedClass;
}
/**
* Static members for {@link (ApiExportedMixin:interface)}.
*
* @public
*/
export namespace ApiExportedMixin {
/**
* A type guard that tests whether the specified `ApiItem` subclass extends the `ApiExportedMixin` mixin.
*
* @remarks
*
* The JavaScript `instanceof` operator cannot be used to test for mixin inheritance, because each invocation of
* the mixin function produces a different subclass. (This could be mitigated by `Symbol.hasInstance`, however
* the TypeScript type system cannot invoke a runtime test.)
*/
export function isBaseClassOf(apiItem: ApiItem): apiItem is ApiExportedMixin {
return apiItem.hasOwnProperty(_isExported);
}
}

View File

@@ -0,0 +1,130 @@
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
// See LICENSE in the project root for license information.
import { InternalError } from '@rushstack/node-core-library';
import { ApiDeclaredItem } from '../items/ApiDeclaredItem.js';
import type { ApiItem, IApiItemJson, IApiItemConstructor, IApiItemOptions } from '../items/ApiItem.js';
import type { DeserializerContext } from '../model/DeserializerContext.js';
import type { IExcerptTokenRange, Excerpt } from './Excerpt.js';
/**
* Constructor options for {@link (IApiInitializerMixinOptions:interface)}.
*
* @public
*/
export interface IApiInitializerMixinOptions extends IApiItemOptions {
initializerTokenRange?: IExcerptTokenRange | undefined;
}
export interface IApiInitializerMixinJson extends IApiItemJson {
initializerTokenRange?: IExcerptTokenRange;
}
const _initializerExcerpt: unique symbol = Symbol('ApiInitializerMixin._initializerExcerpt');
/**
* The mixin base class for API items that can have an initializer.
*
* @remarks
*
* This is part of the {@link ApiModel} hierarchy of classes, which are serializable representations of
* API declarations. The non-abstract classes (e.g. `ApiClass`, `ApiEnum`, `ApiInterface`, etc.) use
* TypeScript "mixin" functions (e.g. `ApiDeclaredItem`, `ApiItemContainerMixin`, etc.) to add various
* features that cannot be represented as a normal inheritance chain (since TypeScript does not allow a child class
* to extend more than one base class). The "mixin" is a TypeScript merged declaration with three components:
* the function that generates a subclass, an interface that describes the members of the subclass, and
* a namespace containing static members of the class.
* @public
*/
export interface ApiInitializerMixin extends ApiItem {
/**
* An {@link Excerpt} that describes the item's initializer.
*/
readonly initializerExcerpt?: Excerpt | undefined;
/**
* @override
*/
serializeInto(jsonObject: Partial<IApiInitializerMixinJson>): void;
}
/**
* Mixin function for {@link (ApiInitializerMixin:interface)}.
*
* @param baseClass - The base class to be extended
* @returns A child class that extends baseClass, adding the {@link (ApiInitializerMixin:interface)} functionality.
* @public
*/
export function ApiInitializerMixin<TBaseClass extends IApiItemConstructor>(
baseClass: TBaseClass,
): TBaseClass & (new (...args: any[]) => ApiInitializerMixin) {
class MixedClass extends baseClass implements ApiInitializerMixin {
public [_initializerExcerpt]?: Excerpt;
public constructor(...args: any[]) {
super(...args);
const options: IApiInitializerMixinOptions = args[0];
if (this instanceof ApiDeclaredItem) {
if (options.initializerTokenRange) {
this[_initializerExcerpt] = this.buildExcerpt(options.initializerTokenRange);
}
} else {
throw new InternalError('ApiInitializerMixin expects a base class that inherits from ApiDeclaredItem');
}
}
/**
* @override
*/
public static override onDeserializeInto(
options: Partial<IApiInitializerMixinOptions>,
context: DeserializerContext,
jsonObject: IApiInitializerMixinJson,
): void {
baseClass.onDeserializeInto(options, context, jsonObject);
options.initializerTokenRange = jsonObject.initializerTokenRange;
}
public get initializerExcerpt(): Excerpt | undefined {
return this[_initializerExcerpt];
}
/**
* @override
*/
public override serializeInto(jsonObject: Partial<IApiInitializerMixinJson>): void {
super.serializeInto(jsonObject);
// Note that JSON does not support the "undefined" value, so we simply omit the field entirely if it is undefined
if (this.initializerExcerpt) {
jsonObject.initializerTokenRange = this.initializerExcerpt.tokenRange;
}
}
}
return MixedClass;
}
/**
* Static members for {@link (ApiInitializerMixin:interface)}.
*
* @public
*/
export namespace ApiInitializerMixin {
/**
* A type guard that tests whether the specified `ApiItem` subclass extends the `ApiInitializerMixin` mixin.
*
* @remarks
*
* The JavaScript `instanceof` operator cannot be used to test for mixin inheritance, because each invocation of
* the mixin function produces a different subclass. (This could be mitigated by `Symbol.hasInstance`, however
* the TypeScript type system cannot invoke a runtime test.)
*/
export function isBaseClassOf(apiItem: ApiItem): apiItem is ApiInitializerMixin {
return apiItem.hasOwnProperty(_initializerExcerpt);
}
}

View File

@@ -0,0 +1,562 @@
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
// See LICENSE in the project root for license information.
import type { DeclarationReference } from '@microsoft/tsdoc/lib-commonjs/beta/DeclarationReference.js';
import { InternalError } from '@rushstack/node-core-library';
import {
ApiItem,
apiItem_onParentChanged,
type IApiItemJson,
type IApiItemOptions,
type IApiItemConstructor,
ApiItemKind,
} from '../items/ApiItem.js';
import type { ApiClass } from '../model/ApiClass.js';
import type { ApiInterface } from '../model/ApiInterface.js';
import type { ApiModel } from '../model/ApiModel.js';
import type { DeserializerContext } from '../model/DeserializerContext.js';
import type { HeritageType } from '../model/HeritageType.js';
import type { IResolveDeclarationReferenceResult } from '../model/ModelReferenceResolver.js';
import { ApiNameMixin } from './ApiNameMixin.js';
import { type ExcerptToken, ExcerptTokenKind } from './Excerpt.js';
import { type IFindApiItemsResult, type IFindApiItemsMessage, FindApiItemsMessageId } from './IFindApiItemsResult.js';
/**
* Constructor options for {@link (ApiItemContainerMixin:interface)}.
*
* @public
*/
export interface IApiItemContainerMixinOptions extends IApiItemOptions {
members?: ApiItem[] | undefined;
preserveMemberOrder?: boolean | undefined;
}
export interface IApiItemContainerJson extends IApiItemJson {
members: IApiItemJson[];
preserveMemberOrder?: boolean;
}
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');
const _preserveMemberOrder: unique symbol = Symbol('ApiItemContainerMixin._preserveMemberOrder');
/**
* The mixin base class for API items that act as containers for other child items.
*
* @remarks
*
* This is part of the {@link ApiModel} hierarchy of classes, which are serializable representations of
* API declarations. The non-abstract classes (e.g. `ApiClass`, `ApiEnum`, `ApiInterface`, etc.) use
* TypeScript "mixin" functions (e.g. `ApiDeclaredItem`, `ApiItemContainerMixin`, etc.) to add various
* features that cannot be represented as a normal inheritance chain (since TypeScript does not allow a child class
* to extend more than one base class). The "mixin" is a TypeScript merged declaration with three components:
* the function that generates a subclass, an interface that describes the members of the subclass, and
* a namespace containing static members of the class.
*
* Examples of `ApiItemContainerMixin` child classes include `ApiModel`, `ApiPackage`, `ApiEntryPoint`,
* and `ApiEnum`. But note that `Parameter` is not considered a "member" of an `ApiMethod`; this relationship
* is modeled using {@link (ApiParameterListMixin:interface).parameters} instead
* of {@link ApiItem.members}.
* @public
*/
export interface ApiItemContainerMixin extends ApiItem {
/**
* For a given member of this container, return its `ApiItem.getMergedSiblings()` list.
*
* @internal
*/
_getMergedSiblingsForMember(memberApiItem: ApiItem): readonly ApiItem[];
/**
* Adds a new member to the container.
*
* @remarks
* An ApiItem cannot be added to more than one container.
*/
addMember(member: ApiItem): void;
/**
* Returns a list of members with the specified name.
*/
findMembersByName(name: string): readonly ApiItem[];
/**
* Finds all of the ApiItem's immediate and inherited members by walking up the inheritance tree.
*
* @remarks
*
* Given the following class heritage:
*
* ```
* export class A {
* public a: number|boolean;
* }
*
* export class B extends A {
* public a: number;
* public b: string;
* }
*
* export class C extends B {
* public c: boolean;
* }
* ```
*
* Calling `findMembersWithInheritance` on `C` will return `B.a`, `B.b`, and `C.c`. Calling the
* method on `B` will return `B.a` and `B.b`. And calling the method on `A` will return just
* `A.a`.
*
* The inherited members returned by this method may be incomplete. If so, there will be a flag
* on the result object indicating this as well as messages explaining the errors in more detail.
* Some scenarios include:
*
* - Interface extending from a type alias.
*
* - Class extending from a variable.
*
* - Extending from a declaration not present in the model (e.g. external package).
*
* - Extending from an unexported declaration (e.g. ae-forgotten-export). Common in mixin
* patterns.
*
* - Unexpected runtime errors...
*
* Lastly, be aware that the types of inherited members are returned with respect to their
* defining class as opposed to with respect to the inheriting class. For example, consider
* the following:
*
* ```
* export class A<T> {
* public a: T;
* }
*
* export class B extends A<number> {}
* ```
*
* When called on `B`, this method will return `B.a` with type `T` as opposed to type
* `number`, although the latter is more accurate.
*/
findMembersWithInheritance(): IFindApiItemsResult;
/**
* Disables automatic sorting of {@link ApiItem.members}.
*
* @remarks
* By default `ApiItemContainerMixin` will automatically sort its members according to their
* {@link ApiItem.getSortKey} string, which provides a standardized mostly alphabetical ordering
* that is appropriate for most API items. When loading older .api.json files the automatic sorting
* is reapplied and may update the ordering.
*
* Set `preserveMemberOrder` to true to disable automatic sorting for this container; instead, the
* members will retain whatever ordering appeared in the {@link IApiItemContainerMixinOptions.members} array.
* The `preserveMemberOrder` option is saved in the .api.json file.
*/
readonly preserveMemberOrder: boolean;
/**
* @override
*/
serializeInto(jsonObject: Partial<IApiItemJson>): void;
/**
* Attempts to retrieve a member using its containerKey, or returns `undefined` if no matching member was found.
*
* @remarks
* Use the `getContainerKey()` static member to construct the key. Each subclass has a different implementation
* of this function, according to the aspects that are important for identifying it.
*
* See {@link ApiItem.containerKey} for more information.
*/
tryGetMemberByKey(containerKey: string): ApiItem | undefined;
}
/**
* Mixin function for {@link ApiDeclaredItem}.
*
* @param baseClass - The base class to be extended
* @returns A child class that extends baseClass, adding the {@link (ApiItemContainerMixin:interface)} functionality.
* @public
*/
export function ApiItemContainerMixin<TBaseClass extends IApiItemConstructor>(
baseClass: TBaseClass,
): TBaseClass & (new (...args: any[]) => ApiItemContainerMixin) {
class MixedClass extends baseClass implements ApiItemContainerMixin {
public readonly [_members]: ApiItem[];
public [_membersSorted]: boolean;
public [_membersByContainerKey]: Map<string, ApiItem>;
public [_preserveMemberOrder]: boolean;
// 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
public constructor(...args: any[]) {
super(...args);
const options: IApiItemContainerMixinOptions = args[0] as IApiItemContainerMixinOptions;
this[_members] = [];
this[_membersSorted] = false;
this[_membersByContainerKey] = new Map<string, ApiItem>();
this[_preserveMemberOrder] = options.preserveMemberOrder ?? false;
if (options.members) {
for (const member of options.members) {
this.addMember(member);
}
}
}
/**
* @override
*/
public static override onDeserializeInto(
options: Partial<IApiItemContainerMixinOptions>,
context: DeserializerContext,
jsonObject: IApiItemContainerJson,
): void {
baseClass.onDeserializeInto(options, context, jsonObject);
options.preserveMemberOrder = jsonObject.preserveMemberOrder;
options.members = [];
for (const memberObject of jsonObject.members) {
options.members.push(ApiItem.deserialize(memberObject, context));
}
}
/**
* @override
*/
public override get members(): readonly ApiItem[] {
if (!this[_membersSorted] && !this[_preserveMemberOrder]) {
this[_members].sort((x, y) => x.getSortKey().localeCompare(y.getSortKey()));
this[_membersSorted] = true;
}
return this[_members];
}
public get preserveMemberOrder(): boolean {
return this[_preserveMemberOrder];
}
public addMember(member: ApiItem): void {
if (this[_membersByContainerKey].has(member.containerKey)) {
throw new Error(
`Another member has already been added with the same name (${member.displayName})` +
` and containerKey (${member.containerKey})`,
);
}
const existingParent: ApiItem | undefined = member.parent;
if (existingParent !== undefined) {
throw new Error(`This item has already been added to another container: "${existingParent.displayName}"`);
}
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);
member[apiItem_onParentChanged](this);
}
public tryGetMemberByKey(containerKey: string): ApiItem | undefined {
return this[_membersByContainerKey].get(containerKey);
}
public findMembersByName(name: string): readonly ApiItem[] {
this._ensureMemberMaps();
return this[_membersByName]!.get(name) ?? [];
}
public findMembersWithInheritance(): IFindApiItemsResult {
const messages: IFindApiItemsMessage[] = [];
let maybeIncompleteResult = false;
// For API items that don't support inheritance, this method just returns the item's
// immediate members.
switch (this.kind) {
case ApiItemKind.Class:
case ApiItemKind.Interface:
break;
default: {
return {
items: this.members.concat(),
messages,
maybeIncompleteResult,
};
}
}
const membersByName: Map<string, ApiItem[]> = new Map();
const membersByKind: Map<ApiItemKind, ApiItem[]> = new Map();
const toVisit: ApiItem[] = [];
let next: ApiItem | undefined = this;
while (next) {
const membersToAdd: ApiItem[] = [];
// For each member, check to see if we've already seen a member with the same name
// previously in the inheritance tree. If so, we know we won't inherit it, and thus
// do not add it to our `membersToAdd` array.
for (const member of next.members) {
// We add the to-be-added members to an intermediate array instead of immediately
// to the maps themselves to support method overloads with the same name.
if (ApiNameMixin.isBaseClassOf(member)) {
if (!membersByName.has(member.name)) {
membersToAdd.push(member);
}
} else if (!membersByKind.has(member.kind)) {
membersToAdd.push(member);
}
}
for (const member of membersToAdd) {
if (ApiNameMixin.isBaseClassOf(member)) {
const members: ApiItem[] = membersByName.get(member.name) ?? [];
members.push(member);
membersByName.set(member.name, members);
} else {
const members: ApiItem[] = membersByKind.get(member.kind) ?? [];
members.push(member);
membersByKind.set(member.kind, members);
}
}
// Interfaces can extend multiple interfaces, so iterate through all of them.
const extendedItems: ApiItem[] = [];
let extendsTypes: readonly HeritageType[] | undefined;
switch (next.kind) {
case ApiItemKind.Class: {
const apiClass: ApiClass = next as ApiClass;
extendsTypes = apiClass.extendsType ? [apiClass.extendsType] : [];
break;
}
case ApiItemKind.Interface: {
const apiInterface: ApiInterface = next as ApiInterface;
extendsTypes = apiInterface.extendsTypes;
break;
}
default:
break;
}
if (extendsTypes === undefined) {
messages.push({
messageId: FindApiItemsMessageId.UnsupportedKind,
text: `Unable to analyze references of API item ${next.displayName} because it is of unsupported kind ${next.kind}`,
});
maybeIncompleteResult = true;
next = toVisit.shift();
continue;
}
for (const extendsType of extendsTypes) {
// We want to find the reference token associated with the actual inherited declaration.
// In every case we support, this is the first reference token. For example:
//
// ```
// export class A extends B {}
// ^
// export class A extends B<C> {}
// ^
// export class A extends B.C {}
// ^^^
// ```
const firstReferenceToken: ExcerptToken | undefined = extendsType.excerpt.spannedTokens.find(
(token: ExcerptToken) => {
return token.kind === ExcerptTokenKind.Reference && token.canonicalReference;
},
);
if (!firstReferenceToken) {
messages.push({
messageId: FindApiItemsMessageId.ExtendsClauseMissingReference,
text: `Unable to analyze extends clause ${extendsType.excerpt.text} of API item ${next.displayName} because no canonical reference was found`,
});
maybeIncompleteResult = true;
continue;
}
const apiModel: ApiModel | undefined = this.getAssociatedModel();
if (!apiModel) {
messages.push({
messageId: FindApiItemsMessageId.NoAssociatedApiModel,
text: `Unable to analyze references of API item ${next.displayName} because it is not associated with an ApiModel`,
});
maybeIncompleteResult = true;
continue;
}
const canonicalReference: DeclarationReference = firstReferenceToken.canonicalReference!;
const apiItemResult: IResolveDeclarationReferenceResult = apiModel.resolveDeclarationReference(
canonicalReference,
undefined,
);
const apiItem: ApiItem | undefined = apiItemResult.resolvedApiItem;
if (!apiItem) {
messages.push({
messageId: FindApiItemsMessageId.DeclarationResolutionFailed,
text: `Unable to resolve declaration reference within API item ${next.displayName}: ${apiItemResult.errorMessage}`,
});
maybeIncompleteResult = true;
continue;
}
extendedItems.push(apiItem);
}
// For classes, this array will only have one item. For interfaces, there may be multiple items. Sort the array
// into alphabetical order before adding to our list of API items to visit. This ensures that in the case
// of multiple interface inheritance, a member inherited from multiple interfaces is attributed to the interface
// earlier in alphabetical order (as opposed to source order).
//
// For example, in the code block below, `Bar.x` is reported as the inherited item, not `Foo.x`.
//
// ```
// interface Foo {
// public x: string;
// }
//
// interface Bar {
// public x: string;
// }
//
// interface FooBar extends Foo, Bar {}
// ```
extendedItems.sort((x: ApiItem, y: ApiItem) => x.getSortKey().localeCompare(y.getSortKey()));
toVisit.push(...extendedItems);
next = toVisit.shift();
}
const items: ApiItem[] = [];
for (const members of membersByName.values()) {
items.push(...members);
}
for (const members of membersByKind.values()) {
items.push(...members);
}
items.sort((x: ApiItem, y: ApiItem) => x.getSortKey().localeCompare(y.getSortKey()));
return {
items,
messages,
maybeIncompleteResult,
};
}
/**
* @internal
*/
public _getMergedSiblingsForMember(memberApiItem: ApiItem): readonly 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 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<ApiItemKind, ApiItem[]> | Map<string, ApiItem[]>;
let key: ApiItemKind | string;
if (ApiNameMixin.isBaseClassOf(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] = membersByName;
this[_membersByKind] = membersByKind;
}
}
/**
* @override
*/
public override serializeInto(jsonObject: Partial<IApiItemContainerJson>): void {
super.serializeInto(jsonObject);
const memberObjects: IApiItemJson[] = [];
for (const member of this.members) {
const memberJsonObject: Partial<IApiItemJson> = {};
member.serializeInto(memberJsonObject);
memberObjects.push(memberJsonObject as IApiItemJson);
}
jsonObject.preserveMemberOrder = this.preserveMemberOrder;
jsonObject.members = memberObjects;
}
}
return MixedClass;
}
/**
* Static members for {@link (ApiItemContainerMixin:interface)}.
*
* @public
*/
export namespace ApiItemContainerMixin {
/**
* A type guard that tests whether the specified `ApiItem` subclass extends the `ApiItemContainerMixin` mixin.
*
* @remarks
*
* The JavaScript `instanceof` operator cannot be used to test for mixin inheritance, because each invocation of
* the mixin function produces a different subclass. (This could be mitigated by `Symbol.hasInstance`, however
* the TypeScript type system cannot invoke a runtime test.)
*/
export function isBaseClassOf(apiItem: ApiItem): apiItem is ApiItemContainerMixin {
return apiItem.hasOwnProperty(_members);
}
}

View File

@@ -0,0 +1,128 @@
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
// See LICENSE in the project root for license information.
import type { ApiItem, IApiItemJson, IApiItemConstructor, IApiItemOptions } from '../items/ApiItem.js';
import type { DeserializerContext } from '../model/DeserializerContext.js';
/**
* Constructor options for {@link (IApiNameMixinOptions:interface)}.
*
* @public
*/
export interface IApiNameMixinOptions extends IApiItemOptions {
name: string;
}
export interface IApiNameMixinJson extends IApiItemJson {
name: string;
}
const _name: unique symbol = Symbol('ApiNameMixin._name');
/**
* The mixin base class for API items that have a name. For example, a class has a name, but a class constructor
* does not.
*
* @remarks
*
* This is part of the {@link ApiModel} hierarchy of classes, which are serializable representations of
* API declarations. The non-abstract classes (e.g. `ApiClass`, `ApiEnum`, `ApiInterface`, etc.) use
* TypeScript "mixin" functions (e.g. `ApiDeclaredItem`, `ApiItemContainerMixin`, etc.) to add various
* features that cannot be represented as a normal inheritance chain (since TypeScript does not allow a child class
* to extend more than one base class). The "mixin" is a TypeScript merged declaration with three components:
* the function that generates a subclass, an interface that describes the members of the subclass, and
* a namespace containing static members of the class.
* @public
*/
export interface ApiNameMixin extends ApiItem {
/**
* The exported name of this API item.
*
* @remarks
* Note that due tue type aliasing, the exported name may be different from the locally declared name.
*/
readonly name: string;
/**
* @override
*/
serializeInto(jsonObject: Partial<IApiItemJson>): void;
}
/**
* Mixin function for {@link (ApiNameMixin:interface)}.
*
* @param baseClass - The base class to be extended
* @returns A child class that extends baseClass, adding the {@link (ApiNameMixin:interface)} functionality.
* @public
*/
export function ApiNameMixin<TBaseClass extends IApiItemConstructor>(
baseClass: TBaseClass,
): TBaseClass & (new (...args: any[]) => ApiNameMixin) {
class MixedClass extends baseClass implements ApiNameMixin {
public readonly [_name]: string;
public constructor(...args: any[]) {
super(...args);
const options: IApiNameMixinOptions = args[0];
this[_name] = options.name;
}
/**
* @override
*/
public static override onDeserializeInto(
options: Partial<IApiNameMixinOptions>,
context: DeserializerContext,
jsonObject: IApiNameMixinJson,
): void {
baseClass.onDeserializeInto(options, context, jsonObject);
options.name = jsonObject.name;
}
public get name(): string {
return this[_name];
}
/**
* @override
*/
public override get displayName(): string {
return this[_name];
}
/**
* @override
*/
public override serializeInto(jsonObject: Partial<IApiNameMixinJson>): void {
super.serializeInto(jsonObject);
jsonObject.name = this.name;
}
}
return MixedClass;
}
/**
* Static members for {@link (ApiNameMixin:interface)}.
*
* @public
*/
export namespace ApiNameMixin {
/**
* A type guard that tests whether the specified `ApiItem` subclass extends the `ApiNameMixin` mixin.
*
* @remarks
*
* The JavaScript `instanceof` operator cannot be used to test for mixin inheritance, because each invocation of
* the mixin function produces a different subclass. (This could be mitigated by `Symbol.hasInstance`, however
* the TypeScript type system cannot invoke a runtime test.)
*/
export function isBaseClassOf(apiItem: ApiItem): apiItem is ApiNameMixin {
return apiItem.hasOwnProperty(_name);
}
}

View File

@@ -0,0 +1,127 @@
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
// See LICENSE in the project root for license information.
import type { ApiItem, IApiItemJson, IApiItemConstructor, IApiItemOptions } from '../items/ApiItem.js';
import type { DeserializerContext } from '../model/DeserializerContext.js';
/**
* Constructor options for {@link (IApiOptionalMixinOptions:interface)}.
*
* @public
*/
export interface IApiOptionalMixinOptions extends IApiItemOptions {
isOptional: boolean;
}
export interface IApiOptionalMixinJson extends IApiItemJson {
isOptional: boolean;
}
const _isOptional: unique symbol = Symbol('ApiOptionalMixin._isOptional');
/**
* The mixin base class for API items that can be marked as optional by appending a `?` to them.
* For example, a property of an interface can be optional.
*
* @remarks
*
* This is part of the {@link ApiModel} hierarchy of classes, which are serializable representations of
* API declarations. The non-abstract classes (e.g. `ApiClass`, `ApiEnum`, `ApiInterface`, etc.) use
* TypeScript "mixin" functions (e.g. `ApiDeclaredItem`, `ApiItemContainerMixin`, etc.) to add various
* features that cannot be represented as a normal inheritance chain (since TypeScript does not allow a child class
* to extend more than one base class). The "mixin" is a TypeScript merged declaration with three components:
* the function that generates a subclass, an interface that describes the members of the subclass, and
* a namespace containing static members of the class.
* @public
*/
export interface ApiOptionalMixin extends ApiItem {
/**
* True if this is an optional property.
*
* @remarks
* For example:
* ```ts
* interface X {
* y: string; // not optional
* z?: string; // optional
* }
* ```
*/
readonly isOptional: boolean;
/**
* @override
*/
serializeInto(jsonObject: Partial<IApiItemJson>): void;
}
/**
* Mixin function for {@link (ApiOptionalMixin:interface)}.
*
* @param baseClass - The base class to be extended
* @returns A child class that extends baseClass, adding the {@link (ApiOptionalMixin:interface)} functionality.
* @public
*/
export function ApiOptionalMixin<TBaseClass extends IApiItemConstructor>(
baseClass: TBaseClass,
): TBaseClass & (new (...args: any[]) => ApiOptionalMixin) {
class MixedClass extends baseClass implements ApiOptionalMixin {
public [_isOptional]: boolean;
public constructor(...args: any[]) {
super(...args);
const options: IApiOptionalMixinOptions = args[0];
this[_isOptional] = Boolean(options.isOptional);
}
/**
* @override
*/
public static override onDeserializeInto(
options: Partial<IApiOptionalMixinOptions>,
context: DeserializerContext,
jsonObject: IApiOptionalMixinJson,
): void {
baseClass.onDeserializeInto(options, context, jsonObject);
options.isOptional = Boolean(jsonObject.isOptional);
}
public get isOptional(): boolean {
return this[_isOptional];
}
/**
* @override
*/
public override serializeInto(jsonObject: Partial<IApiOptionalMixinJson>): void {
super.serializeInto(jsonObject);
jsonObject.isOptional = this.isOptional;
}
}
return MixedClass;
}
/**
* Optional members for {@link (ApiOptionalMixin:interface)}.
*
* @public
*/
export namespace ApiOptionalMixin {
/**
* A type guard that tests whether the specified `ApiItem` subclass extends the `ApiOptionalMixin` mixin.
*
* @remarks
*
* The JavaScript `instanceof` operator cannot be used to test for mixin inheritance, because each invocation of
* the mixin function produces a different subclass. (This could be mitigated by `Symbol.hasInstance`, however
* the TypeScript type system cannot invoke a runtime test.)
*/
export function isBaseClassOf(apiItem: ApiItem): apiItem is ApiOptionalMixin {
return apiItem.hasOwnProperty(_isOptional);
}
}

View File

@@ -0,0 +1,202 @@
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
// See LICENSE in the project root for license information.
import { InternalError } from '@rushstack/node-core-library';
import { ApiDeclaredItem } from '../items/ApiDeclaredItem.js';
import type { ApiItem, IApiItemJson, IApiItemConstructor, IApiItemOptions } from '../items/ApiItem.js';
import type { DeserializerContext } from '../model/DeserializerContext.js';
import { Parameter } from '../model/Parameter.js';
import type { IExcerptTokenRange } from './Excerpt.js';
/**
* Represents parameter information that is part of {@link IApiParameterListMixinOptions}
*
* @public
*/
export interface IApiParameterOptions {
isOptional: boolean;
isRest: boolean;
parameterName: string;
parameterTypeTokenRange: IExcerptTokenRange;
}
/**
* Constructor options for {@link (ApiParameterListMixin:interface)}.
*
* @public
*/
export interface IApiParameterListMixinOptions extends IApiItemOptions {
overloadIndex: number;
parameters: IApiParameterOptions[];
}
export interface IApiParameterListJson extends IApiItemJson {
overloadIndex: number;
parameters: IApiParameterOptions[];
}
const _overloadIndex: unique symbol = Symbol('ApiParameterListMixin._overloadIndex');
const _parameters: unique symbol = Symbol('ApiParameterListMixin._parameters');
/**
* The mixin base class for API items that can have function parameters (but not necessarily a return value).
*
* @remarks
*
* This is part of the {@link ApiModel} hierarchy of classes, which are serializable representations of
* API declarations. The non-abstract classes (e.g. `ApiClass`, `ApiEnum`, `ApiInterface`, etc.) use
* TypeScript "mixin" functions (e.g. `ApiDeclaredItem`, `ApiItemContainerMixin`, etc.) to add various
* features that cannot be represented as a normal inheritance chain (since TypeScript does not allow a child class
* to extend more than one base class). The "mixin" is a TypeScript merged declaration with three components:
* the function that generates a subclass, an interface that describes the members of the subclass, and
* a namespace containing static members of the class.
* @public
*/
export interface ApiParameterListMixin extends ApiItem {
/**
* When a function has multiple overloaded declarations, this one-based integer index can be used to uniquely
* identify them.
*
* @remarks
*
* Consider this overloaded declaration:
*
* ```ts
* export namespace Versioning {
* // TSDoc: Versioning.(addVersions:1)
* export function addVersions(x: number, y: number): number;
*
* // TSDoc: Versioning.(addVersions:2)
* export function addVersions(x: string, y: string): string;
*
* // (implementation)
* export function addVersions(x: number|string, y: number|string): number|string {
* // . . .
* }
* }
* ```
*
* In the above example, there are two overloaded declarations. The overload using numbers will have
* `overloadIndex = 1`. The overload using strings will have `overloadIndex = 2`. The third declaration that
* accepts all possible inputs is considered part of the implementation, and is not processed by API Extractor.
*/
readonly overloadIndex: number;
/**
* The function parameters.
*/
readonly parameters: readonly Parameter[];
serializeInto(jsonObject: Partial<IApiItemJson>): void;
}
/**
* Mixin function for {@link (ApiParameterListMixin:interface)}.
*
* @param baseClass - The base class to be extended
* @returns A child class that extends baseClass, adding the {@link (ApiParameterListMixin:interface)} functionality.
* @public
*/
export function ApiParameterListMixin<TBaseClass extends IApiItemConstructor>(
baseClass: TBaseClass,
): TBaseClass & (new (...args: any[]) => ApiParameterListMixin) {
class MixedClass extends baseClass implements ApiParameterListMixin {
public readonly [_overloadIndex]: number;
public readonly [_parameters]: Parameter[];
public constructor(...args: any[]) {
super(...args);
const options: IApiParameterListMixinOptions = args[0];
this[_overloadIndex] = options.overloadIndex;
this[_parameters] = [];
if (this instanceof ApiDeclaredItem) {
if (options.parameters) {
for (const parameterOptions of options.parameters) {
const parameter: Parameter = new Parameter({
name: parameterOptions.parameterName,
parameterTypeExcerpt: this.buildExcerpt(parameterOptions.parameterTypeTokenRange),
// Prior to ApiJsonSchemaVersion.V_1005 this input will be undefined
isOptional: Boolean(parameterOptions.isOptional),
isRest: Boolean(parameterOptions.isRest),
parent: this,
});
this[_parameters].push(parameter);
}
}
} else {
throw new InternalError('ApiReturnTypeMixin expects a base class that inherits from ApiDeclaredItem');
}
}
/**
* @override
*/
public static override onDeserializeInto(
options: Partial<IApiParameterListMixinOptions>,
context: DeserializerContext,
jsonObject: IApiParameterListJson,
): void {
baseClass.onDeserializeInto(options, context, jsonObject);
options.overloadIndex = jsonObject.overloadIndex;
options.parameters = jsonObject.parameters || [];
}
public get overloadIndex(): number {
return this[_overloadIndex];
}
public get parameters(): readonly Parameter[] {
return this[_parameters];
}
/**
* @override
*/
public override serializeInto(jsonObject: Partial<IApiParameterListJson>): void {
super.serializeInto(jsonObject);
jsonObject.overloadIndex = this.overloadIndex;
const parameterObjects: IApiParameterOptions[] = [];
for (const parameter of this.parameters) {
parameterObjects.push({
parameterName: parameter.name,
parameterTypeTokenRange: parameter.parameterTypeExcerpt.tokenRange,
isOptional: parameter.isOptional,
isRest: parameter.isRest,
});
}
jsonObject.parameters = parameterObjects;
}
}
return MixedClass;
}
/**
* Static members for {@link (ApiParameterListMixin:interface)}.
*
* @public
*/
export namespace ApiParameterListMixin {
/**
* A type guard that tests whether the specified `ApiItem` subclass extends the `ApiParameterListMixin` mixin.
*
* @remarks
*
* The JavaScript `instanceof` operator cannot be used to test for mixin inheritance, because each invocation of
* the mixin function produces a different subclass. (This could be mitigated by `Symbol.hasInstance`, however
* the TypeScript type system cannot invoke a runtime test.)
*/
export function isBaseClassOf(apiItem: ApiItem): apiItem is ApiParameterListMixin {
return apiItem.hasOwnProperty(_parameters);
}
}

View File

@@ -0,0 +1,117 @@
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
// See LICENSE in the project root for license information.
import type { ApiItem, IApiItemJson, IApiItemConstructor, IApiItemOptions } from '../items/ApiItem.js';
import type { DeserializerContext } from '../model/DeserializerContext.js';
/**
* Constructor options for {@link (IApiProtectedMixinOptions:interface)}.
*
* @public
*/
export interface IApiProtectedMixinOptions extends IApiItemOptions {
isProtected: boolean;
}
export interface IApiProtectedMixinJson extends IApiItemJson {
isProtected: boolean;
}
const _isProtected: unique symbol = Symbol('ApiProtectedMixin._isProtected');
/**
* The mixin base class for API items that can have the TypeScript `protected` keyword applied to them.
*
* @remarks
*
* This is part of the {@link ApiModel} hierarchy of classes, which are serializable representations of
* API declarations. The non-abstract classes (e.g. `ApiClass`, `ApiEnum`, `ApiInterface`, etc.) use
* TypeScript "mixin" functions (e.g. `ApiDeclaredItem`, `ApiItemContainerMixin`, etc.) to add various
* features that cannot be represented as a normal inheritance chain (since TypeScript does not allow a child class
* to extend more than one base class). The "mixin" is a TypeScript merged declaration with three components:
* the function that generates a subclass, an interface that describes the members of the subclass, and
* a namespace containing static members of the class.
* @public
*/
export interface ApiProtectedMixin extends ApiItem {
/**
* Whether the declaration has the TypeScript `protected` keyword.
*/
readonly isProtected: boolean;
/**
* @override
*/
serializeInto(jsonObject: Partial<IApiItemJson>): void;
}
/**
* Mixin function for {@link (ApiProtectedMixin:interface)}.
*
* @param baseClass - The base class to be extended
* @returns A child class that extends baseClass, adding the {@link (ApiProtectedMixin:interface)} functionality.
* @public
*/
export function ApiProtectedMixin<TBaseClass extends IApiItemConstructor>(
baseClass: TBaseClass,
): TBaseClass & (new (...args: any[]) => ApiProtectedMixin) {
class MixedClass extends baseClass implements ApiProtectedMixin {
public [_isProtected]: boolean;
public constructor(...args: any[]) {
super(...args);
const options: IApiProtectedMixinOptions = args[0];
this[_isProtected] = options.isProtected;
}
/**
* @override
*/
public static override onDeserializeInto(
options: Partial<IApiProtectedMixinOptions>,
context: DeserializerContext,
jsonObject: IApiProtectedMixinJson,
): void {
baseClass.onDeserializeInto(options, context, jsonObject);
options.isProtected = jsonObject.isProtected;
}
public get isProtected(): boolean {
return this[_isProtected];
}
/**
* @override
*/
public override serializeInto(jsonObject: Partial<IApiProtectedMixinJson>): void {
super.serializeInto(jsonObject);
jsonObject.isProtected = this.isProtected;
}
}
return MixedClass;
}
/**
* Static members for {@link (ApiProtectedMixin:interface)}.
*
* @public
*/
export namespace ApiProtectedMixin {
/**
* A type guard that tests whether the specified `ApiItem` subclass extends the `ApiProtectedMixin` mixin.
*
* @remarks
*
* The JavaScript `instanceof` operator cannot be used to test for mixin inheritance, because each invocation of
* the mixin function produces a different subclass. (This could be mitigated by `Symbol.hasInstance`, however
* the TypeScript type system cannot invoke a runtime test.)
*/
export function isBaseClassOf(apiItem: ApiItem): apiItem is ApiProtectedMixin {
return apiItem.hasOwnProperty(_isProtected);
}
}

View File

@@ -0,0 +1,137 @@
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
// See LICENSE in the project root for license information.
import type { ApiItem, IApiItemJson, IApiItemConstructor, IApiItemOptions } from '../items/ApiItem.js';
import type { DeserializerContext } from '../model/DeserializerContext.js';
/**
* Constructor options for {@link (ApiReadonlyMixin:interface)}.
*
* @public
*/
export interface IApiReadonlyMixinOptions extends IApiItemOptions {
isReadonly: boolean;
}
export interface IApiReadonlyMixinJson extends IApiItemJson {
isReadonly: boolean;
}
const _isReadonly: unique symbol = Symbol('ApiReadonlyMixin._isReadonly');
/**
* The mixin base class for API items that cannot be modified after instantiation.
* Examples such as the readonly modifier and only having a getter but no setter.
*
* @remarks
*
* This is part of the {@link ApiModel} hierarchy of classes, which are serializable representations of
* API declarations. The non-abstract classes (e.g. `ApiClass`, `ApiEnum`, `ApiInterface`, etc.) use
* TypeScript "mixin" functions (e.g. `ApiDeclaredItem`, `ApiItemContainerMixin`, etc.) to add various
* features that cannot be represented as a normal inheritance chain (since TypeScript does not allow a child class
* to extend more than one base class). The "mixin" is a TypeScript merged declaration with three components:
* the function that generates a subclass, an interface that describes the members of the subclass, and
* a namespace containing static members of the class.
* @public
*/
export interface ApiReadonlyMixin extends ApiItem {
/**
* Indicates that the API item's value cannot be assigned by an external consumer.
*
* @remarks
* Examples of API items that would be considered "read only" by API Extractor:
*
* - A class or interface's property that has the `readonly` modifier.
*
* - A variable that has the `const` modifier.
*
* - A property or variable whose TSDoc comment includes the `@readonly` tag.
*
* - A property declaration with a getter but no setter.
*
* Note that if the `readonly` keyword appears in a type annotation, this does not
* guarantee that that the API item will be considered readonly. For example:
*
* ```ts
* declare class C {
* // isReadonly=false in this case, because C.x is assignable
* public x: readonly string[];
* }
* ```
*/
readonly isReadonly: boolean;
serializeInto(jsonObject: Partial<IApiItemJson>): void;
}
/**
* Mixin function for {@link (ApiReadonlyMixin:interface)}.
*
* @param baseClass - The base class to be extended
* @returns A child class that extends baseClass, adding the {@link (ApiReadonlyMixin:interface)}
* functionality.
* @public
*/
export function ApiReadonlyMixin<TBaseClass extends IApiItemConstructor>(
baseClass: TBaseClass,
): TBaseClass & (new (...args: any[]) => ApiReadonlyMixin) {
class MixedClass extends baseClass implements ApiReadonlyMixin {
public [_isReadonly]: boolean;
public constructor(...args: any[]) {
super(...args);
const options: IApiReadonlyMixinOptions = args[0];
this[_isReadonly] = options.isReadonly;
}
/**
* @override
*/
public static override onDeserializeInto(
options: Partial<IApiReadonlyMixinOptions>,
context: DeserializerContext,
jsonObject: IApiReadonlyMixinJson,
): void {
baseClass.onDeserializeInto(options, context, jsonObject);
options.isReadonly = jsonObject.isReadonly || false;
}
public get isReadonly(): boolean {
return this[_isReadonly];
}
/**
* @override
*/
public override serializeInto(jsonObject: Partial<IApiReadonlyMixinJson>): void {
super.serializeInto(jsonObject);
jsonObject.isReadonly = this.isReadonly;
}
}
return MixedClass;
}
/**
* Static members for {@link (ApiReadonlyMixin:interface)}.
*
* @public
*/
export namespace ApiReadonlyMixin {
/**
* A type guard that tests whether the specified `ApiItem` subclass extends the `ApiReadonlyMixin` mixin.
*
* @remarks
*
* The JavaScript `instanceof` operator cannot be used to test for mixin inheritance, because each invocation of
* the mixin function produces a different subclass. (This could be mitigated by `Symbol.hasInstance`, however
* the TypeScript type system cannot invoke a runtime test.)
*/
export function isBaseClassOf(apiItem: ApiItem): apiItem is ApiReadonlyMixin {
return apiItem.hasOwnProperty(_isReadonly);
}
}

View File

@@ -0,0 +1,132 @@
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
// See LICENSE in the project root for license information.
import { Enum } from '@rushstack/node-core-library';
import { ReleaseTag } from '../aedoc/ReleaseTag.js';
import type { ApiItem, IApiItemJson, IApiItemConstructor, IApiItemOptions } from '../items/ApiItem.js';
import type { DeserializerContext } from '../model/DeserializerContext.js';
/**
* Constructor options for {@link (ApiReleaseTagMixin:interface)}.
*
* @public
*/
export interface IApiReleaseTagMixinOptions extends IApiItemOptions {
releaseTag: ReleaseTag;
}
export interface IApiReleaseTagMixinJson extends IApiItemJson {
releaseTag: string;
}
const _releaseTag: unique symbol = Symbol('ApiReleaseTagMixin._releaseTag');
/**
* The mixin base class for API items that can be attributed with a TSDoc tag such as `@internal`,
* `@alpha`, `@beta`, or `@public`. These "release tags" indicate the support level for an API.
*
* @remarks
*
* This is part of the {@link ApiModel} hierarchy of classes, which are serializable representations of
* API declarations. The non-abstract classes (e.g. `ApiClass`, `ApiEnum`, `ApiInterface`, etc.) use
* TypeScript "mixin" functions (e.g. `ApiDeclaredItem`, `ApiItemContainerMixin`, etc.) to add various
* features that cannot be represented as a normal inheritance chain (since TypeScript does not allow a child class
* to extend more than one base class). The "mixin" is a TypeScript merged declaration with three components:
* the function that generates a subclass, an interface that describes the members of the subclass, and
* a namespace containing static members of the class.
* @public
*/
export interface ApiReleaseTagMixin extends ApiItem {
/**
* The effective release tag for this declaration. If it is not explicitly specified, the value may be
* inherited from a containing declaration.
*
* @remarks
* For example, an `ApiEnumMember` may inherit its release tag from the containing `ApiEnum`.
*/
readonly releaseTag: ReleaseTag;
/**
* @override
*/
serializeInto(jsonObject: Partial<IApiItemJson>): void;
}
/**
* Mixin function for {@link (ApiReleaseTagMixin:interface)}.
*
* @param baseClass - The base class to be extended
* @returns A child class that extends baseClass, adding the {@link (ApiReleaseTagMixin:interface)} functionality.
* @public
*/
export function ApiReleaseTagMixin<TBaseClass extends IApiItemConstructor>(
baseClass: TBaseClass,
): TBaseClass & (new (...args: any[]) => ApiReleaseTagMixin) {
class MixedClass extends baseClass implements ApiReleaseTagMixin {
public [_releaseTag]: ReleaseTag;
public constructor(...args: any[]) {
super(...args);
const options: IApiReleaseTagMixinOptions = args[0];
this[_releaseTag] = options.releaseTag;
}
/**
* @override
*/
public static override onDeserializeInto(
options: Partial<IApiReleaseTagMixinOptions>,
context: DeserializerContext,
jsonObject: IApiReleaseTagMixinJson,
): void {
baseClass.onDeserializeInto(options, context, jsonObject);
const deserializedReleaseTag: ReleaseTag | undefined = Enum.tryGetValueByKey<ReleaseTag>(
ReleaseTag as any,
jsonObject.releaseTag,
);
if (deserializedReleaseTag === undefined) {
throw new Error(`Failed to deserialize release tag ${JSON.stringify(jsonObject.releaseTag)}`);
}
options.releaseTag = deserializedReleaseTag;
}
public get releaseTag(): ReleaseTag {
return this[_releaseTag];
}
/**
* @override
*/
public override serializeInto(jsonObject: Partial<IApiReleaseTagMixinJson>): void {
super.serializeInto(jsonObject);
jsonObject.releaseTag = ReleaseTag[this.releaseTag];
}
}
return MixedClass;
}
/**
* Static members for {@link (ApiReleaseTagMixin:interface)}.
*
* @public
*/
export namespace ApiReleaseTagMixin {
/**
* A type guard that tests whether the specified `ApiItem` subclass extends the `ApiReleaseTagMixin` mixin.
*
* @remarks
*
* The JavaScript `instanceof` operator cannot be used to test for mixin inheritance, because each invocation of
* the mixin function produces a different subclass. (This could be mitigated by `Symbol.hasInstance`, however
* the TypeScript type system cannot invoke a runtime test.)
*/
export function isBaseClassOf(apiItem: ApiItem): apiItem is ApiReleaseTagMixin {
return apiItem.hasOwnProperty(_releaseTag);
}
}

View File

@@ -0,0 +1,125 @@
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
// See LICENSE in the project root for license information.
import { InternalError } from '@rushstack/node-core-library';
import { ApiDeclaredItem } from '../items/ApiDeclaredItem.js';
import type { ApiItem, IApiItemJson, IApiItemConstructor, IApiItemOptions } from '../items/ApiItem.js';
import type { DeserializerContext } from '../model/DeserializerContext.js';
import type { IExcerptTokenRange, Excerpt } from './Excerpt.js';
/**
* Constructor options for {@link (ApiReturnTypeMixin:interface)}.
*
* @public
*/
export interface IApiReturnTypeMixinOptions extends IApiItemOptions {
returnTypeTokenRange: IExcerptTokenRange;
}
export interface IApiReturnTypeMixinJson extends IApiItemJson {
returnTypeTokenRange: IExcerptTokenRange;
}
const _returnTypeExcerpt: unique symbol = Symbol('ApiReturnTypeMixin._returnTypeExcerpt');
/**
* The mixin base class for API items that are functions that return a value.
*
* @remarks
*
* This is part of the {@link ApiModel} hierarchy of classes, which are serializable representations of
* API declarations. The non-abstract classes (e.g. `ApiClass`, `ApiEnum`, `ApiInterface`, etc.) use
* TypeScript "mixin" functions (e.g. `ApiDeclaredItem`, `ApiItemContainerMixin`, etc.) to add various
* features that cannot be represented as a normal inheritance chain (since TypeScript does not allow a child class
* to extend more than one base class). The "mixin" is a TypeScript merged declaration with three components:
* the function that generates a subclass, an interface that describes the members of the subclass, and
* a namespace containing static members of the class.
* @public
*/
export interface ApiReturnTypeMixin extends ApiItem {
/**
* An {@link Excerpt} that describes the type of the function's return value.
*/
readonly returnTypeExcerpt: Excerpt;
/**
* @override
*/
serializeInto(jsonObject: Partial<IApiReturnTypeMixinJson>): void;
}
/**
* Mixin function for {@link (ApiReturnTypeMixin:interface)}.
*
* @param baseClass - The base class to be extended
* @returns A child class that extends baseClass, adding the {@link (ApiReturnTypeMixin:interface)} functionality.
* @public
*/
export function ApiReturnTypeMixin<TBaseClass extends IApiItemConstructor>(
baseClass: TBaseClass,
): TBaseClass & (new (...args: any[]) => ApiReturnTypeMixin) {
class MixedClass extends baseClass implements ApiReturnTypeMixin {
public [_returnTypeExcerpt]: Excerpt;
public constructor(...args: any[]) {
super(...args);
const options: IApiReturnTypeMixinOptions = args[0];
if (this instanceof ApiDeclaredItem) {
this[_returnTypeExcerpt] = this.buildExcerpt(options.returnTypeTokenRange);
} else {
throw new InternalError('ApiReturnTypeMixin expects a base class that inherits from ApiDeclaredItem');
}
}
/**
* @override
*/
public static override onDeserializeInto(
options: Partial<IApiReturnTypeMixinOptions>,
context: DeserializerContext,
jsonObject: IApiReturnTypeMixinJson,
): void {
baseClass.onDeserializeInto(options, context, jsonObject);
options.returnTypeTokenRange = jsonObject.returnTypeTokenRange;
}
public get returnTypeExcerpt(): Excerpt {
return this[_returnTypeExcerpt];
}
/**
* @override
*/
public override serializeInto(jsonObject: Partial<IApiReturnTypeMixinJson>): void {
super.serializeInto(jsonObject);
jsonObject.returnTypeTokenRange = this.returnTypeExcerpt.tokenRange;
}
}
return MixedClass;
}
/**
* Static members for {@link (ApiReturnTypeMixin:interface)}.
*
* @public
*/
export namespace ApiReturnTypeMixin {
/**
* A type guard that tests whether the specified `ApiItem` subclass extends the `ApiReturnTypeMixin` mixin.
*
* @remarks
*
* The JavaScript `instanceof` operator cannot be used to test for mixin inheritance, because each invocation of
* the mixin function produces a different subclass. (This could be mitigated by `Symbol.hasInstance`, however
* the TypeScript type system cannot invoke a runtime test.)
*/
export function isBaseClassOf(apiItem: ApiItem): apiItem is ApiReturnTypeMixin {
return apiItem.hasOwnProperty(_returnTypeExcerpt);
}
}

View File

@@ -0,0 +1,117 @@
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
// See LICENSE in the project root for license information.
import type { ApiItem, IApiItemJson, IApiItemConstructor, IApiItemOptions } from '../items/ApiItem.js';
import type { DeserializerContext } from '../model/DeserializerContext.js';
/**
* Constructor options for {@link (IApiStaticMixinOptions:interface)}.
*
* @public
*/
export interface IApiStaticMixinOptions extends IApiItemOptions {
isStatic: boolean;
}
export interface IApiStaticMixinJson extends IApiItemJson {
isStatic: boolean;
}
const _isStatic: unique symbol = Symbol('ApiStaticMixin._isStatic');
/**
* The mixin base class for API items that can have the TypeScript `static` keyword applied to them.
*
* @remarks
*
* This is part of the {@link ApiModel} hierarchy of classes, which are serializable representations of
* API declarations. The non-abstract classes (e.g. `ApiClass`, `ApiEnum`, `ApiInterface`, etc.) use
* TypeScript "mixin" functions (e.g. `ApiDeclaredItem`, `ApiItemContainerMixin`, etc.) to add various
* features that cannot be represented as a normal inheritance chain (since TypeScript does not allow a child class
* to extend more than one base class). The "mixin" is a TypeScript merged declaration with three components:
* the function that generates a subclass, an interface that describes the members of the subclass, and
* a namespace containing static members of the class.
* @public
*/
export interface ApiStaticMixin extends ApiItem {
/**
* Whether the declaration has the TypeScript `static` keyword.
*/
readonly isStatic: boolean;
/**
* @override
*/
serializeInto(jsonObject: Partial<IApiItemJson>): void;
}
/**
* Mixin function for {@link (ApiStaticMixin:interface)}.
*
* @param baseClass - The base class to be extended
* @returns A child class that extends baseClass, adding the {@link (ApiStaticMixin:interface)} functionality.
* @public
*/
export function ApiStaticMixin<TBaseClass extends IApiItemConstructor>(
baseClass: TBaseClass,
): TBaseClass & (new (...args: any[]) => ApiStaticMixin) {
class MixedClass extends baseClass implements ApiStaticMixin {
public [_isStatic]: boolean;
public constructor(...args: any[]) {
super(...args);
const options: IApiStaticMixinOptions = args[0];
this[_isStatic] = options.isStatic;
}
/**
* @override
*/
public static override onDeserializeInto(
options: Partial<IApiStaticMixinOptions>,
context: DeserializerContext,
jsonObject: IApiStaticMixinJson,
): void {
baseClass.onDeserializeInto(options, context, jsonObject);
options.isStatic = jsonObject.isStatic;
}
public get isStatic(): boolean {
return this[_isStatic];
}
/**
* @override
*/
public override serializeInto(jsonObject: Partial<IApiStaticMixinJson>): void {
super.serializeInto(jsonObject);
jsonObject.isStatic = this.isStatic;
}
}
return MixedClass;
}
/**
* Static members for {@link (ApiStaticMixin:interface)}.
*
* @public
*/
export namespace ApiStaticMixin {
/**
* A type guard that tests whether the specified `ApiItem` subclass extends the `ApiStaticMixin` mixin.
*
* @remarks
*
* The JavaScript `instanceof` operator cannot be used to test for mixin inheritance, because each invocation of
* the mixin function produces a different subclass. (This could be mitigated by `Symbol.hasInstance`, however
* the TypeScript type system cannot invoke a runtime test.)
*/
export function isBaseClassOf(apiItem: ApiItem): apiItem is ApiStaticMixin {
return apiItem.hasOwnProperty(_isStatic);
}
}

View File

@@ -0,0 +1,161 @@
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
// See LICENSE in the project root for license information.
import { InternalError } from '@rushstack/node-core-library';
import { ApiDeclaredItem } from '../items/ApiDeclaredItem.js';
import type { ApiItem, IApiItemJson, IApiItemConstructor, IApiItemOptions } from '../items/ApiItem.js';
import type { DeserializerContext } from '../model/DeserializerContext.js';
import { TypeParameter } from '../model/TypeParameter.js';
import type { Excerpt, IExcerptTokenRange } from './Excerpt.js';
/**
* Represents parameter information that is part of {@link IApiTypeParameterListMixinOptions}
*
* @public
*/
export interface IApiTypeParameterOptions {
constraintTokenRange: IExcerptTokenRange;
defaultTypeTokenRange: IExcerptTokenRange;
typeParameterName: string;
}
/**
* Constructor options for {@link (ApiTypeParameterListMixin:interface)}.
*
* @public
*/
export interface IApiTypeParameterListMixinOptions extends IApiItemOptions {
typeParameters: IApiTypeParameterOptions[];
}
export interface IApiTypeParameterListMixinJson extends IApiItemJson {
typeParameters: IApiTypeParameterOptions[];
}
const _typeParameters: unique symbol = Symbol('ApiTypeParameterListMixin._typeParameters');
/**
* The mixin base class for API items that can have type parameters.
*
* @remarks
*
* This is part of the {@link ApiModel} hierarchy of classes, which are serializable representations of
* API declarations. The non-abstract classes (e.g. `ApiClass`, `ApiEnum`, `ApiInterface`, etc.) use
* TypeScript "mixin" functions (e.g. `ApiDeclaredItem`, `ApiItemContainerMixin`, etc.) to add various
* features that cannot be represented as a normal inheritance chain (since TypeScript does not allow a child class
* to extend more than one base class). The "mixin" is a TypeScript merged declaration with three components:
* the function that generates a subclass, an interface that describes the members of the subclass, and
* a namespace containing static members of the class.
* @public
*/
export interface ApiTypeParameterListMixin extends ApiItem {
serializeInto(jsonObject: Partial<IApiItemJson>): void;
/**
* The type parameters.
*/
readonly typeParameters: readonly TypeParameter[];
}
/**
* Mixin function for {@link (ApiTypeParameterListMixin:interface)}.
*
* @param baseClass - The base class to be extended
* @returns A child class that extends baseClass, adding the {@link (ApiTypeParameterListMixin:interface)}
* functionality.
* @public
*/
export function ApiTypeParameterListMixin<TBaseClass extends IApiItemConstructor>(
baseClass: TBaseClass,
): TBaseClass & (new (...args: any[]) => ApiTypeParameterListMixin) {
class MixedClass extends baseClass implements ApiTypeParameterListMixin {
public readonly [_typeParameters]: TypeParameter[];
public constructor(...args: any[]) {
super(...args);
const options: IApiTypeParameterListMixinOptions = args[0];
this[_typeParameters] = [];
if (this instanceof ApiDeclaredItem) {
if (options.typeParameters) {
for (const typeParameterOptions of options.typeParameters) {
const defaultTypeExcerpt: Excerpt = this.buildExcerpt(typeParameterOptions.defaultTypeTokenRange);
const typeParameter: TypeParameter = new TypeParameter({
name: typeParameterOptions.typeParameterName,
constraintExcerpt: this.buildExcerpt(typeParameterOptions.constraintTokenRange),
defaultTypeExcerpt,
isOptional: !defaultTypeExcerpt.isEmpty,
parent: this,
});
this[_typeParameters].push(typeParameter);
}
}
} else {
throw new InternalError('ApiTypeParameterListMixin expects a base class that inherits from ApiDeclaredItem');
}
}
/**
* @override
*/
public static override onDeserializeInto(
options: Partial<IApiTypeParameterListMixinOptions>,
context: DeserializerContext,
jsonObject: IApiTypeParameterListMixinJson,
): void {
baseClass.onDeserializeInto(options, context, jsonObject);
options.typeParameters = jsonObject.typeParameters || [];
}
public get typeParameters(): readonly TypeParameter[] {
return this[_typeParameters];
}
/**
* @override
*/
public override serializeInto(jsonObject: Partial<IApiTypeParameterListMixinJson>): void {
super.serializeInto(jsonObject);
const typeParameterObjects: IApiTypeParameterOptions[] = [];
for (const typeParameter of this.typeParameters) {
typeParameterObjects.push({
typeParameterName: typeParameter.name,
constraintTokenRange: typeParameter.constraintExcerpt.tokenRange,
defaultTypeTokenRange: typeParameter.defaultTypeExcerpt.tokenRange,
});
}
if (typeParameterObjects.length > 0) {
jsonObject.typeParameters = typeParameterObjects;
}
}
}
return MixedClass;
}
/**
* Static members for {@link (ApiTypeParameterListMixin:interface)}.
*
* @public
*/
export namespace ApiTypeParameterListMixin {
/**
* A type guard that tests whether the specified `ApiItem` subclass extends the `ApiParameterListMixin` mixin.
*
* @remarks
*
* The JavaScript `instanceof` operator cannot be used to test for mixin inheritance, because each invocation of
* the mixin function produces a different subclass. (This could be mitigated by `Symbol.hasInstance`, however
* the TypeScript type system cannot invoke a runtime test.)
*/
export function isBaseClassOf(apiItem: ApiItem): apiItem is ApiTypeParameterListMixin {
return apiItem.hasOwnProperty(_typeParameters);
}
}

View File

@@ -0,0 +1,173 @@
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
// See LICENSE in the project root for license information.
import type { DeclarationReference } from '@microsoft/tsdoc/lib-commonjs/beta/DeclarationReference';
import { Text } from '@rushstack/node-core-library';
/**
* @public
*/
export enum ExcerptTokenKind {
/**
* Generic text without any special properties
*/
Content = 'Content',
/**
* A reference to an API declaration
*/
Reference = 'Reference',
}
/**
* Used by {@link Excerpt} to indicate a range of indexes within an array of `ExcerptToken` objects.
*
* @public
*/
export interface IExcerptTokenRange {
/**
* The index of the last member of the span, plus one.
*
* @remarks
*
* If `startIndex` and `endIndex` are the same number, then the span is empty.
*/
endIndex: number;
/**
* The starting index of the span.
*/
startIndex: number;
}
/**
* @public
*/
export interface IExcerptToken {
canonicalReference?: string | undefined;
readonly kind: ExcerptTokenKind;
text: string;
}
/**
* Represents a fragment of text belonging to an {@link Excerpt} object.
*
* @public
*/
export class ExcerptToken {
private readonly _kind: ExcerptTokenKind;
private readonly _text: string;
private readonly _canonicalReference: DeclarationReference | undefined;
public constructor(kind: ExcerptTokenKind, text: string, canonicalReference?: DeclarationReference) {
this._kind = kind;
// Standardize the newlines across operating systems. Even though this may deviate from the actual
// input source file that was parsed, it's useful because the newline gets serialized inside
// a string literal in .api.json, which cannot be automatically normalized by Git.
this._text = Text.convertToLf(text);
this._canonicalReference = canonicalReference;
}
/**
* Indicates the kind of token.
*/
public get kind(): ExcerptTokenKind {
return this._kind;
}
/**
* The text fragment.
*/
public get text(): string {
return this._text;
}
/**
* The hyperlink target for a token whose type is `ExcerptTokenKind.Reference`. For other token types,
* this property will be `undefined`.
*/
public get canonicalReference(): DeclarationReference | undefined {
return this._canonicalReference;
}
}
/**
* The `Excerpt` class is used by {@link ApiDeclaredItem} to represent a TypeScript code fragment that may be
* annotated with hyperlinks to declared types (and in the future, source code locations).
*
* @remarks
* API Extractor's .api.json file format stores excerpts compactly as a start/end indexes into an array of tokens.
* Every `ApiDeclaredItem` has a "main excerpt" corresponding to the full list of tokens. The declaration may
* also have have "captured" excerpts that correspond to subranges of tokens.
*
* For example, if the main excerpt is:
*
* ```
* function parse(s: string): Vector | undefined;
* ```
*
* ...then this entire signature is the "main excerpt", whereas the function's return type `Vector | undefined` is a
* captured excerpt. The `Vector` token might be a hyperlink to that API item.
*
* An excerpt may be empty (i.e. a token range containing zero tokens). For example, if a function's return value
* is not explicitly declared, then the returnTypeExcerpt will be empty. By contrast, a class constructor cannot
* have a return value, so ApiConstructor has no returnTypeExcerpt property at all.
* @public
*/
export class Excerpt {
/**
* The complete list of tokens for the source code fragment that this excerpt is based upon.
* If this object is the main excerpt, then it will span all of the tokens; otherwise, it will correspond to
* a range within the array.
*/
public readonly tokens: readonly ExcerptToken[];
/**
* Specifies the excerpt's range within the `tokens` array.
*/
public readonly tokenRange: Readonly<IExcerptTokenRange>;
/**
* The tokens spanned by this excerpt. It is the range of the `tokens` array as specified by the `tokenRange`
* property.
*/
public readonly spannedTokens: readonly ExcerptToken[];
private _text: string | undefined;
public constructor(tokens: readonly ExcerptToken[], tokenRange: IExcerptTokenRange) {
this.tokens = tokens;
this.tokenRange = tokenRange;
if (
this.tokenRange.startIndex < 0 ||
this.tokenRange.endIndex > this.tokens.length ||
this.tokenRange.startIndex > this.tokenRange.endIndex
) {
throw new Error('Invalid token range');
}
this.spannedTokens = this.tokens.slice(this.tokenRange.startIndex, this.tokenRange.endIndex);
}
/**
* The excerpted text, formed by concatenating the text of the `spannedTokens` strings.
*/
public get text(): string {
if (this._text === undefined) {
this._text = this.spannedTokens.map((x) => x.text).join('');
}
return this._text;
}
/**
* Returns true if the excerpt is an empty range.
*/
public get isEmpty(): boolean {
return this.tokenRange.startIndex === this.tokenRange.endIndex;
}
}

View File

@@ -0,0 +1,73 @@
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
// See LICENSE in the project root for license information.
import type { ApiItem } from '../items/ApiItem.js';
/**
* Generic result object for finding API items used by different kinds of find operations.
*
* @public
*/
export interface IFindApiItemsResult {
/**
* The API items that were found. Not guaranteed to be complete, see `maybeIncompleteResult`.
*/
items: ApiItem[];
/**
* Indicates whether the result is potentially incomplete due to errors during the find operation.
* If true, the `messages` explain the errors in more detail.
*/
maybeIncompleteResult: boolean;
/**
* Diagnostic messages regarding the find operation.
*/
messages: IFindApiItemsMessage[];
}
/**
* This object is used for messages returned as part of `IFindApiItemsResult`.
*
* @public
*/
export interface IFindApiItemsMessage {
/**
* Unique identifier for the message.
*
* @beta
*/
messageId: FindApiItemsMessageId;
/**
* Text description of the message.
*/
text: string;
}
/**
* Unique identifiers for messages returned as part of `IFindApiItemsResult`.
*
* @public
*/
export enum FindApiItemsMessageId {
/**
* "Unable to resolve declaration reference within API item ___: ___"
*/
DeclarationResolutionFailed = 'declaration-resolution-failed',
/**
* "Unable to analyze extends clause ___ of API item ___ because no canonical reference was found."
*/
ExtendsClauseMissingReference = 'extends-clause-missing-reference',
/**
* "Unable to analyze references of API item ___ because it is not associated with an ApiModel"
*/
NoAssociatedApiModel = 'no-associated-api-model',
/**
* "Unable to analyze references of API item ___ because it is of unsupported kind ___"
*/
UnsupportedKind = 'unsupported-kind',
}

View File

@@ -0,0 +1,18 @@
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
// See LICENSE in the project root for license information.
/**
* This abstraction is used by the mixin pattern.
* It describes a class constructor.
*
* @public
*/
export type Constructor<T = {}> = new (...args: any[]) => T;
/**
* This abstraction is used by the mixin pattern.
* It describes the "static side" of a class.
*
* @public
*/
export type PropertiesOf<T> = { [K in keyof T]: T[K] };

View File

@@ -0,0 +1,90 @@
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
// See LICENSE in the project root for license information.
import { DeclarationReference } from '@microsoft/tsdoc/lib-commonjs/beta/DeclarationReference.js';
import { type IApiDeclaredItemOptions, ApiDeclaredItem } from '../items/ApiDeclaredItem.js';
import { ApiItemKind, Navigation, Meaning } from '../items/ApiItem.js';
import { type IApiParameterListMixinOptions, ApiParameterListMixin } from '../mixins/ApiParameterListMixin.js';
import { type IApiReleaseTagMixinOptions, ApiReleaseTagMixin } from '../mixins/ApiReleaseTagMixin.js';
import { type IApiReturnTypeMixinOptions, ApiReturnTypeMixin } from '../mixins/ApiReturnTypeMixin.js';
import {
type IApiTypeParameterListMixinOptions,
ApiTypeParameterListMixin,
} from '../mixins/ApiTypeParameterListMixin.js';
/**
* Constructor options for {@link ApiCallSignature}.
*
* @public
*/
export interface IApiCallSignatureOptions
extends IApiTypeParameterListMixinOptions,
IApiParameterListMixinOptions,
IApiReleaseTagMixinOptions,
IApiReturnTypeMixinOptions,
IApiDeclaredItemOptions {}
/**
* Represents a TypeScript function call signature.
*
* @remarks
*
* This is part of the {@link ApiModel} hierarchy of classes, which are serializable representations of
* API declarations.
*
* `ApiCallSignature` represents a TypeScript declaration such as `(x: number, y: number): number`
* in this example:
*
* ```ts
* export interface IChooser {
* // A call signature:
* (x: number, y: number): number;
*
* // Another overload for this call signature:
* (x: string, y: string): string;
* }
*
* function chooseFirst<T>(x: T, y: T): T {
* return x;
* }
*
* let chooser: IChooser = chooseFirst;
* ```
* @public
*/
export class ApiCallSignature extends ApiTypeParameterListMixin(
ApiParameterListMixin(ApiReleaseTagMixin(ApiReturnTypeMixin(ApiDeclaredItem))),
) {
public constructor(options: IApiCallSignatureOptions) {
super(options);
}
public static getContainerKey(overloadIndex: number): string {
return `|${ApiItemKind.CallSignature}|${overloadIndex}`;
}
/**
* @override
*/
public override get kind(): ApiItemKind {
return ApiItemKind.CallSignature;
}
/**
* @override
*/
public override get containerKey(): string {
return ApiCallSignature.getContainerKey(this.overloadIndex);
}
/**
* @beta @override
*/
public override buildCanonicalReference(): DeclarationReference {
const parent: DeclarationReference = this.parent
? this.parent.canonicalReference
: // .withMeaning() requires some kind of component
DeclarationReference.empty().addNavigationStep(Navigation.Members as any, '(parent)');
return parent.withMeaning(Meaning.CallSignature as any).withOverloadIndex(this.overloadIndex);
}
}

View File

@@ -0,0 +1,157 @@
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
// See LICENSE in the project root for license information.
import { DeclarationReference, type Component } from '@microsoft/tsdoc/lib-commonjs/beta/DeclarationReference.js';
import { ApiDeclaredItem, type IApiDeclaredItemOptions, type IApiDeclaredItemJson } from '../items/ApiDeclaredItem.js';
import { ApiItemKind, Navigation, Meaning } from '../items/ApiItem.js';
import {
ApiAbstractMixin,
type IApiAbstractMixinJson,
type IApiAbstractMixinOptions,
} from '../mixins/ApiAbstractMixin.js';
import {
type IApiExportedMixinJson,
type IApiExportedMixinOptions,
ApiExportedMixin,
} from '../mixins/ApiExportedMixin.js';
import { ApiItemContainerMixin, type IApiItemContainerMixinOptions } from '../mixins/ApiItemContainerMixin.js';
import { type IApiNameMixinOptions, ApiNameMixin } from '../mixins/ApiNameMixin.js';
import { ApiReleaseTagMixin, type IApiReleaseTagMixinOptions } from '../mixins/ApiReleaseTagMixin.js';
import {
ApiTypeParameterListMixin,
type IApiTypeParameterListMixinOptions,
type IApiTypeParameterListMixinJson,
} from '../mixins/ApiTypeParameterListMixin.js';
import type { IExcerptTokenRange } from '../mixins/Excerpt.js';
import type { DeserializerContext } from './DeserializerContext.js';
import { HeritageType } from './HeritageType.js';
/**
* Constructor options for {@link ApiClass}.
*
* @public
*/
export interface IApiClassOptions
extends IApiItemContainerMixinOptions,
IApiNameMixinOptions,
IApiAbstractMixinOptions,
IApiReleaseTagMixinOptions,
IApiDeclaredItemOptions,
IApiTypeParameterListMixinOptions,
IApiExportedMixinOptions {
extendsTokenRange: IExcerptTokenRange | undefined;
implementsTokenRanges: IExcerptTokenRange[];
}
export interface IApiClassJson
extends IApiDeclaredItemJson,
IApiAbstractMixinJson,
IApiTypeParameterListMixinJson,
IApiExportedMixinJson {
extendsTokenRange?: IExcerptTokenRange;
implementsTokenRanges: IExcerptTokenRange[];
}
/**
* Represents a TypeScript class declaration.
*
* @remarks
*
* This is part of the {@link ApiModel} hierarchy of classes, which are serializable representations of
* API declarations.
*
* `ApiClass` represents a TypeScript declaration such as this:
*
* ```ts
* export class X { }
* ```
* @public
*/
export class ApiClass extends ApiItemContainerMixin(
ApiNameMixin(ApiAbstractMixin(ApiTypeParameterListMixin(ApiReleaseTagMixin(ApiExportedMixin(ApiDeclaredItem))))),
) {
/**
* The base class that this class inherits from (using the `extends` keyword), or undefined if there is no base class.
*/
public readonly extendsType: HeritageType | undefined;
private readonly _implementsTypes: HeritageType[] = [];
public constructor(options: IApiClassOptions) {
super(options);
if (options.extendsTokenRange) {
this.extendsType = new HeritageType(this.buildExcerpt(options.extendsTokenRange));
} else {
this.extendsType = undefined;
}
for (const implementsTokenRange of options.implementsTokenRanges) {
this._implementsTypes.push(new HeritageType(this.buildExcerpt(implementsTokenRange)));
}
}
public static getContainerKey(name: string): string {
return `${name}|${ApiItemKind.Class}`;
}
/**
* @override
*/
public static override onDeserializeInto(
options: Partial<IApiClassOptions>,
context: DeserializerContext,
jsonObject: IApiClassJson,
): void {
super.onDeserializeInto(options, context, jsonObject);
options.extendsTokenRange = jsonObject.extendsTokenRange;
options.implementsTokenRanges = jsonObject.implementsTokenRanges;
}
/**
* @override
*/
public override get kind(): ApiItemKind {
return ApiItemKind.Class;
}
/**
* @override
*/
public override get containerKey(): string {
return ApiClass.getContainerKey(this.name);
}
/**
* The list of interfaces that this class implements using the `implements` keyword.
*/
public get implementsTypes(): readonly HeritageType[] {
return this._implementsTypes;
}
/**
* @override
*/
public override serializeInto(jsonObject: Partial<IApiClassJson>): void {
super.serializeInto(jsonObject);
// Note that JSON does not support the "undefined" value, so we simply omit the field entirely if it is undefined
if (this.extendsType) {
jsonObject.extendsTokenRange = this.extendsType.excerpt.tokenRange;
}
jsonObject.implementsTokenRanges = this.implementsTypes.map((x) => x.excerpt.tokenRange);
}
/**
* @beta @override
*/
public override buildCanonicalReference(): DeclarationReference {
const nameComponent: Component = DeclarationReference.parseComponent(this.name);
const navigation: Navigation = this.isExported ? Navigation.Exports : Navigation.Locals;
return (this.parent ? this.parent.canonicalReference : DeclarationReference.empty())
.addNavigationStep(navigation as any, nameComponent)
.withMeaning(Meaning.Class as any);
}
}

View File

@@ -0,0 +1,103 @@
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
// See LICENSE in the project root for license information.
import { DeclarationReference } from '@microsoft/tsdoc/lib-commonjs/beta/DeclarationReference.js';
import { type IApiDeclaredItemOptions, ApiDeclaredItem } from '../items/ApiDeclaredItem.js';
import { ApiItemKind, Navigation, Meaning } from '../items/ApiItem.js';
import { type IApiParameterListMixinOptions, ApiParameterListMixin } from '../mixins/ApiParameterListMixin.js';
import { type IApiReleaseTagMixinOptions, ApiReleaseTagMixin } from '../mixins/ApiReleaseTagMixin.js';
import { type IApiReturnTypeMixinOptions, ApiReturnTypeMixin } from '../mixins/ApiReturnTypeMixin.js';
import {
ApiTypeParameterListMixin,
type IApiTypeParameterListMixinOptions,
} from '../mixins/ApiTypeParameterListMixin.js';
/**
* Constructor options for {@link ApiConstructor}.
*
* @public
*/
export interface IApiConstructSignatureOptions
extends IApiTypeParameterListMixinOptions,
IApiParameterListMixinOptions,
IApiReleaseTagMixinOptions,
IApiReturnTypeMixinOptions,
IApiDeclaredItemOptions {}
/**
* Represents a TypeScript construct signature that belongs to an `ApiInterface`.
*
* @remarks
*
* This is part of the {@link ApiModel} hierarchy of classes, which are serializable representations of
* API declarations.
*
* `ApiConstructSignature` represents a construct signature using the `new` keyword such as in this example:
*
* ```ts
* export interface IVector {
* x: number;
* y: number;
* }
*
* export interface IVectorConstructor {
* // A construct signature:
* new(x: number, y: number): IVector;
* }
*
* export function createVector(vectorConstructor: IVectorConstructor,
* x: number, y: number): IVector {
* return new vectorConstructor(x, y);
* }
*
* class Vector implements IVector {
* public x: number;
* public y: number;
* public constructor(x: number, y: number) {
* this.x = x;
* this.y = y;
* }
* }
*
* let vector: Vector = createVector(Vector, 1, 2);
* ```
*
* Compare with {@link ApiConstructor}, which describes the class constructor itself.
* @public
*/
export class ApiConstructSignature extends ApiTypeParameterListMixin(
ApiParameterListMixin(ApiReleaseTagMixin(ApiReturnTypeMixin(ApiDeclaredItem))),
) {
public constructor(options: IApiConstructSignatureOptions) {
super(options);
}
public static getContainerKey(overloadIndex: number): string {
return `|${ApiItemKind.ConstructSignature}|${overloadIndex}`;
}
/**
* @override
*/
public override get kind(): ApiItemKind {
return ApiItemKind.ConstructSignature;
}
/**
* @override
*/
public override get containerKey(): string {
return ApiConstructSignature.getContainerKey(this.overloadIndex);
}
/**
* @beta @override
*/
public override buildCanonicalReference(): DeclarationReference {
const parent: DeclarationReference = this.parent
? this.parent.canonicalReference
: // .withMeaning() requires some kind of component
DeclarationReference.empty().addNavigationStep(Navigation.Members as any, '(parent)');
return parent.withMeaning(Meaning.ConstructSignature as any).withOverloadIndex(this.overloadIndex);
}
}

View File

@@ -0,0 +1,81 @@
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
// See LICENSE in the project root for license information.
import { DeclarationReference } from '@microsoft/tsdoc/lib-commonjs/beta/DeclarationReference.js';
import { type IApiDeclaredItemOptions, ApiDeclaredItem } from '../items/ApiDeclaredItem.js';
import { ApiItemKind, Navigation, Meaning } from '../items/ApiItem.js';
import { type IApiParameterListMixinOptions, ApiParameterListMixin } from '../mixins/ApiParameterListMixin.js';
import { ApiProtectedMixin, type IApiProtectedMixinOptions } from '../mixins/ApiProtectedMixin.js';
import { type IApiReleaseTagMixinOptions, ApiReleaseTagMixin } from '../mixins/ApiReleaseTagMixin.js';
/**
* Constructor options for {@link ApiConstructor}.
*
* @public
*/
export interface IApiConstructorOptions
extends IApiParameterListMixinOptions,
IApiProtectedMixinOptions,
IApiReleaseTagMixinOptions,
IApiDeclaredItemOptions {}
/**
* Represents a TypeScript class constructor declaration that belongs to an `ApiClass`.
*
* @remarks
*
* This is part of the {@link ApiModel} hierarchy of classes, which are serializable representations of
* API declarations.
*
* `ApiConstructor` represents a declaration using the `constructor` keyword such as in this example:
*
* ```ts
* export class Vector {
* public x: number;
* public y: number;
*
* // A class constructor:
* public constructor(x: number, y: number) {
* this.x = x;
* this.y = y;
* }
* }
* ```
*
* Compare with {@link ApiConstructSignature}, which describes the construct signature for a class constructor.
* @public
*/
export class ApiConstructor extends ApiParameterListMixin(ApiProtectedMixin(ApiReleaseTagMixin(ApiDeclaredItem))) {
public constructor(options: IApiConstructorOptions) {
super(options);
}
public static getContainerKey(overloadIndex: number): string {
return `|${ApiItemKind.Constructor}|${overloadIndex}`;
}
/**
* @override
*/
public override get kind(): ApiItemKind {
return ApiItemKind.Constructor;
}
/**
* @override
*/
public override get containerKey(): string {
return ApiConstructor.getContainerKey(this.overloadIndex);
}
/**
* @beta @override
*/
public override buildCanonicalReference(): DeclarationReference {
const parent: DeclarationReference = this.parent
? this.parent.canonicalReference
: // .withMeaning() requires some kind of component
DeclarationReference.empty().addNavigationStep(Navigation.Members as any, '(parent)');
return parent.withMeaning(Meaning.Constructor as any).withOverloadIndex(this.overloadIndex);
}
}

View File

@@ -0,0 +1,89 @@
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
// See LICENSE in the project root for license information.
import { DeclarationReference } from '@microsoft/tsdoc/lib-commonjs/beta/DeclarationReference.js';
import { ApiItem, ApiItemKind } from '../items/ApiItem.js';
import { ApiItemContainerMixin, type IApiItemContainerMixinOptions } from '../mixins/ApiItemContainerMixin.js';
import { type IApiNameMixinOptions, ApiNameMixin } from '../mixins/ApiNameMixin.js';
import { ApiPackage } from './ApiPackage.js';
/**
* Constructor options for {@link ApiEntryPoint}.
*
* @public
*/
export interface IApiEntryPointOptions extends IApiItemContainerMixinOptions, IApiNameMixinOptions {}
/**
* Represents the entry point for an NPM package.
*
* @remarks
*
* This is part of the {@link ApiModel} hierarchy of classes, which are serializable representations of
* API declarations.
*
* `ApiEntryPoint` represents the entry point to an NPM package. API Extractor does not currently support
* analysis of multiple entry points, but the `ApiEntryPoint` object is included to support a future feature.
* In the current implementation, `ApiEntryPoint.importPath` is always the empty string.
*
* For example, suppose the package.json file looks like this:
*
* ```json
* {
* "name": "example-library",
* "version": "1.0.0",
* "main": "./lib/index.js",
* "typings": "./lib/index.d.ts"
* }
* ```
*
* In this example, the `ApiEntryPoint` would represent the TypeScript module for `./lib/index.js`.
* @public
*/
export class ApiEntryPoint extends ApiItemContainerMixin(ApiNameMixin(ApiItem)) {
public constructor(options: IApiEntryPointOptions) {
super(options);
}
/**
* @override
*/
public override get kind(): ApiItemKind {
return ApiItemKind.EntryPoint;
}
/**
* @override
*/
public override get containerKey(): string {
// No prefix needed, because ApiEntryPoint is the only possible member of an ApiPackage
return this.name;
}
/**
* The module path for this entry point, relative to the parent `ApiPackage`. In the current implementation,
* this is always the empty string, indicating the default entry point.
*
* @remarks
*
* API Extractor does not currently support analysis of multiple entry points. If that feature is implemented
* in the future, then the `ApiEntryPoint.importPath` will be used to distinguish different entry points,
* for example: `controls/Button` in `import { Button } from "example-package/controls/Button";`.
*
* The `ApiEntryPoint.name` property stores the same value as `ApiEntryPoint.importPath`.
*/
public get importPath(): string {
return this.name;
}
/**
* @beta @override
*/
public override buildCanonicalReference(): DeclarationReference {
if (this.parent instanceof ApiPackage) {
return DeclarationReference.package(this.parent.name, this.importPath);
}
return DeclarationReference.empty();
}
}

View File

@@ -0,0 +1,97 @@
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
// See LICENSE in the project root for license information.
import { DeclarationReference, type Component } from '@microsoft/tsdoc/lib-commonjs/beta/DeclarationReference.js';
import { ApiDeclaredItem, type IApiDeclaredItemOptions } from '../items/ApiDeclaredItem.js';
import { ApiItemKind, Navigation, Meaning } from '../items/ApiItem.js';
import { type IApiExportedMixinOptions, ApiExportedMixin } from '../mixins/ApiExportedMixin.js';
import { ApiItemContainerMixin, type IApiItemContainerMixinOptions } from '../mixins/ApiItemContainerMixin.js';
import { type IApiNameMixinOptions, ApiNameMixin } from '../mixins/ApiNameMixin.js';
import { ApiReleaseTagMixin, type IApiReleaseTagMixinOptions } from '../mixins/ApiReleaseTagMixin.js';
import type { ApiEnumMember } from './ApiEnumMember.js';
/**
* Constructor options for {@link ApiEnum}.
*
* @public
*/
export interface IApiEnumOptions
extends IApiItemContainerMixinOptions,
IApiNameMixinOptions,
IApiReleaseTagMixinOptions,
IApiDeclaredItemOptions,
IApiExportedMixinOptions {}
/**
* Represents a TypeScript enum declaration.
*
* @remarks
*
* This is part of the {@link ApiModel} hierarchy of classes, which are serializable representations of
* API declarations.
*
* `ApiEnum` represents an enum declaration such as `FontSizes` in the example below:
*
* ```ts
* export enum FontSizes {
* Small = 100,
* Medium = 200,
* Large = 300
* }
* ```
* @public
*/
export class ApiEnum extends ApiItemContainerMixin(
ApiNameMixin(ApiReleaseTagMixin(ApiExportedMixin(ApiDeclaredItem))),
) {
public constructor(options: IApiEnumOptions) {
super(options);
}
public static getContainerKey(name: string): string {
return `${name}|${ApiItemKind.Enum}`;
}
/**
* @override
*/
public override get kind(): ApiItemKind {
return ApiItemKind.Enum;
}
/**
* @override
*/
public override get members(): readonly ApiEnumMember[] {
return super.members as readonly ApiEnumMember[];
}
/**
* @override
*/
public override get containerKey(): string {
return ApiEnum.getContainerKey(this.name);
}
/**
* @override
*/
public override addMember(member: ApiEnumMember): void {
if (member.kind !== ApiItemKind.EnumMember) {
throw new Error('Only ApiEnumMember objects can be added to an ApiEnum');
}
super.addMember(member);
}
/**
* @beta @override
*/
public override buildCanonicalReference(): DeclarationReference {
const nameComponent: Component = DeclarationReference.parseComponent(this.name);
const navigation: Navigation = this.isExported ? Navigation.Exports : Navigation.Locals;
return (this.parent ? this.parent.canonicalReference : DeclarationReference.empty())
.addNavigationStep(navigation as any, nameComponent)
.withMeaning(Meaning.Enum as any);
}
}

View File

@@ -0,0 +1,101 @@
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
// See LICENSE in the project root for license information.
import { DeclarationReference, type Component } from '@microsoft/tsdoc/lib-commonjs/beta/DeclarationReference.js';
import { ApiDeclaredItem, type IApiDeclaredItemOptions } from '../items/ApiDeclaredItem.js';
import { ApiItemKind, Navigation, Meaning } from '../items/ApiItem.js';
import { ApiInitializerMixin, type IApiInitializerMixinOptions } from '../mixins/ApiInitializerMixin.js';
import { type IApiNameMixinOptions, ApiNameMixin } from '../mixins/ApiNameMixin.js';
import { ApiReleaseTagMixin, type IApiReleaseTagMixinOptions } from '../mixins/ApiReleaseTagMixin.js';
/**
* Constructor options for {@link ApiEnumMember}.
*
* @public
*/
export interface IApiEnumMemberOptions
extends IApiNameMixinOptions,
IApiReleaseTagMixinOptions,
IApiDeclaredItemOptions,
IApiInitializerMixinOptions {}
/**
* Options for customizing the sort order of {@link ApiEnum} members.
*
* @privateRemarks
* This enum is currently only used by the `@microsoft/api-extractor` package; it is declared here
* because we anticipate that if more options are added in the future, their sorting will be implemented
* by the `@microsoft/api-extractor-model` package.
*
* See https://github.com/microsoft/rushstack/issues/918 for details.
* @public
*/
export enum EnumMemberOrder {
/**
* `ApiEnumMember` items are sorted according to their {@link ApiItem.getSortKey}. The order is
* basically alphabetical by identifier name, but otherwise unspecified to allow for cosmetic improvements.
*
* This is the default behavior.
*/
ByName = 'by-name',
/**
* `ApiEnumMember` items preserve the original order of the declarations in the source file.
* (This disables the automatic sorting that is normally applied based on {@link ApiItem.getSortKey}.)
*/
Preserve = 'preserve',
}
/**
* Represents a member of a TypeScript enum declaration.
*
* @remarks
*
* This is part of the {@link ApiModel} hierarchy of classes, which are serializable representations of
* API declarations.
*
* `ApiEnumMember` represents an enum member such as `Small = 100` in the example below:
*
* ```ts
* export enum FontSizes {
* Small = 100,
* Medium = 200,
* Large = 300
* }
* ```
* @public
*/
export class ApiEnumMember extends ApiNameMixin(ApiReleaseTagMixin(ApiInitializerMixin(ApiDeclaredItem))) {
public constructor(options: IApiEnumMemberOptions) {
super(options);
}
public static getContainerKey(name: string): string {
// No prefix needed, because ApiEnumMember is the only possible member of an ApiEnum
return name;
}
/**
* @override
*/
public override get kind(): ApiItemKind {
return ApiItemKind.EnumMember;
}
/**
* @override
*/
public override get containerKey(): string {
return ApiEnumMember.getContainerKey(this.name);
}
/**
* @beta @override
*/
public override buildCanonicalReference(): DeclarationReference {
const nameComponent: Component = DeclarationReference.parseComponent(this.name);
return (this.parent ? this.parent.canonicalReference : DeclarationReference.empty())
.addNavigationStep(Navigation.Exports as any, nameComponent)
.withMeaning(Meaning.Member as any);
}
}

View File

@@ -0,0 +1,89 @@
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
// See LICENSE in the project root for license information.
import { DeclarationReference, type Component } from '@microsoft/tsdoc/lib-commonjs/beta/DeclarationReference.js';
import { type IApiDeclaredItemOptions, ApiDeclaredItem } from '../items/ApiDeclaredItem.js';
import { ApiItemKind, Navigation, Meaning } from '../items/ApiItem.js';
import { type IApiExportedMixinOptions, ApiExportedMixin } from '../mixins/ApiExportedMixin.js';
import { type IApiNameMixinOptions, ApiNameMixin } from '../mixins/ApiNameMixin.js';
import { type IApiParameterListMixinOptions, ApiParameterListMixin } from '../mixins/ApiParameterListMixin.js';
import { type IApiReleaseTagMixinOptions, ApiReleaseTagMixin } from '../mixins/ApiReleaseTagMixin.js';
import { type IApiReturnTypeMixinOptions, ApiReturnTypeMixin } from '../mixins/ApiReturnTypeMixin.js';
import {
type IApiTypeParameterListMixinOptions,
ApiTypeParameterListMixin,
} from '../mixins/ApiTypeParameterListMixin.js';
/**
* Constructor options for {@link ApiFunction}.
*
* @public
*/
export interface IApiFunctionOptions
extends IApiNameMixinOptions,
IApiTypeParameterListMixinOptions,
IApiParameterListMixinOptions,
IApiReleaseTagMixinOptions,
IApiReturnTypeMixinOptions,
IApiDeclaredItemOptions,
IApiExportedMixinOptions {}
/**
* Represents a TypeScript function declaration.
*
* @remarks
*
* This is part of the {@link ApiModel} hierarchy of classes, which are serializable representations of
* API declarations.
*
* `ApiFunction` represents a TypeScript declaration such as this example:
*
* ```ts
* export function getAverage(x: number, y: number): number {
* return (x + y) / 2.0;
* }
* ```
*
* Functions are exported by an entry point module or by a namespace. Compare with {@link ApiMethod}, which
* represents a function that is a member of a class.
* @public
*/
export class ApiFunction extends ApiNameMixin(
ApiTypeParameterListMixin(
ApiParameterListMixin(ApiReleaseTagMixin(ApiReturnTypeMixin(ApiExportedMixin(ApiDeclaredItem)))),
),
) {
public constructor(options: IApiFunctionOptions) {
super(options);
}
public static getContainerKey(name: string, overloadIndex: number): string {
return `${name}|${ApiItemKind.Function}|${overloadIndex}`;
}
/**
* @override
*/
public override get kind(): ApiItemKind {
return ApiItemKind.Function;
}
/**
* @override
*/
public override get containerKey(): string {
return ApiFunction.getContainerKey(this.name, this.overloadIndex);
}
/**
* @beta @override
*/
public override buildCanonicalReference(): DeclarationReference {
const nameComponent: Component = DeclarationReference.parseComponent(this.name);
const navigation: Navigation = this.isExported ? Navigation.Exports : Navigation.Locals;
return (this.parent ? this.parent.canonicalReference : DeclarationReference.empty())
.addNavigationStep(navigation as any, nameComponent)
.withMeaning(Meaning.Function as any)
.withOverloadIndex(this.overloadIndex);
}
}

View File

@@ -0,0 +1,80 @@
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
// See LICENSE in the project root for license information.
import { DeclarationReference } from '@microsoft/tsdoc/lib-commonjs/beta/DeclarationReference.js';
import { type IApiDeclaredItemOptions, ApiDeclaredItem } from '../items/ApiDeclaredItem.js';
import { ApiItemKind, Navigation, Meaning } from '../items/ApiItem.js';
import { type IApiParameterListMixinOptions, ApiParameterListMixin } from '../mixins/ApiParameterListMixin.js';
import { type IApiReadonlyMixinOptions, ApiReadonlyMixin } from '../mixins/ApiReadonlyMixin.js';
import { type IApiReleaseTagMixinOptions, ApiReleaseTagMixin } from '../mixins/ApiReleaseTagMixin.js';
import { type IApiReturnTypeMixinOptions, ApiReturnTypeMixin } from '../mixins/ApiReturnTypeMixin.js';
/**
* Constructor options for {@link ApiIndexSignature}.
*
* @public
*/
export interface IApiIndexSignatureOptions
extends IApiParameterListMixinOptions,
IApiReleaseTagMixinOptions,
IApiReturnTypeMixinOptions,
IApiReadonlyMixinOptions,
IApiDeclaredItemOptions {}
/**
* Represents a TypeScript index signature.
*
* @remarks
*
* This is part of the {@link ApiModel} hierarchy of classes, which are serializable representations of
* API declarations.
*
* `ApiIndexSignature` represents a TypeScript declaration such as `[x: number]: number` in this example:
*
* ```ts
* export interface INumberTable {
* // An index signature
* [value: number]: number;
*
* // An overloaded index signature
* [name: string]: number;
* }
* ```
* @public
*/
export class ApiIndexSignature extends ApiParameterListMixin(
ApiReleaseTagMixin(ApiReturnTypeMixin(ApiReadonlyMixin(ApiDeclaredItem))),
) {
public constructor(options: IApiIndexSignatureOptions) {
super(options);
}
public static getContainerKey(overloadIndex: number): string {
return `|${ApiItemKind.IndexSignature}|${overloadIndex}`;
}
/**
* @override
*/
public override get kind(): ApiItemKind {
return ApiItemKind.IndexSignature;
}
/**
* @override
*/
public override get containerKey(): string {
return ApiIndexSignature.getContainerKey(this.overloadIndex);
}
/**
* @beta @override
*/
public override buildCanonicalReference(): DeclarationReference {
const parent: DeclarationReference = this.parent
? this.parent.canonicalReference
: // .withMeaning() requires some kind of component
DeclarationReference.empty().addNavigationStep(Navigation.Members as any, '(parent)');
return parent.withMeaning(Meaning.IndexSignature as any).withOverloadIndex(this.overloadIndex);
}
}

View File

@@ -0,0 +1,143 @@
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
// See LICENSE in the project root for license information.
import { DeclarationReference, type Component } from '@microsoft/tsdoc/lib-commonjs/beta/DeclarationReference.js';
import { ApiDeclaredItem, type IApiDeclaredItemOptions, type IApiDeclaredItemJson } from '../items/ApiDeclaredItem.js';
import { ApiItemKind, Navigation, Meaning } from '../items/ApiItem.js';
import {
type IApiExportedMixinJson,
type IApiExportedMixinOptions,
ApiExportedMixin,
} from '../mixins/ApiExportedMixin.js';
import {
ApiItemContainerMixin,
type IApiItemContainerMixinOptions,
type IApiItemContainerJson,
} from '../mixins/ApiItemContainerMixin.js';
import { type IApiNameMixinOptions, ApiNameMixin, type IApiNameMixinJson } from '../mixins/ApiNameMixin.js';
import {
type IApiReleaseTagMixinOptions,
ApiReleaseTagMixin,
type IApiReleaseTagMixinJson,
} from '../mixins/ApiReleaseTagMixin.js';
import {
type IApiTypeParameterListMixinOptions,
type IApiTypeParameterListMixinJson,
ApiTypeParameterListMixin,
} from '../mixins/ApiTypeParameterListMixin.js';
import type { IExcerptTokenRange } from '../mixins/Excerpt.js';
import type { DeserializerContext } from './DeserializerContext.js';
import { HeritageType } from './HeritageType.js';
/**
* Constructor options for {@link ApiInterface}.
*
* @public
*/
export interface IApiInterfaceOptions
extends IApiItemContainerMixinOptions,
IApiNameMixinOptions,
IApiTypeParameterListMixinOptions,
IApiReleaseTagMixinOptions,
IApiDeclaredItemOptions,
IApiExportedMixinOptions {
extendsTokenRanges: IExcerptTokenRange[];
}
export interface IApiInterfaceJson
extends IApiItemContainerJson,
IApiNameMixinJson,
IApiTypeParameterListMixinJson,
IApiReleaseTagMixinJson,
IApiDeclaredItemJson,
IApiExportedMixinJson {
extendsTokenRanges: IExcerptTokenRange[];
}
/**
* Represents a TypeScript class declaration.
*
* @remarks
*
* This is part of the {@link ApiModel} hierarchy of classes, which are serializable representations of
* API declarations.
*
* `ApiInterface` represents a TypeScript declaration such as this:
*
* ```ts
* export interface X extends Y {
* }
* ```
* @public
*/
export class ApiInterface extends ApiItemContainerMixin(
ApiNameMixin(ApiTypeParameterListMixin(ApiReleaseTagMixin(ApiExportedMixin(ApiDeclaredItem)))),
) {
private readonly _extendsTypes: HeritageType[] = [];
public constructor(options: IApiInterfaceOptions) {
super(options);
for (const extendsTokenRange of options.extendsTokenRanges) {
this._extendsTypes.push(new HeritageType(this.buildExcerpt(extendsTokenRange)));
}
}
public static getContainerKey(name: string): string {
return `${name}|${ApiItemKind.Interface}`;
}
/**
* @override
*/
public static override onDeserializeInto(
options: Partial<IApiInterfaceOptions>,
context: DeserializerContext,
jsonObject: IApiInterfaceJson,
): void {
super.onDeserializeInto(options, context, jsonObject);
options.extendsTokenRanges = jsonObject.extendsTokenRanges;
}
/**
* @override
*/
public override get kind(): ApiItemKind {
return ApiItemKind.Interface;
}
/**
* @override
*/
public override get containerKey(): string {
return ApiInterface.getContainerKey(this.name);
}
/**
* The list of base interfaces that this interface inherits from using the `extends` keyword.
*/
public get extendsTypes(): readonly HeritageType[] {
return this._extendsTypes;
}
/**
* @override
*/
public override serializeInto(jsonObject: Partial<IApiInterfaceJson>): void {
super.serializeInto(jsonObject);
jsonObject.extendsTokenRanges = this.extendsTypes.map((x) => x.excerpt.tokenRange);
}
/**
* @beta @override
*/
public override buildCanonicalReference(): DeclarationReference {
const nameComponent: Component = DeclarationReference.parseComponent(this.name);
const navigation: Navigation = this.isExported ? Navigation.Exports : Navigation.Locals;
return (this.parent ? this.parent.canonicalReference : DeclarationReference.empty())
.addNavigationStep(navigation as any, nameComponent)
.withMeaning(Meaning.Interface as any);
}
}

View File

@@ -0,0 +1,104 @@
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
// See LICENSE in the project root for license information.
import { DeclarationReference, type Component } from '@microsoft/tsdoc/lib-commonjs/beta/DeclarationReference.js';
import { type IApiDeclaredItemOptions, ApiDeclaredItem } from '../items/ApiDeclaredItem.js';
import { ApiItemKind, Navigation, Meaning } from '../items/ApiItem.js';
import { type IApiAbstractMixinOptions, ApiAbstractMixin } from '../mixins/ApiAbstractMixin.js';
import { type IApiNameMixinOptions, ApiNameMixin } from '../mixins/ApiNameMixin.js';
import { ApiOptionalMixin, type IApiOptionalMixinOptions } from '../mixins/ApiOptionalMixin.js';
import { type IApiParameterListMixinOptions, ApiParameterListMixin } from '../mixins/ApiParameterListMixin.js';
import { ApiProtectedMixin, type IApiProtectedMixinOptions } from '../mixins/ApiProtectedMixin.js';
import { type IApiReleaseTagMixinOptions, ApiReleaseTagMixin } from '../mixins/ApiReleaseTagMixin.js';
import { ApiReturnTypeMixin, type IApiReturnTypeMixinOptions } from '../mixins/ApiReturnTypeMixin.js';
import { ApiStaticMixin, type IApiStaticMixinOptions } from '../mixins/ApiStaticMixin.js';
import {
ApiTypeParameterListMixin,
type IApiTypeParameterListMixinOptions,
} from '../mixins/ApiTypeParameterListMixin.js';
/**
* Constructor options for {@link ApiMethod}.
*
* @public
*/
export interface IApiMethodOptions
extends IApiNameMixinOptions,
IApiAbstractMixinOptions,
IApiOptionalMixinOptions,
IApiParameterListMixinOptions,
IApiProtectedMixinOptions,
IApiReleaseTagMixinOptions,
IApiReturnTypeMixinOptions,
IApiStaticMixinOptions,
IApiTypeParameterListMixinOptions,
IApiDeclaredItemOptions {}
/**
* Represents a TypeScript member function declaration that belongs to an `ApiClass`.
*
* @remarks
*
* This is part of the {@link ApiModel} hierarchy of classes, which are serializable representations of
* API declarations.
*
* `ApiMethod` represents a TypeScript declaration such as the `render` member function in this example:
*
* ```ts
* export class Widget {
* public render(): void { }
* }
* ```
*
* Compare with {@link ApiMethodSignature}, which represents a method belonging to an interface.
* For example, a class method can be `static` but an interface method cannot.
* @public
*/
export class ApiMethod extends ApiNameMixin(
ApiAbstractMixin(
ApiOptionalMixin(
ApiParameterListMixin(
ApiProtectedMixin(
ApiReleaseTagMixin(ApiReturnTypeMixin(ApiStaticMixin(ApiTypeParameterListMixin(ApiDeclaredItem)))),
),
),
),
),
) {
public constructor(options: IApiMethodOptions) {
super(options);
}
public static getContainerKey(name: string, isStatic: boolean, overloadIndex: number): string {
if (isStatic) {
return `${name}|${ApiItemKind.Method}|static|${overloadIndex}`;
} else {
return `${name}|${ApiItemKind.Method}|instance|${overloadIndex}`;
}
}
/**
* @override
*/
public override get kind(): ApiItemKind {
return ApiItemKind.Method;
}
/**
* @override
*/
public override get containerKey(): string {
return ApiMethod.getContainerKey(this.name, this.isStatic, this.overloadIndex);
}
/**
* @beta @override
*/
public override buildCanonicalReference(): DeclarationReference {
const nameComponent: Component = DeclarationReference.parseComponent(this.name);
return (this.parent ? this.parent.canonicalReference : DeclarationReference.empty())
.addNavigationStep((this.isStatic ? Navigation.Exports : Navigation.Members) as any, nameComponent)
.withMeaning(Meaning.Member as any)
.withOverloadIndex(this.overloadIndex);
}
}

View File

@@ -0,0 +1,86 @@
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
// See LICENSE in the project root for license information.
import { DeclarationReference, type Component } from '@microsoft/tsdoc/lib-commonjs/beta/DeclarationReference.js';
import { ApiDeclaredItem, type IApiDeclaredItemOptions } from '../items/ApiDeclaredItem.js';
import { ApiItemKind, Navigation, Meaning } from '../items/ApiItem.js';
import { type IApiNameMixinOptions, ApiNameMixin } from '../mixins/ApiNameMixin.js';
import { ApiOptionalMixin, type IApiOptionalMixinOptions } from '../mixins/ApiOptionalMixin.js';
import { ApiParameterListMixin, type IApiParameterListMixinOptions } from '../mixins/ApiParameterListMixin.js';
import { ApiReleaseTagMixin, type IApiReleaseTagMixinOptions } from '../mixins/ApiReleaseTagMixin.js';
import { type IApiReturnTypeMixinOptions, ApiReturnTypeMixin } from '../mixins/ApiReturnTypeMixin.js';
import {
type IApiTypeParameterListMixinOptions,
ApiTypeParameterListMixin,
} from '../mixins/ApiTypeParameterListMixin.js';
/**
* @public
*/
export interface IApiMethodSignatureOptions
extends IApiNameMixinOptions,
IApiTypeParameterListMixinOptions,
IApiParameterListMixinOptions,
IApiReleaseTagMixinOptions,
IApiReturnTypeMixinOptions,
IApiOptionalMixinOptions,
IApiDeclaredItemOptions {}
/**
* Represents a TypeScript member function declaration that belongs to an `ApiInterface`.
*
* @remarks
*
* This is part of the {@link ApiModel} hierarchy of classes, which are serializable representations of
* API declarations.
*
* `ApiMethodSignature` represents a TypeScript declaration such as the `render` member function in this example:
*
* ```ts
* export interface IWidget {
* render(): void;
* }
* ```
*
* Compare with {@link ApiMethod}, which represents a method belonging to a class.
* For example, a class method can be `static` but an interface method cannot.
* @public
*/
export class ApiMethodSignature extends ApiNameMixin(
ApiTypeParameterListMixin(
ApiParameterListMixin(ApiReleaseTagMixin(ApiReturnTypeMixin(ApiOptionalMixin(ApiDeclaredItem)))),
),
) {
public constructor(options: IApiMethodSignatureOptions) {
super(options);
}
public static getContainerKey(name: string, overloadIndex: number): string {
return `${name}|${ApiItemKind.MethodSignature}|${overloadIndex}`;
}
/**
* @override
*/
public override get kind(): ApiItemKind {
return ApiItemKind.MethodSignature;
}
/**
* @override
*/
public override get containerKey(): string {
return ApiMethodSignature.getContainerKey(this.name, this.overloadIndex);
}
/**
* @beta @override
*/
public override buildCanonicalReference(): DeclarationReference {
const nameComponent: Component = DeclarationReference.parseComponent(this.name);
return (this.parent ? this.parent.canonicalReference : DeclarationReference.empty())
.addNavigationStep(Navigation.Members as any, nameComponent)
.withMeaning(Meaning.Member as any)
.withOverloadIndex(this.overloadIndex);
}
}

View File

@@ -0,0 +1,207 @@
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
// See LICENSE in the project root for license information.
import { DocDeclarationReference } from '@microsoft/tsdoc';
import { DeclarationReference } from '@microsoft/tsdoc/lib-commonjs/beta/DeclarationReference.js';
import { PackageName } from '@rushstack/node-core-library';
import { ApiItem, ApiItemKind } from '../items/ApiItem.js';
import { ApiItemContainerMixin } from '../mixins/ApiItemContainerMixin.js';
import { ApiPackage } from './ApiPackage.js';
import { ModelReferenceResolver, type IResolveDeclarationReferenceResult } from './ModelReferenceResolver.js';
/**
* A serializable representation of a collection of API declarations.
*
* @remarks
*
* An `ApiModel` represents a collection of API declarations that can be serialized to disk. It captures all the
* important information needed to generate documentation, without any reliance on the TypeScript compiler engine.
*
* An `ApiModel` acts as the root of a tree of objects that all inherit from the `ApiItem` base class.
* The tree children are determined by the {@link (ApiItemContainerMixin:interface)} mixin base class. The model
* contains packages. Packages have an entry point (today, only one). And the entry point can contain various types
* of API declarations. The container relationships might look like this:
*
* ```
* Things that can contain other things:
*
* - ApiModel
* - ApiPackage
* - ApiEntryPoint
* - ApiClass
* - ApiMethod
* - ApiProperty
* - ApiEnum
* - ApiEnumMember
* - ApiInterface
* - ApiMethodSignature
* - ApiPropertySignature
* - ApiNamespace
* - (ApiClass, ApiEnum, ApiInterace, ...)
*
* ```
*
* Normally, API Extractor writes an .api.json file to disk for each project that it builds. Then, a tool like
* API Documenter can load the various `ApiPackage` objects into a single `ApiModel` and process them as a group.
* This is useful because compilation generally occurs separately (e.g. because projects may reside in different
* Git repos, or because they build with different TypeScript compiler configurations that may be incompatible),
* whereas API Documenter cannot detect broken hyperlinks without seeing the entire documentation set.
* @public
*/
export class ApiModel extends ApiItemContainerMixin(ApiItem) {
private readonly _resolver: ModelReferenceResolver;
private _packagesByName: Map<string, ApiPackage> | undefined = undefined;
private _apiItemsByCanonicalReference: Map<string, ApiItem> | undefined = undefined;
public constructor() {
super({});
this._resolver = new ModelReferenceResolver(this);
}
public loadPackage(apiJsonFilename: string): ApiPackage {
const apiPackage: ApiPackage = ApiPackage.loadFromJsonFile(apiJsonFilename);
this.addMember(apiPackage);
return apiPackage;
}
/**
* @override
*/
public override get kind(): ApiItemKind {
return ApiItemKind.Model;
}
/**
* @override
*/
// eslint-disable-next-line @typescript-eslint/class-literal-property-style
public override get containerKey(): string {
return '';
}
public get packages(): readonly ApiPackage[] {
return this.members as readonly ApiPackage[];
}
/**
* @override
*/
public override addMember(member: ApiPackage): void {
if (member.kind !== ApiItemKind.Package) {
throw new Error('Only items of type ApiPackage may be added to an ApiModel');
}
super.addMember(member);
this._packagesByName = undefined; // invalidate the cache
this._apiItemsByCanonicalReference = undefined; // invalidate the cache
}
/**
* Efficiently finds a package by the NPM package name.
*
* @remarks
*
* If the NPM scope is omitted in the package name, it will still be found provided that it is an unambiguous match.
* For example, it's often convenient to write `{@link node-core-library#JsonFile}` instead of
* `{@link @rushstack/node-core-library#JsonFile}`.
*/
public tryGetPackageByName(packageName: string): ApiPackage | undefined {
// Build the lookup on demand
if (this._packagesByName === undefined) {
this._packagesByName = new Map<string, ApiPackage>();
const unscopedMap: Map<string, ApiPackage | undefined> = new Map<string, ApiPackage | undefined>();
for (const apiPackage of this.packages) {
if (this._packagesByName.get(apiPackage.name)) {
// This should not happen
throw new Error(`The model contains multiple packages with the name ${apiPackage.name}`);
}
this._packagesByName.set(apiPackage.name, apiPackage);
const unscopedName: string = PackageName.parse(apiPackage.name).unscopedName;
if (unscopedMap.has(unscopedName)) {
// If another package has the same unscoped name, then we won't register it
unscopedMap.set(unscopedName, undefined);
} else {
unscopedMap.set(unscopedName, apiPackage);
}
}
for (const [unscopedName, apiPackage] of unscopedMap) {
if (apiPackage && !this._packagesByName.has(unscopedName)) {
// If the unscoped name is unambiguous, then we can also use it as a lookup
this._packagesByName.set(unscopedName, apiPackage);
}
}
}
return this._packagesByName.get(packageName);
}
public resolveDeclarationReference(
declarationReference: DeclarationReference | DocDeclarationReference,
contextApiItem: ApiItem | undefined,
): IResolveDeclarationReferenceResult {
if (declarationReference instanceof DocDeclarationReference) {
return this._resolver.resolve(declarationReference, contextApiItem);
} else if (declarationReference instanceof DeclarationReference) {
// use this._apiItemsByCanonicalReference to look up ApiItem
// Build the lookup on demand
if (!this._apiItemsByCanonicalReference) {
this._apiItemsByCanonicalReference = new Map<string, ApiItem>();
for (const apiPackage of this.packages) {
this._initApiItemsRecursive(apiPackage, this._apiItemsByCanonicalReference);
}
}
const result: IResolveDeclarationReferenceResult = {
resolvedApiItem: undefined,
errorMessage: undefined,
};
const apiItem: ApiItem | undefined = this._apiItemsByCanonicalReference.get(declarationReference.toString());
if (apiItem) {
result.resolvedApiItem = apiItem;
} else {
result.errorMessage = `${declarationReference.toString()} can not be located`;
}
return result;
} else {
// NOTE: The "instanceof DeclarationReference" test assumes a specific version of the @microsoft/tsdoc package.
throw new TypeError(
'The "declarationReference" parameter must be an instance of' +
' DocDeclarationReference or DeclarationReference',
);
}
}
private _initApiItemsRecursive(apiItem: ApiItem, apiItemsByCanonicalReference: Map<string, ApiItem>): void {
if (apiItem.canonicalReference && !apiItem.canonicalReference.isEmpty) {
apiItemsByCanonicalReference.set(apiItem.canonicalReference.toString(), apiItem);
}
// Recurse container members
if (ApiItemContainerMixin.isBaseClassOf(apiItem)) {
for (const apiMember of apiItem.members) {
this._initApiItemsRecursive(apiMember, apiItemsByCanonicalReference);
}
}
}
/**
* @beta @override
*/
public override buildCanonicalReference(): DeclarationReference {
return DeclarationReference.empty();
}
}

View File

@@ -0,0 +1,80 @@
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
// See LICENSE in the project root for license information.
import { DeclarationReference, type Component } from '@microsoft/tsdoc/lib-commonjs/beta/DeclarationReference.js';
import { type IApiDeclaredItemOptions, ApiDeclaredItem } from '../items/ApiDeclaredItem.js';
import { ApiItemKind, Navigation, Meaning } from '../items/ApiItem.js';
import { type IApiExportedMixinOptions, ApiExportedMixin } from '../mixins/ApiExportedMixin.js';
import { ApiItemContainerMixin, type IApiItemContainerMixinOptions } from '../mixins/ApiItemContainerMixin.js';
import { type IApiNameMixinOptions, ApiNameMixin } from '../mixins/ApiNameMixin.js';
import { ApiReleaseTagMixin, type IApiReleaseTagMixinOptions } from '../mixins/ApiReleaseTagMixin.js';
/**
* Constructor options for {@link ApiClass}.
*
* @public
*/
export interface IApiNamespaceOptions
extends IApiItemContainerMixinOptions,
IApiNameMixinOptions,
IApiReleaseTagMixinOptions,
IApiDeclaredItemOptions,
IApiExportedMixinOptions {}
/**
* Represents a TypeScript namespace declaration.
*
* @remarks
*
* This is part of the {@link ApiModel} hierarchy of classes, which are serializable representations of
* API declarations.
*
* `ApiNamespace` represents a TypeScript declaration such `X` or `Y` in this example:
*
* ```ts
* export namespace X {
* export namespace Y {
* export interface IWidget {
* render(): void;
* }
* }
* }
* ```
* @public
*/
export class ApiNamespace extends ApiItemContainerMixin(
ApiNameMixin(ApiReleaseTagMixin(ApiExportedMixin(ApiDeclaredItem))),
) {
public constructor(options: IApiNamespaceOptions) {
super(options);
}
public static getContainerKey(name: string): string {
return `${name}|${ApiItemKind.Namespace}`;
}
/**
* @override
*/
public override get kind(): ApiItemKind {
return ApiItemKind.Namespace;
}
/**
* @override
*/
public override get containerKey(): string {
return ApiNamespace.getContainerKey(this.name);
}
/**
* @beta @override
*/
public override buildCanonicalReference(): DeclarationReference {
const nameComponent: Component = DeclarationReference.parseComponent(this.name);
const navigation: Navigation = this.isExported ? Navigation.Exports : Navigation.Locals;
return (this.parent ? this.parent.canonicalReference : DeclarationReference.empty())
.addNavigationStep(navigation as any, nameComponent)
.withMeaning(Meaning.Namespace as any);
}
}

View File

@@ -0,0 +1,312 @@
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
// See LICENSE in the project root for license information.
import { TSDocConfiguration } from '@microsoft/tsdoc';
import { DeclarationReference } from '@microsoft/tsdoc/lib-commonjs/beta/DeclarationReference.js';
import { TSDocConfigFile } from '@microsoft/tsdoc-config';
import {
JsonFile,
type IJsonFileSaveOptions,
PackageJsonLookup,
type IPackageJson,
type JsonObject,
} from '@rushstack/node-core-library';
import { ApiDocumentedItem, type IApiDocumentedItemOptions } from '../items/ApiDocumentedItem.js';
import { ApiItem, ApiItemKind, type IApiItemJson } from '../items/ApiItem.js';
import { ApiItemContainerMixin, type IApiItemContainerMixinOptions } from '../mixins/ApiItemContainerMixin.js';
import { type IApiNameMixinOptions, ApiNameMixin } from '../mixins/ApiNameMixin.js';
import type { ApiEntryPoint } from './ApiEntryPoint.js';
import { DeserializerContext, ApiJsonSchemaVersion } from './DeserializerContext.js';
/**
* Constructor options for {@link ApiPackage}.
*
* @public
*/
export interface IApiPackageOptions
extends IApiItemContainerMixinOptions,
IApiNameMixinOptions,
IApiDocumentedItemOptions {
projectFolderUrl?: string | undefined;
tsdocConfiguration: TSDocConfiguration;
}
export interface IApiPackageMetadataJson {
/**
* To support forwards compatibility, the `oldestForwardsCompatibleVersion` field tracks the oldest schema version
* whose corresponding deserializer could safely load this file.
*
* @remarks
* Normally api-extractor-model should refuse to load a schema version that is newer than the latest version
* that its deserializer understands. However, sometimes a schema change may merely introduce some new fields
* without modifying or removing any existing fields. In this case, an older api-extractor-model library can
* safely deserialize the newer version (by ignoring the extra fields that it doesn't recognize). The newer
* serializer can use this field to communicate that.
*
* If present, the `oldestForwardsCompatibleVersion` must be less than or equal to
* `IApiPackageMetadataJson.schemaVersion`.
*/
oldestForwardsCompatibleVersion?: ApiJsonSchemaVersion;
/**
* The schema version for the .api.json file format. Used for determining whether the file format is
* supported, and for backwards compatibility.
*/
schemaVersion: ApiJsonSchemaVersion;
/**
* The NPM package name for the tool that wrote the *.api.json file.
* For informational purposes only.
*/
toolPackage: string;
/**
* The NPM package version for the tool that wrote the *.api.json file.
* For informational purposes only.
*/
toolVersion: string;
/**
* The TSDoc configuration that was used when analyzing the API for this package.
*
* @remarks
*
* The structure of this objet is defined by the `@microsoft/tsdoc-config` library.
* Normally this configuration is loaded from the project's tsdoc.json file. It is stored
* in the .api.json file so that doc comments can be parsed accurately when loading the file.
*/
tsdocConfig: JsonObject;
}
export interface IApiPackageJson extends IApiItemJson {
/**
* A file header that stores metadata about the tool that wrote the *.api.json file.
*/
metadata: IApiPackageMetadataJson;
/**
* The base URL where the project's source code can be viewed on a website such as GitHub or
* Azure DevOps. This URL path corresponds to the `<projectFolder>` path on disk. Provided via the
* `api-extractor.json` config.
*/
projectFolderUrl?: string;
}
/**
* Options for {@link ApiPackage.saveToJsonFile}.
*
* @public
*/
export interface IApiPackageSaveOptions extends IJsonFileSaveOptions {
/**
* Set to true only when invoking API Extractor's test harness.
*
* @remarks
* When `testMode` is true, the `toolVersion` field in the .api.json file is assigned an empty string
* to prevent spurious diffs in output files tracked for tests.
*/
testMode?: boolean;
/**
* Optionally specifies a value for the "toolPackage" field in the output .api.json data file;
* otherwise, the value will be "api-extractor-model".
*/
toolPackage?: string;
/**
* Optionally specifies a value for the "toolVersion" field in the output .api.json data file;
* otherwise, the value will be the current version of the api-extractor-model package.
*/
toolVersion?: string;
}
/**
* Represents an NPM package containing API declarations.
*
* @remarks
*
* This is part of the {@link ApiModel} hierarchy of classes, which are serializable representations of
* API declarations.
* @public
*/
export class ApiPackage extends ApiItemContainerMixin(ApiNameMixin(ApiDocumentedItem)) {
private readonly _tsdocConfiguration: TSDocConfiguration;
private readonly _projectFolderUrl?: string | undefined;
public constructor(options: IApiPackageOptions) {
super(options);
this._tsdocConfiguration = options.tsdocConfiguration;
this._projectFolderUrl = options.projectFolderUrl;
}
/**
* @override
*/
public static override onDeserializeInto(
options: Partial<IApiPackageOptions>,
context: DeserializerContext,
jsonObject: IApiPackageJson,
): void {
super.onDeserializeInto(options, context, jsonObject);
options.projectFolderUrl = jsonObject.projectFolderUrl;
}
public static loadFromJsonFile(apiJsonFilename: string): ApiPackage {
const jsonObject: IApiPackageJson = JsonFile.load(apiJsonFilename);
if (!jsonObject?.metadata || typeof jsonObject.metadata.schemaVersion !== 'number') {
throw new Error(
`Error loading ${apiJsonFilename}:` +
`\nThe file format is not recognized; the "metadata.schemaVersion" field is missing or invalid`,
);
}
const schemaVersion: number = jsonObject.metadata.schemaVersion;
if (schemaVersion < ApiJsonSchemaVersion.OLDEST_SUPPORTED) {
throw new Error(
`Error loading ${apiJsonFilename}:` +
`\nThe file format is version ${schemaVersion},` +
` whereas ${ApiJsonSchemaVersion.OLDEST_SUPPORTED} is the oldest version supported by this tool`,
);
}
let oldestForwardsCompatibleVersion: number = schemaVersion;
if (jsonObject.metadata.oldestForwardsCompatibleVersion) {
// Sanity check
if (jsonObject.metadata.oldestForwardsCompatibleVersion > schemaVersion) {
throw new Error(
`Error loading ${apiJsonFilename}:` +
`\nInvalid file format; "oldestForwardsCompatibleVersion" cannot be newer than "schemaVersion"`,
);
}
oldestForwardsCompatibleVersion = jsonObject.metadata.oldestForwardsCompatibleVersion;
}
let versionToDeserialize: number = schemaVersion;
if (versionToDeserialize > ApiJsonSchemaVersion.LATEST) {
// If the file format is too new, can we treat it as some earlier compatible version
// as indicated by oldestForwardsCompatibleVersion?
versionToDeserialize = Math.max(oldestForwardsCompatibleVersion, ApiJsonSchemaVersion.LATEST);
if (versionToDeserialize > ApiJsonSchemaVersion.LATEST) {
// Nope, still too new
throw new Error(
`Error loading ${apiJsonFilename}:` +
`\nThe file format version ${schemaVersion} was written by a newer release of` +
` the api-extractor-model library; you may need to upgrade your software`,
);
}
}
const tsdocConfiguration: TSDocConfiguration = new TSDocConfiguration();
if (versionToDeserialize >= ApiJsonSchemaVersion.V_1004) {
const tsdocConfigFile: TSDocConfigFile = TSDocConfigFile.loadFromObject(jsonObject.metadata.tsdocConfig);
if (tsdocConfigFile.hasErrors) {
throw new Error(`Error loading ${apiJsonFilename}:\n` + tsdocConfigFile.getErrorSummary());
}
tsdocConfigFile.configureParser(tsdocConfiguration);
}
const context: DeserializerContext = new DeserializerContext({
apiJsonFilename,
toolPackage: jsonObject.metadata.toolPackage,
toolVersion: jsonObject.metadata.toolVersion,
versionToDeserialize,
tsdocConfiguration,
});
return ApiItem.deserialize(jsonObject, context) as ApiPackage;
}
/**
* @override
*/
public override get kind(): ApiItemKind {
return ApiItemKind.Package;
}
/**
* @override
*/
public override get containerKey(): string {
// No prefix needed, because ApiPackage is the only possible member of an ApiModel
return this.name;
}
public get entryPoints(): readonly ApiEntryPoint[] {
return this.members as readonly ApiEntryPoint[];
}
/**
* The TSDoc configuration that was used when analyzing the API for this package.
*
* @remarks
*
* Normally this configuration is loaded from the project's tsdoc.json file. It is stored
* in the .api.json file so that doc comments can be parsed accurately when loading the file.
*/
public get tsdocConfiguration(): TSDocConfiguration {
return this._tsdocConfiguration;
}
public get projectFolderUrl(): string | undefined {
return this._projectFolderUrl;
}
/**
* @override
*/
public override addMember(member: ApiEntryPoint): void {
if (member.kind !== ApiItemKind.EntryPoint) {
throw new Error('Only items of type ApiEntryPoint may be added to an ApiPackage');
}
super.addMember(member);
}
public findEntryPointsByPath(importPath: string): readonly ApiEntryPoint[] {
return this.findMembersByName(importPath) as readonly ApiEntryPoint[];
}
public saveToJsonFile(apiJsonFilename: string, options?: IApiPackageSaveOptions): void {
const ioptions = options ?? {};
const packageJson: IPackageJson = PackageJsonLookup.loadOwnPackageJson(__dirname);
const tsdocConfigFile: TSDocConfigFile = TSDocConfigFile.loadFromParser(this.tsdocConfiguration);
const tsdocConfig: JsonObject = tsdocConfigFile.saveToObject();
const jsonObject: IApiPackageJson = {
metadata: {
toolPackage: ioptions.toolPackage ?? packageJson.name,
// In test mode, we don't write the real version, since that would cause spurious diffs whenever
// the version is bumped. Instead we write a placeholder string.
toolVersion: ioptions.testMode ? '[test mode]' : ioptions.toolVersion ?? packageJson.version,
schemaVersion: ApiJsonSchemaVersion.LATEST,
oldestForwardsCompatibleVersion: ApiJsonSchemaVersion.OLDEST_FORWARDS_COMPATIBLE,
tsdocConfig,
},
} as IApiPackageJson;
if (this.projectFolderUrl) {
jsonObject.projectFolderUrl = this.projectFolderUrl;
}
this.serializeInto(jsonObject);
JsonFile.save(jsonObject, apiJsonFilename, ioptions);
}
/**
* @beta @override
*/
public override buildCanonicalReference(): DeclarationReference {
return DeclarationReference.package(this.name);
}
}

Some files were not shown because too many files have changed in this diff Show More