diff --git a/apps/website/src/components/ExcerptText.tsx b/apps/website/src/components/ExcerptText.tsx index bcf552563..c5050ef2f 100644 --- a/apps/website/src/components/ExcerptText.tsx +++ b/apps/website/src/components/ExcerptText.tsx @@ -38,9 +38,13 @@ export function ExcerptText({ 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' || meaning === 'var') href += `#${token.text}`; - else href += `/${meaning}/${token.text}`; + if (meaning === 'member' && path && 'parent' in path) { + href += `/enum/${path.parent}#${path.component}`; + } else if (meaning === 'type' || meaning === 'var') { + href += `#${token.text}`; + } else { + href += `/${meaning}/${token.text}`; + } return ( diff --git a/apps/website/src/components/documentation/tsdoc/TSDoc.tsx b/apps/website/src/components/documentation/tsdoc/TSDoc.tsx index a0a0854fb..487e2edb0 100644 --- a/apps/website/src/components/documentation/tsdoc/TSDoc.tsx +++ b/apps/website/src/components/documentation/tsdoc/TSDoc.tsx @@ -6,6 +6,7 @@ import Link from 'next/link'; import { Fragment, useCallback, type ReactNode } from 'react'; import { DocumentationLink } from '~/components/DocumentationLink'; import { BuiltinDocumentationLinks } from '~/util/builtinDocumentationLinks'; +import { DISCORD_API_TYPES_DOCS_URL } from '~/util/constants'; import { ItemLink } from '../../ItemLink'; import { SyntaxHighlighter } from '../../SyntaxHighlighter'; import { resolveCanonicalReference, resolveItemURI } from '../util'; @@ -32,7 +33,6 @@ export function TSDoc({ item, tsdoc }: { readonly item: ApiItem; readonly tsdoc: return ; case DocNodeKind.LinkTag: { const { codeDestination, urlDestination, linkText } = tsdoc as DocLinkTag; - if (codeDestination) { if ( !codeDestination.importPath && @@ -56,6 +56,28 @@ export function TSDoc({ item, tsdoc }: { readonly item: ApiItem; readonly tsdoc: if (!foundItem && !resolved) return null; + if (resolved && resolved.package === 'discord-api-types') { + const { displayName, kind, members, containerKey } = resolved.item; + let href = DISCORD_API_TYPES_DOCS_URL; + + // dapi-types doesn't have routes for class members + // so we can assume this member is for an enum + if (kind === 'enum' && members?.[0]) { + href += `/enum/${displayName}#${members[0].displayName}`; + } else if (kind === 'type' || kind === 'var') { + href += `#${displayName}`; + } else { + href += `/${kind}/${displayName}`; + } + + return ( + + {displayName} + {members?.map((member) => `.${member.displayName}`).join('') ?? ''} + + ); + } + return ( member.kind === ApiItemKind.Property || member.kind === ApiItemKind.PropertySignature, @@ -83,9 +100,12 @@ export function resolveCanonicalReference( return { package: canonicalReference.packageName?.replace('@discordjs/', ''), item: { - kind: member.selector!.selector as ApiItemKind, + kind: member.selector!.selector, displayName: member.memberIdentifier!.identifier, containerKey: `|${member.selector!.selector}|${member.memberIdentifier!.identifier}`, + members: canonicalReference.memberReferences + .slice(1) + .map((member) => ({ kind: member.kind, displayName: member.memberIdentifier!.identifier! })), }, }; } @@ -93,37 +113,12 @@ export function resolveCanonicalReference( return null; } -function mapMeaningToKind(meaning: Meaning): ApiItemKind { - switch (meaning) { - case Meaning.CallSignature: - return ApiItemKind.CallSignature; - case Meaning.Class: - return ApiItemKind.Class; - case Meaning.ComplexType: - throw new Error('Not a valid canonicalReference: Meaning.ComplexType'); - case Meaning.ConstructSignature: - return ApiItemKind.ConstructSignature; - case Meaning.Constructor: - return ApiItemKind.Constructor; - case Meaning.Enum: - return ApiItemKind.Enum; - case Meaning.Event: - return ApiItemKind.Event; - case Meaning.Function: - return ApiItemKind.Function; - case Meaning.IndexSignature: - return ApiItemKind.IndexSignature; - case Meaning.Interface: - return ApiItemKind.Interface; - case Meaning.Member: - return ApiItemKind.Property; - case Meaning.Namespace: - return ApiItemKind.Namespace; - case Meaning.TypeAlias: - return ApiItemKind.TypeAlias; - case Meaning.Variable: - return ApiItemKind.Variable; - } +export function mapMeaningToKind(meaning: Meaning): ApiItemKind { + return [...kindToMeaning.entries()].find((mapping) => mapping[1] === meaning)?.[0] ?? ApiItemKind.None; +} + +export function mapKindToMeaning(kind: ApiItemKind): Meaning { + return kindToMeaning.get(kind) ?? Meaning.Variable; } export function memberPredicate( diff --git a/packages/api-extractor/src/generators/ApiModelGenerator.ts b/packages/api-extractor/src/generators/ApiModelGenerator.ts index 983ee49a9..567d232c9 100644 --- a/packages/api-extractor/src/generators/ApiModelGenerator.ts +++ b/packages/api-extractor/src/generators/ApiModelGenerator.ts @@ -210,14 +210,8 @@ interface IProcessAstEntityContext { parentDocgenJson?: DocgenContainerJson | undefined; } -const linkRegEx = /{@link\s(?\w+)#(?event:)?(?[\w()]+)(?\s[^}]*)?}/g; -function fixLinkTags(input?: string): string | undefined { - return input?.replaceAll( - linkRegEx, - (_match, _p1, _p2, _p3, _p4, _offset, _string, groups) => - `{@link ${groups.class}.${groups.prop}${groups.name ? ` |${groups.name}` : ''}}`, - ); -} +const linkRegEx = + /{@link\s(?:(?\w+)(?:[#.](?event:)?(?[\w()]+))?|(?https?:\/\/[^\s}]*))(?\s[^}]*)?}/g; function filePathFromJson(meta: DocgenMetaJson): string { return `${meta.path.slice('packages/discord.js/'.length)}/${meta.file}`; @@ -243,6 +237,8 @@ export class ApiModelGenerator { private readonly _referenceGenerator: DeclarationReferenceGenerator; + private readonly _jsDocJson: DocgenJson | undefined; + public constructor(collector: Collector) { this._collector = collector; this._apiModel = new ApiModel(); @@ -257,11 +253,11 @@ export class ApiModelGenerator { public buildApiPackage(): ApiPackage { const packageDocComment: tsdoc.DocComment | undefined = this._collector.workingPackage.tsdocComment; - let jsDocJson: DocgenJson | undefined; const jsDocFilepath = `${this._collector.extractorConfig.apiJsonFilePath.slice(0, -8)}json`; if (existsSync(jsDocFilepath)) { - jsDocJson = JsonFile.load(jsDocFilepath); + // @ts-expect-error assign value only when starting to build a new ApiPackage + this._jsDocJson = JsonFile.load(jsDocFilepath); } const apiPackage: ApiPackage = new ApiPackage({ @@ -284,7 +280,7 @@ export class ApiModelGenerator { name: entity.nameForEmit!, isExported: entity.exportedFromEntryPoint, parentApiItem: apiEntryPoint, - parentDocgenJson: jsDocJson, + parentDocgenJson: this._jsDocJson, }); } } @@ -560,9 +556,9 @@ export class ApiModelGenerator { const apiItemMetadata: ApiItemMetadata = this._collector.fetchApiItemMetadata(astDeclaration); const docComment: tsdoc.DocComment | undefined = parent?.construct ? this._tsDocParser.parseString( - `/*+\n * ${fixLinkTags(parent.construct.description)}\n${ + `/*+\n * ${this._fixLinkTags(parent.construct.description) ?? ''}\n${ parent.construct.params - ?.map((param) => ` * @param ${param.name} - ${fixLinkTags(param.description)}\n`) + ?.map((param) => ` * @param ${param.name} - ${this._fixLinkTags(param.description) ?? ''}\n`) .join('') ?? '' } */`, ).docComment @@ -646,10 +642,12 @@ export class ApiModelGenerator { const apiItemMetadata: ApiItemMetadata = this._collector.fetchApiItemMetadata(astDeclaration); const docComment: tsdoc.DocComment | undefined = jsDoc ? this._tsDocParser.parseString( - `/**\n * ${fixLinkTags(jsDoc.description)}\n${jsDoc.see?.map((see) => ` * @see ${see}\n`).join('') ?? ''}${ + `/**\n * ${this._fixLinkTags(jsDoc.description) ?? ''}\n${ + jsDoc.see?.map((see) => ` * @see ${see}\n`).join('') ?? '' + }${ jsDoc.deprecated ? ` * @deprecated ${ - typeof jsDoc.deprecated === 'string' ? fixLinkTags(jsDoc.deprecated) : jsDoc.deprecated + typeof jsDoc.deprecated === 'string' ? this._fixLinkTags(jsDoc.deprecated) : jsDoc.deprecated }\n` : '' } */`, @@ -718,9 +716,9 @@ export class ApiModelGenerator { const apiItemMetadata: ApiItemMetadata = this._collector.fetchApiItemMetadata(astDeclaration); const docComment: tsdoc.DocComment | undefined = parent?.construct ? this._tsDocParser.parseString( - `/*+\n * ${fixLinkTags(parent.construct.description)}\n${ + `/*+\n * ${this._fixLinkTags(parent.construct.description) ?? ''}\n${ parent.construct.params - ?.map((param) => ` * @param ${param.name} - ${fixLinkTags(param.description)}\n`) + ?.map((param) => ` * @param ${param.name} - ${this._fixLinkTags(param.description) ?? ''}\n`) .join('') ?? '' } */`, ).docComment @@ -849,17 +847,18 @@ export class ApiModelGenerator { const apiItemMetadata: ApiItemMetadata = this._collector.fetchApiItemMetadata(astDeclaration); const docComment: tsdoc.DocComment | undefined = jsDoc ? this._tsDocParser.parseString( - `/**\n * ${fixLinkTags(jsDoc.description)}\n${ - jsDoc.params?.map((param) => ` * @param ${param.name} - ${fixLinkTags(param.description)}\n`).join('') ?? - '' + `/**\n * ${this._fixLinkTags(jsDoc.description) ?? ''}\n${ + jsDoc.params + ?.map((param) => ` * @param ${param.name} - ${this._fixLinkTags(param.description) ?? ''}\n`) + .join('') ?? '' }${ jsDoc.returns?.length && !Array.isArray(jsDoc.returns[0]) - ? ` * @returns ${fixLinkTags(jsDoc.returns[0]!.description ?? '')}` + ? ` * @returns ${this._fixLinkTags(jsDoc.returns[0]!.description) ?? ''}` : '' }${ jsDoc.deprecated ? ` * @deprecated ${ - typeof jsDoc.deprecated === 'string' ? fixLinkTags(jsDoc.deprecated) : jsDoc.deprecated + typeof jsDoc.deprecated === 'string' ? this._fixLinkTags(jsDoc.deprecated) : jsDoc.deprecated }\n` : '' } */`, @@ -976,10 +975,12 @@ export class ApiModelGenerator { const apiItemMetadata: ApiItemMetadata = this._collector.fetchApiItemMetadata(astDeclaration); const docComment: tsdoc.DocComment | undefined = jsDoc ? this._tsDocParser.parseString( - `/**\n * ${fixLinkTags(jsDoc.description)}\n${jsDoc.see?.map((see) => ` * @see ${see}\n`).join('') ?? ''}${ + `/**\n * ${this._fixLinkTags(jsDoc.description) ?? ''}\n${ + jsDoc.see?.map((see) => ` * @see ${see}\n`).join('') ?? '' + }${ jsDoc.deprecated ? ` * @deprecated ${ - typeof jsDoc.deprecated === 'string' ? fixLinkTags(jsDoc.deprecated) : jsDoc.deprecated + typeof jsDoc.deprecated === 'string' ? this._fixLinkTags(jsDoc.deprecated) : jsDoc.deprecated }\n` : '' } */`, @@ -1046,20 +1047,20 @@ export class ApiModelGenerator { const apiItemMetadata: ApiItemMetadata = this._collector.fetchApiItemMetadata(astDeclaration); const docComment: tsdoc.DocComment | undefined = jsDoc ? this._tsDocParser.parseString( - `/**\n * ${fixLinkTags(jsDoc.description)}\n${ + `/**\n * ${this._fixLinkTags(jsDoc.description) ?? ''}\n${ jsDoc.params - ?.map((param) => ` * @param ${param.name} - ${fixLinkTags(param.description)}\n`) + ?.map((param) => ` * @param ${param.name} - ${this._fixLinkTags(param.description) ?? ''}\n`) .join('') ?? '' }${ jsDoc.returns?.length && !Array.isArray(jsDoc.returns[0]) - ? ` * @returns ${fixLinkTags(jsDoc.returns[0]!.description ?? '')}` + ? ` * @returns ${this._fixLinkTags(jsDoc.returns[0]!.description) ?? ''}\n` : '' }${ jsDoc.examples?.map((example) => ` * @example\n * \`\`\`js\n * ${example}\n * \`\`\`\n`).join('') ?? '' }${ jsDoc.deprecated ? ` * @deprecated ${ - typeof jsDoc.deprecated === 'string' ? fixLinkTags(jsDoc.deprecated) : jsDoc.deprecated + typeof jsDoc.deprecated === 'string' ? this._fixLinkTags(jsDoc.deprecated) : jsDoc.deprecated }\n` : '' } */`, @@ -1136,18 +1137,18 @@ export class ApiModelGenerator { const apiItemMetadata: ApiItemMetadata = this._collector.fetchApiItemMetadata(astDeclaration); const docComment: tsdoc.DocComment | undefined = jsDoc ? this._tsDocParser.parseString( - `/**\n * ${fixLinkTags(jsDoc.description)}\n${ + `/**\n * ${this._fixLinkTags(jsDoc.description) ?? ''}\n${ jsDoc.params - ?.map((param) => ` * @param ${param.name} - ${fixLinkTags(param.description)}\n`) + ?.map((param) => ` * @param ${param.name} - ${this._fixLinkTags(param.description) ?? ''}\n`) .join('') ?? '' }${ jsDoc.returns?.length && !Array.isArray(jsDoc.returns[0]) - ? ` * @returns ${fixLinkTags(jsDoc.returns[0]!.description ?? '')}` + ? ` * @returns ${this._fixLinkTags(jsDoc.returns[0]!.description) ?? ''}\n` : '' }${ jsDoc.deprecated ? ` * @deprecated ${ - typeof jsDoc.deprecated === 'string' ? fixLinkTags(jsDoc.deprecated) : jsDoc.deprecated + typeof jsDoc.deprecated === 'string' ? this._fixLinkTags(jsDoc.deprecated) : jsDoc.deprecated }\n` : '' } */`, @@ -1253,12 +1254,12 @@ export class ApiModelGenerator { const apiItemMetadata: ApiItemMetadata = this._collector.fetchApiItemMetadata(astDeclaration); const docComment: tsdoc.DocComment | undefined = jsDoc ? this._tsDocParser.parseString( - `/**\n * ${fixLinkTags(jsDoc.description)}\n${ + `/**\n * ${this._fixLinkTags(jsDoc.description) ?? ''}\n${ 'see' in jsDoc ? jsDoc.see.map((see) => ` * @see ${see}\n`).join('') : '' }${'readonly' in jsDoc && jsDoc.readonly ? ' * @readonly\n' : ''}${ 'deprecated' in jsDoc && jsDoc.deprecated ? ` * @deprecated ${ - typeof jsDoc.deprecated === 'string' ? fixLinkTags(jsDoc.deprecated) : jsDoc.deprecated + typeof jsDoc.deprecated === 'string' ? this._fixLinkTags(jsDoc.deprecated) : jsDoc.deprecated }\n` : '' } */`, @@ -1328,10 +1329,14 @@ export class ApiModelGenerator { const apiItemMetadata: ApiItemMetadata = this._collector.fetchApiItemMetadata(astDeclaration); const docComment: tsdoc.DocComment | undefined = jsDoc ? this._tsDocParser.parseString( - `/**\n * ${fixLinkTags(jsDoc.description)}\n${ + `/**\n * ${this._fixLinkTags(jsDoc.description) ?? ''}\n${ 'see' in jsDoc ? jsDoc.see.map((see) => ` * @see ${see}\n`).join('') : '' }${'readonly' in jsDoc && jsDoc.readonly ? ' * @readonly\n' : ''}${ - 'deprecated' in jsDoc && jsDoc.deprecated ? ` * @deprecated ${jsDoc.deprecated}\n` : '' + 'deprecated' in jsDoc && jsDoc.deprecated + ? ` * @deprecated ${ + typeof jsDoc.deprecated === 'string' ? this._fixLinkTags(jsDoc.deprecated) : jsDoc.deprecated + }\n` + : '' } */`, ).docComment : apiItemMetadata.tsdocComment; @@ -1396,13 +1401,17 @@ export class ApiModelGenerator { const apiItemMetadata: ApiItemMetadata = this._collector.fetchApiItemMetadata(astDeclaration); const docComment: tsdoc.DocComment | undefined = jsDoc ? this._tsDocParser.parseString( - `/**\n * ${fixLinkTags(jsDoc.description) ?? ''}\n${ + `/**\n * ${this._fixLinkTags(jsDoc.description) ?? ''}\n${ 'params' in jsDoc - ? jsDoc.params.map((param) => ` * @param ${param.name} - ${fixLinkTags(param.description)}\n`).join('') + ? jsDoc.params + .map((param) => ` * @param ${param.name} - ${this._fixLinkTags(param.description) ?? ''}\n`) + .join('') : '' }${ 'returns' in jsDoc - ? jsDoc.returns.map((ret) => ` * @returns ${Array.isArray(ret) ? '' : fixLinkTags(ret.description)}\n`) + ? jsDoc.returns + .map((ret) => ` * @returns ${Array.isArray(ret) ? '' : this._fixLinkTags(ret.description) ?? ''}\n`) + .join('') : '' } */`, ).docComment @@ -1531,12 +1540,14 @@ export class ApiModelGenerator { } const docComment: tsdoc.DocComment | undefined = this._tsDocParser.parseString( - `/**\n * ${fixLinkTags(jsDoc.description)}\n${ - jsDoc.params?.map((param) => ` * @param ${param.name} - ${fixLinkTags(param.description)}\n`).join('') ?? '' + `/**\n * ${this._fixLinkTags(jsDoc.description) ?? ''}\n${ + jsDoc.params + ?.map((param) => ` * @param ${param.name} - ${this._fixLinkTags(param.description) ?? ''}\n`) + .join('') ?? '' }${'see' in jsDoc ? jsDoc.see.map((see) => ` * @see ${see}\n`).join('') : ''}${ 'deprecated' in jsDoc && jsDoc.deprecated ? ` * @deprecated ${ - typeof jsDoc.deprecated === 'string' ? fixLinkTags(jsDoc.deprecated) : jsDoc.deprecated + typeof jsDoc.deprecated === 'string' ? this._fixLinkTags(jsDoc.deprecated) : jsDoc.deprecated }\n` : '' } */`, @@ -1672,6 +1683,23 @@ export class ApiModelGenerator { return sourceLocation; } + private _fixLinkTags(input?: string): string | undefined { + return input?.replaceAll(linkRegEx, (_match, _p1, _p2, _p3, _p4, _p5, _offset, _string, groups) => { + let target = groups.class ?? groups.url; + const external = this._jsDocJson?.externals.find((external) => groups.class && external.name === groups.class); + const match = /discord-api-types-(?[^#]*?)(?:#|\/(?[^#/]*)\/)(?[^/}]*)}$/.exec( + external?.see?.[0] ?? '', + ); + if (match) { + target = `discord-api-types#(${match.groups!.name}:${ + /^v\d+$/.test(match.groups!.type!) ? match.groups!.kind : 'type' + })`; + } + + return `{@link ${target}${groups.prop ? `.${groups.prop}` : ''}${groups.name ? ` |${groups.name}` : ''}}`; + }); + } + private _mapVarType(typey: DocgenVarTypeJson): IExcerptToken[] { const mapper = Array.isArray(typey) ? typey : typey.types ?? []; const lookup: { [K in ts.SyntaxKind]?: string } = { @@ -1729,9 +1757,9 @@ export class ApiModelGenerator { isOptional: Boolean(prop.nullable), isReadonly: Boolean(prop.readonly), docComment: this._tsDocParser.parseString( - `/**\n * ${prop.description}\n${prop.see?.map((see) => ` * @see ${see}\n`).join('') ?? ''}${ - prop.readonly ? ' * @readonly\n' : '' - } */`, + `/**\n * ${this._fixLinkTags(prop.description) ?? ''}\n${ + prop.see?.map((see) => ` * @see ${see}\n`).join('') ?? '' + }${prop.readonly ? ' * @readonly\n' : ''} */`, ).docComment, excerptTokens: [ { @@ -1820,11 +1848,13 @@ export class ApiModelGenerator { : { startIndex: 0, endIndex: 0 }, typeParameters: [], docComment: this._tsDocParser.parseString( - `/**\n * ${method.description}\n${ - method.params?.map((param) => ` * @param ${param.name} - ${param.description}\n`).join('') ?? '' + `/**\n * ${this._fixLinkTags(method.description) ?? ''}\n${ + method.params + ?.map((param) => ` * @param ${param.name} - ${this._fixLinkTags(param.description) ?? ''}\n`) + .join('') ?? '' }${ method.returns?.length && !Array.isArray(method.returns[0]) - ? ` * @returns ${method.returns[0]!.description}` + ? ` * @returns ${this._fixLinkTags(method.returns[0]!.description) ?? ''}\n` : '' }${method.examples?.map((example) => ` * @example\n * \`\`\`js\n * ${example}\n * \`\`\`\n`).join('') ?? ''}${ method.deprecated diff --git a/packages/discord.js/src/client/BaseClient.js b/packages/discord.js/src/client/BaseClient.js index 8b1c261fa..8df5e549d 100644 --- a/packages/discord.js/src/client/BaseClient.js +++ b/packages/discord.js/src/client/BaseClient.js @@ -19,11 +19,11 @@ class BaseClient extends EventEmitter { throw new DiscordjsTypeError(ErrorCodes.InvalidType, 'options', 'object', true); } + const defaultOptions = Options.createDefault(); /** * The options the client was instantiated with * @type {ClientOptions} */ - const defaultOptions = Options.createDefault(); this.options = { ...defaultOptions, ...options, diff --git a/packages/discord.js/src/client/voice/ClientVoiceManager.js b/packages/discord.js/src/client/voice/ClientVoiceManager.js index 192e7001a..55b0d830e 100644 --- a/packages/discord.js/src/client/voice/ClientVoiceManager.js +++ b/packages/discord.js/src/client/voice/ClientVoiceManager.js @@ -16,7 +16,7 @@ class ClientVoiceManager { Object.defineProperty(this, 'client', { value: client }); /** - * Maps guild ids to voice adapters created for use with @discordjs/voice. + * Maps guild ids to voice adapters created for use with `@discordjs/voice`. * @type {Map} */ this.adapters = new Map(); diff --git a/packages/discord.js/src/structures/Guild.js b/packages/discord.js/src/structures/Guild.js index da3843855..51a26750a 100644 --- a/packages/discord.js/src/structures/Guild.js +++ b/packages/discord.js/src/structures/Guild.js @@ -1405,7 +1405,7 @@ class Guild extends AnonymousGuild { } /** - * The voice state adapter for this guild that can be used with @discordjs/voice to play audio in voice + * The voice state adapter for this guild that can be used with `@discordjs/voice` to play audio in voice * and stage channels. * @type {Function} * @readonly diff --git a/packages/discord.js/src/util/Sweepers.js b/packages/discord.js/src/util/Sweepers.js index 3cc910838..3402f2fe9 100644 --- a/packages/discord.js/src/util/Sweepers.js +++ b/packages/discord.js/src/util/Sweepers.js @@ -8,7 +8,7 @@ const { DiscordjsTypeError, ErrorCodes } = require('../errors'); /** * @typedef {Function} GlobalSweepFilter * @returns {?Function} Return `null` to skip sweeping, otherwise a function passed to `sweep()`, - * See {@link [Collection#sweep](https://discord.js.org/docs/packages/collection/stable/Collection:Class#sweep)} + * See {@link https://discord.js.org/docs/packages/collection/stable/Collection:Class#sweep Collection#sweep} * for the definition of this function. */