diff --git a/playground/src/pages/components/api-link.astro b/playground/src/pages/components/api-link.astro index bdfa5f3..88c74c3 100644 --- a/playground/src/pages/components/api-link.astro +++ b/playground/src/pages/components/api-link.astro @@ -15,7 +15,6 @@ const headings = [ { depth: 2, slug: 'function', text: 'Function' }, { depth: 2, slug: 'type-alias', text: 'Type alias' }, { depth: 2, slug: 'sub-package', text: 'Sub-package' }, - { depth: 2, slug: 'platform-overrides', text: 'Platform overrides' }, { depth: 2, slug: 'sass-links', text: 'Sass links' }, { depth: 2, slug: 'platform-env', text: 'Platform env var' }, ]; @@ -49,11 +48,6 @@ const headings = [

Sub-package

Use the pkg prop to target a sub-package:

-

Platform overrides

-

exclude renders plain code on matching platforms:

-

excludePrefixFor keeps symbols unprefixed on matching platforms:

-

excludeSuffixFor suppresses framework class suffixes on matching platforms:

-

Sass module page with prose label:

Sass API anchor with code label:

diff --git a/src/components/mdx/ApiLink/ApiLink.astro b/src/components/mdx/ApiLink/ApiLink.astro index c19c6d9..b43b6a6 100644 --- a/src/components/mdx/ApiLink/ApiLink.astro +++ b/src/components/mdx/ApiLink/ApiLink.astro @@ -2,163 +2,124 @@ /** * Inline API documentation link. * - * Renders a link pointing to the TypeDoc API reference for a class, - * interface, enum, type alias, variable, or function — and optionally a - * specific member (property / method) on a class or interface. - * - * Usage: - * - * - * → IgrToast - * - * - * - * → Show - * - * - * - * - * - * - * → configureTheme - * - * - * - * → - * - * - * - * → AbsolutePosition - * - * - * - * → - * - * - * - * → + * TypeDoc links first resolve against the compact api-docs ApiLink index. If + * the index is unavailable, the component falls back to legacy URL generation + * so existing docs can migrate gradually. */ -import type { PlatformContext } from '../../../lib/types.ts'; +import type { ApiPackageConfig, PlatformContext } from '../../../lib/types.ts'; +import { + KIND_SEGMENT, + resolveApiLinkFromIndex, + type TypeDocKind, +} from './api-link-index'; import './ApiLink.scss'; -type ApiKind = 'class' | 'interface' | 'enum' | 'type' | 'variable' | 'function' | 'sass'; - -/** Props for SASS API documentation links (`kind="sass"`). */ +/** Props for Sass API documentation links (`kind="sass"`). */ type SassProps = { + /** Selects Sass API link mode. */ kind: 'sass'; - /** The anchor fragment without "#", e.g. "mixin-slide-in-left". Optional when linking to the module page. */ + /** Anchor fragment without `#`, e.g. `mixin-slide-in-left`. */ type?: string; - /** SASS module name, e.g. "animations", "themes". Required for correct URL generation. */ + /** Sass module path segment, e.g. `animations` or `themes`. */ module?: string; - /** - * When true, wraps the label in . - * @default true — matches TypeDoc ApiLink code-formatting behaviour. - * Set to false for descriptive/prose labels. - */ + /** Wrap the rendered label in ``. Defaults to `true`. */ code?: boolean; - /** Override the display text. Defaults to `type ?? module ?? ''`. */ + /** Override the rendered label. */ label?: string; }; -/** Props for TypeDoc API documentation links (all non-sass kinds). */ +/** Props for TypeDoc API documentation links. */ type TypeDocProps = { /** - * TypeDoc symbol kind. Determines the URL segment used. + * TypeDoc symbol kind. Narrows registry lookup and determines the legacy + * fallback URL segment. * @default "class" */ - kind?: Exclude; + kind?: TypeDocKind; /** * Short type/symbol name without platform prefix, e.g. "Toast". * Required for all TypeDoc kinds. */ type: string; - /** Optional member name (property / method), e.g. "show". Appended as #anchor. */ + /** Optional class/interface member, property, method, or enum value anchor. */ member?: string; - /** - * Package key as defined in platform-context apiPackages. - * Defaults to "core" (the main component package). - * Use "charts", "grids", "gauges", "maps", "inputs", "layouts", - * "excel", "spreadsheet", "datasources" for sub-packages. - */ + /** Package key used only to filter ambiguous registry matches. */ pkg?: string; - /** - * Override the display text. Defaults to the (prefixed) name, - * optionally suffixed with ".member". - */ + /** Override the rendered label. */ label?: string; /** - * When true (default) the platform prefix (Igr/Igx/Igc/Igb) is - * prepended to `type` automatically. Set to false when passing a - * fully-qualified name or a non-prefixed symbol like a function name. + * Whether to prepend the platform prefix before registry lookup or legacy + * fallback URL generation. + * @default true */ prefixed?: boolean; /** - * When true (default) the package classSuffix (e.g. "Component" for Angular) - * is appended to the class name. Set to false for utility/non-component classes - * that do not carry the platform class suffix (e.g. FilteringOperand, SortingStrategy). + * Whether to try the platform class suffix before the unsuffixed symbol. * @default true */ suffix?: boolean; - /** - * Comma-separated list of platforms (e.g. "React" or "React,Blazor") for - * which the API link does NOT exist on the documentation site. When the - * current platform matches any entry the component renders the type name - * as plain inline code (no link), preserving the symbol reference for the - * reader without producing a broken URL. - * - * Use this instead of wrapping a single in a - * just to hide it from a specific platform. - */ - exclude?: string; - /** - * Comma-separated list of platforms for which the package classSuffix - * (e.g. "Component") should NOT be appended, overriding the package default. - * Useful when the same type is a plain class on some platforms but carries - * a framework suffix on others (e.g. "Angular,WebComponents"). - * - * Does not suppress the link — combine with `exclude` to suppress entirely. - */ - excludeSuffixFor?: string; - /** - * Comma-separated list of platforms for which the platform prefix - * (Igr/Igx/Igc/Igb) should NOT be prepended, overriding the package default. - * Useful when a symbol has no prefix on certain platforms. - * - * Does not suppress the link — combine with `exclude` to suppress entirely. - */ - excludePrefixFor?: string; - }; type Props = SassProps | TypeDocProps; -const KIND_SEGMENT: Record, string> = { - class: 'classes', - interface: 'interfaces', - enum: 'enums', - type: 'types', - variable: 'variables', - function: 'functions', -}; - const ctx = Astro.locals.platformContext as PlatformContext; -const { name: platformName, prefix, apiPackages } = ctx; +const { prefix, apiPackages } = ctx; const label = Astro.props.label; -const splitList = (s?: string) => s ? s.split(',').map(p => p.trim()).filter(Boolean) : []; +const upperFirst = (value: string) => value ? value.charAt(0).toUpperCase() + value.slice(1) : value; + +function buildLegacyUrl(options: { + type: string; + kind: TypeDocKind; + member?: string; + prefix: string; + prefixed: boolean; + suffix: boolean; + pkgConfig: ApiPackageConfig; +}) { + const { type, kind, member, prefix, prefixed, suffix, pkgConfig } = options; + const baseType = prefixed ? `${prefix}${type}` : type; + const segment = KIND_SEGMENT[kind]; + + if (kind === 'class') { + const fullType = (suffix && pkgConfig.classSuffix) ? `${baseType}${pkgConfig.classSuffix}` : baseType; + const cased = pkgConfig.preserveCase ? fullType : fullType.toLowerCase(); + const classSlug = pkgConfig.noPackagePrefix + ? cased + : `${pkgConfig.packageId}.${cased}`; + const memberAnchor = member + ? `#${pkgConfig.pascalCaseMembers ? upperFirst(member) : member}` + : ''; + return `${pkgConfig.docRoot}/classes/${classSlug}${memberAnchor}`; + } + + const slug = pkgConfig.noPackagePrefix + ? baseType + : `${pkgConfig.packageId}.${baseType}`; + const memberAnchor = member + ? `#${kind === 'enum' ? member : pkgConfig.pascalCaseMembers ? upperFirst(member) : member.toLowerCase()}` + : ''; + + return `${pkgConfig.docRoot}/${segment}/${slug}${memberAnchor}`; +} + +function getIndexedDisplayName(resolvedName: string, fallbackName: string, type: string, classSuffix?: string) { + if (resolvedName === type) return type; + if (classSuffix && resolvedName === `${type}${classSuffix}`) return type; + return fallbackName; +} let url: string; let displayLabel: string; let renderCode: boolean; -let isExcluded = false; if (Astro.props.kind === 'sass') { const { type, module, code = true } = Astro.props; - if (!module) console.warn('[ApiLink] kind="sass" requires a `module` prop — link may be malformed.'); + if (!module) console.warn('[ApiLink] kind="sass" requires a `module` prop - link may be malformed.'); const base = ctx.sassApiUrl?.trim().replace(/\/+$/, ''); const anchor = type ? `#${type}` : ''; if (!base) { - console.warn('[ApiLink] kind="sass" requires `platformContext.sassApiUrl` to be configured — falling back to "#".'); + console.warn('[ApiLink] kind="sass" requires `platformContext.sassApiUrl` to be configured - falling back to "#".'); url = '#'; } else { url = `${base}/${module ?? ''}${anchor}`; @@ -167,48 +128,55 @@ if (Astro.props.kind === 'sass') { renderCode = code; } else { const { - type, kind = 'class', member, pkg = 'core', - prefixed = true, suffix = true, - exclude, excludeSuffixFor, excludePrefixFor, + type, + member, + pkg = 'core', + prefixed = true, + suffix = true, } = Astro.props; - isExcluded = splitList(exclude).includes(platformName); - const isSuffixExcluded = splitList(excludeSuffixFor).includes(platformName); - const isPrefixExcluded = splitList(excludePrefixFor).includes(platformName); - const effectivePrefixed = prefixed && !isPrefixExcluded; - const effectiveSuffix = suffix && !isSuffixExcluded; - - const pkgConfig = apiPackages[pkg] ?? apiPackages['core']; - const baseType = effectivePrefixed ? `${prefix}${type}` : type; - const segment = KIND_SEGMENT[kind]; - if (kind === 'class') { - const fullType = (effectiveSuffix && pkgConfig.classSuffix) ? `${baseType}${pkgConfig.classSuffix}` : baseType; - const cased = pkgConfig.preserveCase ? fullType : fullType.toLowerCase(); - const classSlug = pkgConfig.noPackagePrefix - ? cased - : `${pkgConfig.packageId}.${cased}`; - const memberAnchor = member - ? `#${pkgConfig.pascalCaseMembers ? member.charAt(0).toUpperCase() + member.slice(1) : member}` - : ''; - url = `${pkgConfig.docRoot}/classes/${classSlug}${memberAnchor}`; - } else { - const slug = pkgConfig.noPackagePrefix - ? baseType - : `${pkgConfig.packageId}.${baseType}`; - const memberAnchorNonClass = member - ? `#${ - kind === 'enum' - ? member - : pkgConfig.pascalCaseMembers - ? member.charAt(0).toUpperCase() + member.slice(1) - : member.toLowerCase() - }` - : ''; - url = `${pkgConfig.docRoot}/${segment}/${slug}${memberAnchorNonClass}`; - } + const explicitKind = 'kind' in Astro.props ? Astro.props.kind : undefined; + const kind: TypeDocKind = explicitKind ?? 'class'; + + const effectivePrefixed = prefixed; + const effectiveSuffix = suffix; + + // pkg is an ambiguity override. Without an explicit pkg prop, search the + // combined api-docs index so symbols can resolve from any package. + const explicitPkg = 'pkg' in Astro.props && typeof pkg === 'string' && pkg.length > 0; + const pkgConfig = apiPackages[explicitPkg ? pkg : 'core'] ?? apiPackages.core; + const baseType = effectivePrefixed ? `${prefix}${type}` : type; + + url = buildLegacyUrl({ + type, + kind, + member, + prefix, + prefixed: effectivePrefixed, + suffix: effectiveSuffix, + pkgConfig, + }); displayLabel = label ?? (member ? `${baseType}.${member}` : baseType); renderCode = true; + + const indexed = await resolveApiLinkFromIndex({ + ctx, + pkgConfig, + explicitPkg, + type, + member, + explicitKind, + prefix, + prefixed: effectivePrefixed, + suffix: effectiveSuffix, + }); + + if (indexed.status === 'resolved') { + url = indexed.url; + const indexedDisplay = getIndexedDisplayName(indexed.symbolName, baseType, type, pkgConfig.classSuffix); + displayLabel = label ?? (indexed.memberName ? `${indexedDisplay}.${indexed.memberName}` : indexedDisplay); + } else if (indexed.status === 'missing' && import.meta.env.DEV) { + console.warn(`[ApiLink] Registry miss for ${type}${member ? `.${member}` : ''}; using legacy URL fallback.`); + } } --- -{isExcluded - ? {displayLabel} - : {renderCode ? {displayLabel} : displayLabel}} +{renderCode ? {displayLabel} : displayLabel} diff --git a/src/components/mdx/ApiLink/README.md b/src/components/mdx/ApiLink/README.md index 5b45efc..309f5ae 100644 --- a/src/components/mdx/ApiLink/README.md +++ b/src/components/mdx/ApiLink/README.md @@ -1,10 +1,13 @@ # ApiLink -Renders an inline API link with platform-aware URL generation. +Renders an inline API documentation link with platform-aware resolution. -For TypeDoc symbols, it renders an inline `` link to a class, -interface, enum, type alias, variable, or function. It can also render Sass API -links when `kind="sass"`. +For TypeDoc symbols, `ApiLink` first queries the generated api-docs link index. +That index contains the real symbol names, packages, URL segments, and member +anchors produced by api-docs. If the index is unavailable, `ApiLink` falls back +to legacy URL generation so migrated MDX continues to build. + +Sass links are separate and still use `kind="sass"`. ## Import @@ -12,101 +15,95 @@ links when `kind="sass"`. import ApiLink from 'igniteui-astro-components/components/mdx/ApiLink.astro'; ``` -## TypeDoc props +## Preferred Usage -| Prop | Type | Default | Description | -|------|------|---------|-------------| -| `type` | `string` | *(required)* | Short symbol name without platform prefix, e.g. `"Toast"`. | -| `kind` | `'class' \| 'interface' \| 'enum' \| 'type' \| 'variable' \| 'function'` | `'class'` | TypeDoc symbol kind — determines the URL path segment. | -| `member` | `string` | — | Optional member name (property/method). Appended as a `#anchor`. | -| `pkg` | `string` | `'core'` | Package key: `'core'` \| `'charts'` \| `'grids'` \| `'gauges'` \| `'maps'` \| `'inputs'` \| `'layouts'` \| `'excel'` \| `'spreadsheet'` \| `'datasources'`. | -| `label` | `string` | auto | Override the display text. Defaults to the prefixed name (+ `.member` when provided). | -| `prefixed` | `boolean` | `true` | When `true`, prepends the platform prefix (`Igr`/`Igx`/`Igc`/`Igb`) to `type`. Set `false` for already-qualified names or non-prefixed symbols like function names. | -| `suffix` | `boolean` | `true` | When `true`, appends the platform class suffix (e.g. `Component` for Angular). Set `false` for utility classes that do not carry a suffix. | -| `exclude` | `string` | — | Comma-separated platform names where the link should not render. Matching platforms render the label as plain inline ``. | -| `excludeSuffixFor` | `string` | — | Comma-separated platform names where `classSuffix` should not be appended, even when `suffix` is `true`. | -| `excludePrefixFor` | `string` | — | Comma-separated platform names where the platform prefix should not be prepended, even when `prefixed` is `true`. | +Author MDX with the unprefixed API name: + +```mdx + + + +``` -Platform names use the display form from `PlatformContext.name`: `Angular`, -`React`, `WebComponents`, or `Blazor`. +The resolver handles platform prefix/suffix candidates, package lookup, kind +lookup, URL segment lookup, and member anchor lookup. -## Sass props +Omitting `pkg` relies on `platformContext.apiLinkIndex` being available. When +the index is unavailable, `ApiLink` falls back to legacy URL generation and +cannot reliably infer the correct package for non-core symbols. -Use `kind="sass"` for Sass API reference links. Sass links read their base URL -from `platformContext.sassApiUrl`. +## TypeDoc Props | Prop | Type | Default | Description | |------|------|---------|-------------| -| `kind` | `'sass'` | *(required)* | Enables Sass API link mode. | -| `module` | `string` | — | Sass module path segment, e.g. `"animations"` or `"themes"`. | -| `type` | `string` | — | Anchor fragment without `#`, e.g. `"mixin-slide-in-left"`. Omit it to link to the module page. | -| `label` | `string` | auto | Override the display text. Defaults to `type`, then `module`, then an empty string. | -| `code` | `boolean` | `true` | Wrap the label in ``. Set `false` for prose labels. | +| `type` | `string` | *(required)* | Short symbol name without platform prefix, e.g. `"Grid"` instead of `"IgrGrid"`. | +| `member` | `string` | — | Optional member name. Resolved through the generated member map when available. | +| `label` | `string` | auto | Override the display text. | +| `pkg` | `string` | — | Ambiguity override only. Use when the same symbol exists in multiple packages and the combined index cannot choose safely. | +| `kind` | `'class' \| 'interface' \| 'enum' \| 'type' \| 'variable' \| 'function'` | auto / legacy `'class'` | Optional narrowing. Usually unnecessary when the index is available. | +| `prefixed` | `boolean` | `true` | Legacy override for symbols that are never platform-prefixed. Avoid for new docs when the index can resolve the symbol. | +| `suffix` | `boolean` | `true` | Legacy override for symbols that never use the platform class suffix. Avoid for new docs when the index can resolve the symbol. | + +Platform names use `PlatformContext.name`: `Angular`, `React`, +`WebComponents`, or `Blazor`. ## Examples ```mdx -{/* Core class — auto-prefix applied */} - -{/* → IgrToast */} +{/* Let the generated index find package, kind, and exact symbol name. */} + -{/* Class member */} - +{/* Member anchors are resolved from the generated member map. */} + -{/* Sub-package */} - +{/* Use pkg only when the symbol name is ambiguous across packages. */} + +``` -{/* Function — no prefix, no suffix */} - +## ApiLink Index -{/* Type alias */} - +`ApiLink` resolves TypeDoc symbols from `platformContext.apiLinkIndex`, a compact +registry generated from api-docs TypeDoc data and stored by the docs host. The +component does not fetch api-docs during the documentation build. -{/* Interface */} - +The docs host loads one latest registry per platform, for example: -{/* Enum */} - +```txt +src/data/api-link-index/angular/staging-latest.json +src/data/api-link-index/webcomponents/staging-latest.json +``` -{/* Utility class without platform suffix */} - +Registry symbol fields are intentionally short because the files are large: -{/* Hide a broken API link on selected platforms */} - +| Field | Meaning | +|-------|---------| +| `p` | Package id that owns the symbol. | +| `u` | Root-relative API URL for the symbol. | +| `k` | Symbol kind, e.g. `class`, `interface`, `enum`, `type`. | +| `s` | API docs URL segment, e.g. `classes` or `interfaces`. | +| `m` | Member name to anchor map. | -{/* Keep the symbol unprefixed only for React */} - +When `pkg` is present, `ApiLink` uses it only as a package filter for ambiguous +symbol names; package URLs and kinds still come from the registry. -{/* Suppress the Angular class suffix for this symbol */} - +## Sass Props +Use `kind="sass"` for Sass API reference links. Sass links do not use the +api-docs link index; they read their base URL from `platformContext.sassApiUrl`. -{/* Sass module page */} - +| Prop | Type | Default | Description | +|------|------|---------|-------------| +| `kind` | `'sass'` | *(required)* | Enables Sass API link mode. | +| `module` | `string` | — | Sass module path segment, e.g. `"animations"` or `"themes"`. | +| `type` | `string` | — | Anchor fragment without `#`, e.g. `"mixin-slide-in-left"`. Omit it to link to the module page. | +| `label` | `string` | auto | Override the display text. Defaults to `type`, then `module`, then an empty string. | +| `code` | `boolean` | `true` | Wrap the label in ``. Set `false` for prose labels. | -{/* Sass symbol anchor */} +```mdx + ``` -## Platform context - -The current platform is read from the `PLATFORM` environment variable (set at build time). Use `siteMetaIntegration({ platform })` or `createDocsSite({ platform })` in `astro.config.ts`: - -```ts -createDocsSite({ platform: 'react' }); -// prefix → 'Igr', docRoot → 'https://www.infragistics.com/products/ignite-ui-react/docs/typescript/latest' -``` - -Supported platforms and their prefixes: - -| Platform | Prefix | Class suffix | -|----------|--------|--------------| -| `Angular` | `Igx` | `Component` (DV pkgs only) | -| `React` | `Igr` | — | -| `WebComponents` | `Igc` | — | -| `Blazor` | `Igb` | — | - -For Sass links, make sure `platformContext.sassApiUrl` is configured. The URL is -assembled as: +Sass URL shape: ```txt {sassApiUrl}/{module}#{type} diff --git a/src/components/mdx/ApiLink/api-link-index.ts b/src/components/mdx/ApiLink/api-link-index.ts new file mode 100644 index 0000000..86033a2 --- /dev/null +++ b/src/components/mdx/ApiLink/api-link-index.ts @@ -0,0 +1,186 @@ +import type { ApiPackageConfig, PlatformContext } from '../../../lib/types'; + +export type ApiKind = 'class' | 'interface' | 'enum' | 'type' | 'variable' | 'function' | 'sass'; +export type TypeDocKind = Exclude; + +export const KIND_SEGMENT: Record = { + class: 'classes', + interface: 'interfaces', + enum: 'enums', + type: 'types', + variable: 'variables', + function: 'functions', +}; + +type ApiLinkIndexSymbol = { + /** Package id. */ + p?: string; + /** URL. */ + u: string; + /** Kind. */ + k?: TypeDocKind; + /** URL segment. */ + s?: string; + /** Member name to anchor map. */ + m?: Record; +}; + +type ApiLinkIndexFile = { + symbols?: Record; +}; + +export type ApiLinkIndexResolution = + | { status: 'resolved'; url: string; symbolName: string; memberName: string; memberAnchor: string } + | { status: 'missing' } + | { status: 'unavailable' }; + +const upperFirst = (value: string) => value ? value.charAt(0).toUpperCase() + value.slice(1) : value; + +function buildCandidateNames(options: { + type: string; + explicitKind?: TypeDocKind; + prefix: string; + prefixed: boolean; + suffix: boolean; + classSuffix?: string; +}): string[] { + const candidates = new Set(); + const baseNames = new Set(); + + if (options.prefixed) baseNames.add(`${options.prefix}${options.type}`); + baseNames.add(options.type); + + for (const baseName of baseNames) { + if ((!options.explicitKind || options.explicitKind === 'class') && options.suffix && options.classSuffix) { + candidates.add(`${baseName}${options.classSuffix}`); + } + candidates.add(baseName); + } + + return [...candidates]; +} + +function resolveIndexedMember(symbol: ApiLinkIndexSymbol, member: string | undefined): { memberName: string; memberAnchor: string } | null { + if (!member) return { memberName: '', memberAnchor: '' }; + + const members = symbol.m ?? {}; + const candidates = new Set([member, upperFirst(member), member.toLowerCase()]); + + for (const candidate of candidates) { + if (Object.hasOwn(members, candidate)) { + return { memberName: candidate, memberAnchor: members[candidate] }; + } + } + + const normalized = member.toLowerCase(); + for (const [registryMember, memberAnchor] of Object.entries(members)) { + if (registryMember.toLowerCase() === normalized) { + return { memberName: registryMember, memberAnchor }; + } + } + + return null; +} + +function findIndexedSymbol(options: { + index: ApiLinkIndexFile; + candidates: string[]; + packageId?: string; + explicitKind?: TypeDocKind; + member?: string; +}) { + const symbols = options.index.symbols ?? {}; + + for (const name of options.candidates) { + const value = symbols[name]; + if (!value) continue; + + const symbolList = Array.isArray(value) ? value : [value]; + for (const symbol of symbolList) { + if (options.packageId && symbol.p && symbol.p !== options.packageId) continue; + if (options.explicitKind && symbol.k && symbol.k !== options.explicitKind) continue; + const memberMatch = resolveIndexedMember(symbol, options.member); + if (memberMatch === null) continue; + return { name, symbol, memberName: memberMatch.memberName, memberAnchor: memberMatch.memberAnchor }; + } + } + + return null; +} + +function absolutizeIndexUrl(indexedPath: string, docRoot: string): string { + if (!indexedPath.startsWith('/')) return indexedPath; + + try { + return `${new URL(docRoot).origin}${indexedPath}`; + } catch { + return indexedPath; + } +} + +function getCandidateClassSuffixes(options: { + ctx: PlatformContext; + explicitPkg: boolean; + pkgConfig: ApiPackageConfig; +}): Array { + if (options.explicitPkg) { + return [options.pkgConfig.classSuffix]; + } + + const suffixes = new Set(); + for (const pkg of Object.values(options.ctx.apiPackages)) { + suffixes.add(pkg.classSuffix); + } + + return [...suffixes]; +} + +export async function resolveApiLinkFromIndex(options: { + ctx: PlatformContext; + pkgConfig: ApiPackageConfig; + explicitPkg: boolean; + type: string; + member?: string; + explicitKind?: TypeDocKind; + prefix: string; + prefixed: boolean; + suffix: boolean; +}): Promise { + const index = options.ctx.apiLinkIndex as ApiLinkIndexFile | undefined; + if (!index?.symbols) { + return { status: 'unavailable' }; + } + + const candidates = new Set(); + for (const classSuffix of getCandidateClassSuffixes(options)) { + for (const candidate of buildCandidateNames({ + type: options.type, + explicitKind: options.explicitKind, + prefix: options.prefix, + prefixed: options.prefixed, + suffix: options.suffix, + classSuffix, + })) { + candidates.add(candidate); + } + } + + const indexed = findIndexedSymbol({ + index, + candidates: [...candidates], + packageId: options.explicitPkg ? options.pkgConfig.packageId : undefined, + explicitKind: options.explicitKind, + member: options.member, + }); + + if (!indexed) return { status: 'missing' }; + + const path = `${indexed.symbol.u}${indexed.memberAnchor ? `#${indexed.memberAnchor}` : ''}`; + return { + status: 'resolved', + url: absolutizeIndexUrl(path, options.pkgConfig.docRoot), + symbolName: indexed.name, + memberName: indexed.memberName, + memberAnchor: indexed.memberAnchor, + }; +} diff --git a/src/lib/types.ts b/src/lib/types.ts index 3bf22a5..1838a74 100644 --- a/src/lib/types.ts +++ b/src/lib/types.ts @@ -20,10 +20,9 @@ export interface ApiPackageConfig { */ preserveCase?: boolean; /** - * Optional suffix appended to the class name before lowercasing, e.g. - * Angular DV packages append "Component" so `CategoryChart` resolves to - * `igniteui_angular_charts.igxcategorychartcomponent.html`. - * Only applied when `prefixed={true}`. + * Preferred class-name suffix used by ApiLink. The generated registry tries + * both the suffixed and unsuffixed names, so this does not mean every API + * symbol is expected to have the suffix. */ classSuffix?: string; /** @@ -35,6 +34,10 @@ export interface ApiPackageConfig { export interface PlatformContext { name: PlatformName; + /** Optional compact ApiLink symbol index loaded by the docs host at build time. */ + apiLinkIndex?: { + symbols?: Record; + }; /** Lower-case slug used in URLs, e.g. "angular" */ lower: string; /** Component class prefix, e.g. "Igx" / "Igr" / "Igc" / "Igb" */