Files
angular__angular/adev/shared-docs/pipeline/shared/marked/renderer.mts
T
Kam b0cda277da refactor(docs-infra): validate api/cdk and api/aria links
`isKnownRoute` exempted both families behind TODOs waiting on route extraction
for those packages. That extraction had already landed when the TODOs were
written in #66254: cdk pages since #60853 and aria pages since the cross-repo
workflow. `defined-routes.json` carries 70 `api/cdk` and 39 `api/aria` routes
today, and all 45 such link targets in the guides resolve, so the build stays
green without the exemptions.

The gap was not theoretical. `guide/aria/select.md` and
`guide/aria/multiselect.md` linked `api/cdk/overlay/CdkConnectedOverlay`, which
has never been a route, and it shipped as a 404 for six months. Link validation
landed four months into that and said nothing, because of this exemption. It
took a user filing #68914 and an outside contributor fixing it in #68915.

Pointing an existing `api/cdk` link at a symbol that does not exist passes the
build today and fails it with this change.
2026-09-09 16:15:14 +02:00

95 lines
3.0 KiB
TypeScript

/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.dev/license
*/
import {Renderer} from 'marked';
import {HighlighterGeneric} from 'shiki';
import {codespanRender} from './transformations/code.mjs';
import {headingRender} from './transformations/heading.mjs';
import {imageRender} from './transformations/image.mjs';
import {linkRender} from './transformations/link.mjs';
import {listRender} from './transformations/list.mjs';
import {tableRender} from './transformations/table.mjs';
import {textRender} from './transformations/text.mjs';
export interface RendererContext {
/**
* Path of the markdown file being rendered.
*/
markdownFilePath?: string;
/**
* List of guide routes defined in the application.
*/
definedRoutes?: string[];
/**
* Map of API entries available for linking within the rendered markdown file.
*/
apiEntries?: Record<string, {moduleName: string; aliases?: string[]}>;
highlighter: HighlighterGeneric<any, any>;
headerIds: Map<string, number>;
/** In the case we want to disable auto-linking because anchor blocks might be incompatible where some code blocks are being rendered */
disableAutoLinking: boolean;
}
/**
* Custom renderer for marked that will be used to transform markdown files to HTML
* files that can be used in the Angular docs.
*/
export class AdevDocsRenderer extends Renderer {
public context: RendererContext;
constructor(context: Partial<RendererContext>) {
super();
if (context.highlighter === undefined) {
throw Error(
'An instance of HighlighterGeneric must be provided to the AdevDocsRenderer at construction',
);
}
context.headerIds = context.headerIds || new Map<string, number>();
this.context = context as RendererContext;
}
defaultRenderer = new Renderer();
isGuideFile(): boolean {
return this.context.markdownFilePath?.includes('/content/guide') ?? false;
}
isKnownRoute(route: string): boolean {
route = route.startsWith('/') ? route.slice(1) : route;
route = route.split('?')[0]; // Remove query params
if (route.startsWith('api/') || route.startsWith('api#')) {
// Remove fragment for API routes, as we don't have enough info to check them.
route = route.split('#')[0];
}
if (!this.context.definedRoutes?.length) {
return true;
}
if (
route.startsWith('http') ||
route.startsWith('#') || // Anchor link within the same page
route.startsWith('mailto:') || // Should we have a regex to exclude any protocol?
route.startsWith('playground') ||
route.startsWith('tutorials') ||
route.startsWith('extended-diagnostics')
) {
return true;
}
return this.context.definedRoutes.includes(route);
}
override link = linkRender;
override table = tableRender;
override list = listRender;
override image = imageRender;
override text = textRender;
override heading = headingRender;
override codespan = codespanRender;
}