diff --git a/apps/website/src/components/ExcerptText.tsx b/apps/website/src/components/ExcerptText.tsx index bd7b89592..68ee9c875 100644 --- a/apps/website/src/components/ExcerptText.tsx +++ b/apps/website/src/components/ExcerptText.tsx @@ -24,14 +24,12 @@ export function ExcerptText({ model, excerpt }: ExcerptTextProps) { return ( {excerpt.spannedTokens.map((token, idx) => { - // TODO: Real fix in api-extractor needed - const text = token.text.replaceAll('\n', '').replaceAll(/\s{2}$/g, ''); if (token.kind === ExcerptTokenKind.Reference) { - if (text in BuiltinDocumentationLinks) { - const href = BuiltinDocumentationLinks[text as keyof typeof BuiltinDocumentationLinks]; + if (token.text in BuiltinDocumentationLinks) { + const href = BuiltinDocumentationLinks[token.text as keyof typeof BuiltinDocumentationLinks]; return ( - - {text} + + {token.text} ); } @@ -45,20 +43,22 @@ export function ExcerptText({ model, excerpt }: ExcerptTextProps) { // dapi-types doesn't have routes for class members // so we can assume this member is for an enum if (meaning === 'member' && path && 'parent' in path) href += `/enum/${path.parent}#${path.component}`; - else if (meaning === 'type') href += `#${text}`; - else href += `/${meaning}/${text}`; + else if (meaning === 'type' || meaning === 'var') href += `#${token.text}`; + else href += `/${meaning}/${token.text}`; return ( - - {text} + + {token.text} ); } - const item = model.resolveDeclarationReference(token.canonicalReference!, model).resolvedApiItem; + const item = token.canonicalReference + ? model.resolveDeclarationReference(token.canonicalReference!, model).resolvedApiItem + : null; if (!item) { - return text; + return token.text; } return ( @@ -68,12 +68,12 @@ export function ExcerptText({ model, excerpt }: ExcerptTextProps) { key={`${item.displayName}-${item.containerKey}-${idx}`} packageName={item.getAssociatedPackage()?.displayName.replace('@discordjs/', '')} > - {text} + {token.text} ); } - return text.replace(/import\("discord-api-types(?:\/v\d+)?"\)\./, ''); + return token.text.replace(/import\("discord-api-types(?:\/v\d+)?"\)\./, ''); })} ); diff --git a/apps/website/src/components/TableOfContentItems.tsx b/apps/website/src/components/TableOfContentItems.tsx index 544b25c78..a1e333160 100644 --- a/apps/website/src/components/TableOfContentItems.tsx +++ b/apps/website/src/components/TableOfContentItems.tsx @@ -127,7 +127,7 @@ export function TableOfContentItems({ serializedMembers }: TableOfContentsItemPr
- Properties + Events
{eventItems} diff --git a/packages/api-extractor-model/src/mixins/ApiItemContainerMixin.ts b/packages/api-extractor-model/src/mixins/ApiItemContainerMixin.ts index 9da10d3eb..5e50629da 100644 --- a/packages/api-extractor-model/src/mixins/ApiItemContainerMixin.ts +++ b/packages/api-extractor-model/src/mixins/ApiItemContainerMixin.ts @@ -1,9 +1,13 @@ +/* eslint-disable @typescript-eslint/no-loop-func */ // 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 type { DeclarationReference } from '@microsoft/tsdoc/lib-commonjs/beta/DeclarationReference.js'; import { InternalError } from '@rushstack/node-core-library'; -import type { ApiDeclaredItem } from '../index.js'; +import type { IExcerptToken, IExcerptTokenRange } from '../index.js'; +import { ApiDeclaredItem } from '../index.js'; +import type { IApiDeclaredItemJson } from '../items/ApiDeclaredItem.js'; import { ApiItem, apiItem_onParentChanged, @@ -15,11 +19,12 @@ import { 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 { ApiJsonSchemaVersion, 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 { ExcerptToken } from './Excerpt.js'; +import { ExcerptTokenKind } from './Excerpt.js'; import { type IFindApiItemsResult, type IFindApiItemsMessage, FindApiItemsMessageId } from './IFindApiItemsResult.js'; /** @@ -37,9 +42,13 @@ export interface IApiItemContainerJson extends IApiItemJson { preserveMemberOrder?: boolean; } +interface ExcerptTokenRangeInDeclaredItem { + item: ApiDeclaredItem; + range: IExcerptTokenRange; +} interface IMappedTypeParameters { item: ApiItem; - mappedTypeParameters: Map; + mappedTypeParameters: Map; } const _members: unique symbol = Symbol('ApiItemContainerMixin._members'); @@ -317,7 +326,7 @@ export function ApiItemContainerMixin( let next: IMappedTypeParameters | undefined = { item: this, mappedTypeParameters: new Map() }; while (next?.item) { - const membersToAdd: ApiItem[] = []; /* + const membersToAdd: ApiItem[] = []; //* const typeParams = next.mappedTypeParameters; const context: DeserializerContext = { apiJsonFilename: '', @@ -325,56 +334,46 @@ export function ApiItemContainerMixin( toolVersion: '', versionToDeserialize: ApiJsonSchemaVersion.LATEST, tsdocConfiguration: new TSDocConfiguration(), - }; */ + }; // */ + + // 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'); // 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.item.members) { + for (let member of next.item.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)) { - // This was supposed to replace type parameters with their assigned values in inheritance, but doesn't work yet - /* - if ( - ApiTypeParameterListMixin.isBaseClassOf(member) && - member.typeParameters.some((param) => typeParams.has(param.name)) - ) { - const jsonObject: Partial = {}; - member.serializeInto(jsonObject); - member = deserializerModule.Deserializer.deserialize(context, { - ...jsonObject, - typeParameters: (jsonObject as IApiTypeParameterListMixinJson).typeParameters.map( - ({ typeParameterName, constraintTokenRange, defaultTypeTokenRange }) => ({ - typeParameterName: typeParams.get(typeParameterName) ?? typeParameterName, - defaultTypeTokenRange, - constraintTokenRange, - }), - ), - } as IApiTypeParameterListMixinJson); + + // This was supposed to replace type parameters with their assigned values in inheritance, but doesn't work yet + //* + if (member instanceof ApiDeclaredItem && member.excerptTokens.some((token) => typeParams.has(token.text))) { + const jsonObject: Partial = {}; + member.serializeInto(jsonObject); + const excerptTokens = (jsonObject as IApiDeclaredItemJson).excerptTokens.map((token) => { + let x: ExcerptToken | undefined; + if (typeParams.has(token.text) && next?.item instanceof ApiDeclaredItem) { + const originalValue = typeParams.get(token.text)!; + x = originalValue.item.excerptTokens[originalValue.range.startIndex]; } - if (ApiReturnTypeMixin.isBaseClassOf(member)) { - const jsonObject: Partial = {}; - member.serializeInto(jsonObject); - member = deserializerModule.Deserializer.deserialize(context, { - ...(jsonObject as IApiReturnTypeMixinJson), - excerptTokens: (jsonObject as IApiDeclaredItemJson).excerptTokens.map((token) => - token.kind === ExcerptTokenKind.Content - ? { - kind: ExcerptTokenKind.Content, - text: [...typeParams.keys()].reduce( - (tok, typ) => tok.replaceAll(new RegExp(`\b${typ}\b`, 'g'), typeParams.get(typ)!), - token.text, - ), - } - : token, - ), - } as IApiReturnTypeMixinJson); - member[apiItem_onParentChanged](next.item); - } // */ + const excerptToken: IExcerptToken = x ? { kind: x.kind, text: x.text } : token; + if (x?.canonicalReference !== undefined) { + excerptToken.canonicalReference = x.canonicalReference.toString(); + } + return excerptToken; + }); + member = deserializerModule.Deserializer.deserialize(context, { + ...jsonObject, + excerptTokens, + } as IApiDeclaredItemJson); + member[apiItem_onParentChanged](next.item); + } + + if (ApiNameMixin.isBaseClassOf(member)) { + if (!membersByName.has(member.name)) { membersToAdd.push(member); } } else if (!membersByKind.has(member.kind)) { @@ -478,14 +477,20 @@ export function ApiItemContainerMixin( continue; } - const mappedTypeParameters: Map = new Map(); + const mappedTypeParameters: Map = new Map(); if ( (apiItem.kind === ApiItemKind.Class || apiItem.kind === ApiItemKind.Interface) && next.item.kind === ApiItemKind.Class ) { - for (const [index, typeParameter] of extendsType.typeParameters?.entries() ?? []) { - const key = (apiItem as ApiClass | ApiInterface).typeParameters[index]?.name ?? ''; - mappedTypeParameters.set(key, typeParameter); + for (const [index, key] of (apiItem as ApiClass | ApiInterface).typeParameters.entries() ?? []) { + const typeParameter = extendsType.typeParameters?.[index]; + if (typeParameter) + mappedTypeParameters.set(key.name, { item: next.item as ApiDeclaredItem, range: typeParameter }); + else if (key.defaultTypeExcerpt) + mappedTypeParameters.set(key.name, { + item: apiItem as ApiDeclaredItem, + range: key.defaultTypeExcerpt.tokenRange, + }); } } diff --git a/packages/api-extractor-model/src/model/ApiClass.ts b/packages/api-extractor-model/src/model/ApiClass.ts index e65a88bd8..7f8e86ec1 100644 --- a/packages/api-extractor-model/src/model/ApiClass.ts +++ b/packages/api-extractor-model/src/model/ApiClass.ts @@ -44,7 +44,7 @@ export interface IApiClassOptions } export interface IExcerptTokenRangeWithTypeParameters extends IExcerptTokenRange { - typeParameters: string[]; + typeParameters: IExcerptTokenRange[]; } export interface IApiClassJson diff --git a/packages/api-extractor-model/src/model/DeserializerContext.ts b/packages/api-extractor-model/src/model/DeserializerContext.ts index daf4de626..b5778c8f9 100644 --- a/packages/api-extractor-model/src/model/DeserializerContext.ts +++ b/packages/api-extractor-model/src/model/DeserializerContext.ts @@ -91,13 +91,18 @@ export enum ApiJsonSchemaVersion { */ V_1011 = 1_011, + /** + * Add a `fileLine`and `fileColumn` field to track source code location + */ + V_1012 = 1_012, + /** * The current latest .api.json schema version. * * IMPORTANT: When incrementing this number, consider whether `OLDEST_SUPPORTED` or `OLDEST_FORWARDS_COMPATIBLE` * should be updated. */ - LATEST = V_1011, + LATEST = V_1012, /** * The oldest .api.json schema version that is still supported for backwards compatibility. diff --git a/packages/api-extractor-model/src/model/HeritageType.ts b/packages/api-extractor-model/src/model/HeritageType.ts index 1efe0f649..5332f4a9b 100644 --- a/packages/api-extractor-model/src/model/HeritageType.ts +++ b/packages/api-extractor-model/src/model/HeritageType.ts @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. // See LICENSE in the project root for license information. -import type { Excerpt } from '../mixins/Excerpt.js'; +import type { Excerpt, IExcerptTokenRange } from '../mixins/Excerpt.js'; /** * Represents a type referenced via an "extends" or "implements" heritage clause for a TypeScript class @@ -38,9 +38,9 @@ export class HeritageType { */ public readonly excerpt: Excerpt; - public readonly typeParameters?: string[]; + public readonly typeParameters?: IExcerptTokenRange[]; - public constructor(excerpt: Excerpt, typeParameters: string[]) { + public constructor(excerpt: Excerpt, typeParameters: IExcerptTokenRange[]) { this.excerpt = excerpt; this.typeParameters = typeParameters; } diff --git a/packages/api-extractor/src/generators/ApiModelGenerator.ts b/packages/api-extractor/src/generators/ApiModelGenerator.ts index 94a8f7aa7..dc2d0b012 100644 --- a/packages/api-extractor/src/generators/ApiModelGenerator.ts +++ b/packages/api-extractor/src/generators/ApiModelGenerator.ts @@ -538,9 +538,6 @@ export class ApiModelGenerator { if (apiClass === undefined) { const classDeclaration: ts.ClassDeclaration = astDeclaration.declaration as ts.ClassDeclaration; - if (name === 'ActionRow') { - console.dir(classDeclaration.heritageClauses?.[0]?.types[0]?.typeArguments, { depth: 3 }); - } const nodesToCapture: IExcerptBuilderNodeToCapture[] = []; @@ -557,9 +554,12 @@ export class ApiModelGenerator { extendsTokenRange = ExcerptBuilder.createEmptyTokenRangeWithTypeParameters(); if (heritageClause.types.length > 0) { extendsTokenRange.typeParameters.push( - ...(heritageClause.types[0]?.typeArguments?.map((typeArgument) => - ts.isTypeReferenceNode(typeArgument) ? typeArgument.typeName.getText() : '', - ) ?? []), + ...(heritageClause.types[0]?.typeArguments?.map((typeArgument) => { + const typeArgumentTokenRange = ExcerptBuilder.createEmptyTokenRange(); + nodesToCapture.push({ node: typeArgument, tokenRange: typeArgumentTokenRange }); + + return typeArgumentTokenRange; + }) ?? []), ); nodesToCapture.push({ node: heritageClause.types[0], tokenRange: extendsTokenRange }); } @@ -568,9 +568,14 @@ export class ApiModelGenerator { const implementsTokenRange: IExcerptTokenRangeWithTypeParameters = ExcerptBuilder.createEmptyTokenRangeWithTypeParameters(); implementsTokenRange.typeParameters.push( - ...(heritageClause.types[0]?.typeArguments?.map((typeArgument) => - ts.isTypeReferenceNode(typeArgument) ? typeArgument.typeName.getText() : '', - ) ?? []), + ...(heritageType.typeArguments?.map((typeArgument) => { + const typeArgumentTokenRange = ExcerptBuilder.createEmptyTokenRange(); + if (ts.isTypeReferenceNode(typeArgument)) { + nodesToCapture.push({ node: typeArgument, tokenRange: typeArgumentTokenRange }); + } + + return typeArgumentTokenRange; + }) ?? []), ); implementsTokenRanges.push(implementsTokenRange); nodesToCapture.push({ node: heritageType, tokenRange: implementsTokenRange }); @@ -893,9 +898,14 @@ export class ApiModelGenerator { const extendsTokenRange: IExcerptTokenRangeWithTypeParameters = ExcerptBuilder.createEmptyTokenRangeWithTypeParameters(); extendsTokenRange.typeParameters.push( - ...(heritageClause.types[0]?.typeArguments?.map((typeArgument) => - ts.isTypeReferenceNode(typeArgument) ? typeArgument.typeName.getText() : '', - ) ?? []), + ...(heritageType.typeArguments?.map((typeArgument) => { + const typeArgumentTokenRange = ExcerptBuilder.createEmptyTokenRange(); + if (ts.isTypeReferenceNode(typeArgument)) { + nodesToCapture.push({ node: typeArgument, tokenRange: typeArgumentTokenRange }); + } + + return typeArgumentTokenRange; + }) ?? []), ); extendsTokenRanges.push(extendsTokenRange); nodesToCapture.push({ node: heritageType, tokenRange: extendsTokenRange }); diff --git a/packages/api-extractor/src/generators/ExcerptBuilder.ts b/packages/api-extractor/src/generators/ExcerptBuilder.ts index 39ea83bf5..da6b0fd69 100644 --- a/packages/api-extractor/src/generators/ExcerptBuilder.ts +++ b/packages/api-extractor/src/generators/ExcerptBuilder.ts @@ -176,14 +176,17 @@ export class ExcerptBuilder { if (span.kind === ts.SyntaxKind.Identifier) { const name: ts.Identifier = span.node as ts.Identifier; - if (!ExcerptBuilder._isDeclarationName(name)) { - canonicalReference = state.referenceGenerator.getDeclarationReferenceForIdentifier(name); - } + canonicalReference = state.referenceGenerator.getDeclarationReferenceForIdentifier(name); } if (canonicalReference) { ExcerptBuilder._appendToken(excerptTokens, ExcerptTokenKind.Reference, span.prefix, canonicalReference); - } else if (ExcerptBuilder.isPrimitiveKeyword(span.node)) { + } else if ( + ExcerptBuilder.isPrimitiveKeyword(span.node) || + (span.node.kind === ts.SyntaxKind.Identifier && + ((ts.isTypeReferenceNode(span.node.parent) && span.node.parent.typeName === span.node) || + (ts.isTypeParameterDeclaration(span.node.parent) && span.node.parent.name === span.node))) + ) { ExcerptBuilder._appendToken(excerptTokens, ExcerptTokenKind.Reference, span.prefix); } else { ExcerptBuilder._appendToken(excerptTokens, ExcerptTokenKind.Content, span.prefix); @@ -209,7 +212,11 @@ export class ExcerptBuilder { } if (span.separator) { - ExcerptBuilder._appendToken(excerptTokens, ExcerptTokenKind.Content, span.separator); + ExcerptBuilder._appendToken( + excerptTokens, + ExcerptTokenKind.Content, + span.separator.replaceAll('\n', '').replaceAll(/\s{2}/g, ' '), + ); state.lastAppendedTokenIsSeparator = true; } @@ -335,7 +342,7 @@ export class ExcerptBuilder { } } } - } + } /* private static _isDeclarationName(name: ts.Identifier): boolean { return ExcerptBuilder._isDeclaration(name.parent) && name.parent.name === name; @@ -366,5 +373,5 @@ export class ExcerptBuilder { default: return false; } - } + } // */ }