mirror of
https://github.com/angular/angular.git
synced 2026-09-14 13:54:52 +08:00
docs(docs-infra): highlight & parse api descriptions. (#63536)
The prevent future regression of this, this commit introduces additional tests. PR Close #63536
This commit is contained in:
committed by
Miles Malerba
parent
e63608d4d2
commit
31c689a5f4
@@ -5,6 +5,10 @@ package(default_visibility = ["//adev/shared-docs/pipeline/api-gen:__subpackages
|
||||
esbuild(
|
||||
name = "bin",
|
||||
entry_point = ":index.mts",
|
||||
external = [
|
||||
"jsdom",
|
||||
"playwright-core",
|
||||
],
|
||||
format = "esm",
|
||||
output = "bin.mjs",
|
||||
platform = "node",
|
||||
|
||||
@@ -21,6 +21,10 @@ export function setCurrentSymbol(symbol: string): void {
|
||||
currentSymbol = symbol;
|
||||
}
|
||||
|
||||
export function getSymbols() {
|
||||
return symbols;
|
||||
}
|
||||
|
||||
export function getCurrentSymbol(): string | undefined {
|
||||
return currentSymbol;
|
||||
}
|
||||
|
||||
@@ -79,12 +79,14 @@ describe('markdown to html', () => {
|
||||
|
||||
// In the description
|
||||
const descriptionItem = entry.querySelector('.docs-reference-description')!;
|
||||
expect(descriptionItem.innerHTML).toContain('<a href="/api/core/afterRender">afterRender</a>');
|
||||
expect(descriptionItem.innerHTML).toContain(
|
||||
'<a href="/api/core/afterRender"><code>afterRender</code></a>',
|
||||
);
|
||||
|
||||
// In the card
|
||||
const cardItem = entry.querySelectorAll('.docs-reference-card-item')[1];
|
||||
expect(cardItem.innerHTML).toContain(
|
||||
'<a href="/api/core/AfterRenderPhase#MixedReadWrite">AfterRenderPhase.MixedReadWrite</a>',
|
||||
'<a href="/api/core/AfterRenderPhase#MixedReadWrite"><code>AfterRenderPhase.MixedReadWrite</code></a>',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
+39
-1
@@ -6,8 +6,10 @@
|
||||
* found in the LICENSE file at https://angular.dev/license
|
||||
*/
|
||||
|
||||
import {initHighlighter} from '../../../../shared/shiki.mjs';
|
||||
import {setHighlighterInstance} from '../../shiki/shiki.mjs';
|
||||
import {setCurrentSymbol, setSymbols} from '../../symbol-context.mjs';
|
||||
import {addHtmlAdditionalLinks} from '../../transforms/jsdoc-transforms.mjs';
|
||||
import {addHtmlAdditionalLinks, addHtmlDescription} from '../../transforms/jsdoc-transforms.mjs';
|
||||
|
||||
// @ts-ignore This compiles fine, but Webstorm doesn't like the ESM import in a CJS context.
|
||||
describe('jsdoc transforms', () => {
|
||||
@@ -151,4 +153,40 @@ describe('jsdoc transforms', () => {
|
||||
|
||||
expect(entryFn).toThrowError(/Forbidden relative link: cli\/build ng build/);
|
||||
});
|
||||
|
||||
it('should parse markdown in descriptions', async () => {
|
||||
setHighlighterInstance(await initHighlighter());
|
||||
|
||||
setSymbols(
|
||||
Object.fromEntries([
|
||||
['Route', 'test'],
|
||||
['Router', 'angular/router'],
|
||||
['Router.someMethod', 'test'],
|
||||
['Router.someMethodWithParenthesis', 'test'],
|
||||
['FormGroup', 'test'],
|
||||
['FormGroup.someMethod', 'test'],
|
||||
]),
|
||||
);
|
||||
|
||||
const entry = addHtmlDescription({
|
||||
description: `
|
||||
\`\`\`angular-ts
|
||||
import { Router } from '@angular/router';
|
||||
|
||||
function setupRouter() {
|
||||
const router = inject(Router);
|
||||
}
|
||||
\`\`\`
|
||||
`,
|
||||
moduleName: 'test',
|
||||
});
|
||||
|
||||
// Should have some shiki variables (meaning the description was highlighted).
|
||||
expect(entry.htmlDescription).toContain('--shiki');
|
||||
|
||||
// Having docs-code means that the description was parsed and formatted correctly (by the shared marked renderer)
|
||||
expect(entry.htmlDescription).toContain('class="docs-code"');
|
||||
|
||||
expect(entry.htmlDescription).toContain('/api/angular/router/Router');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -14,13 +14,17 @@ import {
|
||||
CliCommandRenderable,
|
||||
CliOptionRenderable,
|
||||
} from '../entities/renderables.mjs';
|
||||
import {parseMarkdown} from '../../../shared/marked/parse.mjs';
|
||||
import {getHighlighterInstance} from '../shiki/shiki.mjs';
|
||||
|
||||
/** Given an unprocessed CLI entry, get the fully renderable CLI entry. */
|
||||
export function getCliRenderable(command: CliCommand): CliCommandRenderable {
|
||||
return {
|
||||
...command,
|
||||
subcommands: command.subcommands?.map((sub) => getCliRenderable(sub)),
|
||||
htmlDescription: marked.parse(command.longDescription ?? command.shortDescription) as string,
|
||||
htmlDescription: parseMarkdown(command.longDescription ?? command.shortDescription, {
|
||||
highlighter: getHighlighterInstance(),
|
||||
}),
|
||||
cards: getCliCardsRenderable(command),
|
||||
argumentsLabel: getArgumentsLabel(command),
|
||||
hasOptions: getOptions(command).length > 0,
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
* found in the LICENSE file at https://angular.dev/license
|
||||
*/
|
||||
|
||||
import {marked} from 'marked';
|
||||
import {JsDocTagEntry} from '../entities.mjs';
|
||||
|
||||
import {getDeprecatedEntry, getTagSinceVersion} from '../entities/categorization.mjs';
|
||||
@@ -26,7 +25,14 @@ import {
|
||||
} from '../entities/traits.mjs';
|
||||
|
||||
import {addApiLinksToHtml} from './code-transforms.mjs';
|
||||
import {getCurrentSymbol, getSymbolUrl, unknownSymbolMessage} from '../symbol-context.mjs';
|
||||
import {
|
||||
getCurrentSymbol,
|
||||
getSymbols,
|
||||
getSymbolUrl,
|
||||
unknownSymbolMessage,
|
||||
} from '../symbol-context.mjs';
|
||||
import {parseMarkdown} from '../../../shared/marked/parse.mjs';
|
||||
import {getHighlighterInstance} from '../shiki/shiki.mjs';
|
||||
|
||||
const JS_DOC_USAGE_NOTE_TAGS: Set<string> = new Set(['remarks', 'usageNotes', 'example']);
|
||||
export const JS_DOC_SEE_TAG = 'see';
|
||||
@@ -99,7 +105,11 @@ export function addHtmlUsageNotes<T extends HasJsDocTags>(entry: T): T & HasHtml
|
||||
|
||||
/** Given a markdown JsDoc text, gets the rendered HTML. */
|
||||
function getHtmlForJsDocText(text: string): string {
|
||||
const parsed = marked.parse(convertLinks(wrapExampleHtmlElementsWithCode(text))) as string;
|
||||
const mdToParse = convertLinks(wrapExampleHtmlElementsWithCode(text));
|
||||
const parsed = parseMarkdown(mdToParse, {
|
||||
apiEntries: getSymbols(),
|
||||
highlighter: getHighlighterInstance(),
|
||||
});
|
||||
return addApiLinksToHtml(parsed);
|
||||
}
|
||||
|
||||
|
||||
@@ -63,7 +63,7 @@ export function formatCode(token: CodeToken, context: RendererContext): string {
|
||||
`).firstElementChild!;
|
||||
|
||||
applyContainerAttributesAndClasses(containerEl, token);
|
||||
processForApiLinks(containerEl, context.apiEntries);
|
||||
processForApiLinks(containerEl, context.apiEntries ?? {});
|
||||
|
||||
return containerEl.outerHTML;
|
||||
}
|
||||
|
||||
@@ -13,12 +13,12 @@ import {listRender} from './transformations/list.mjs';
|
||||
import {imageRender} from './transformations/image.mjs';
|
||||
import {textRender} from './transformations/text.mjs';
|
||||
import {headingRender} from './transformations/heading.mjs';
|
||||
import {codespanRender} from './transformations/code.mjs';
|
||||
import {codeRender, codespanRender} from './transformations/code.mjs';
|
||||
import {HighlighterGeneric} from 'shiki';
|
||||
|
||||
export interface RendererContext {
|
||||
markdownFilePath: string;
|
||||
apiEntries: Record<string, string>;
|
||||
markdownFilePath?: string;
|
||||
apiEntries?: Record<string, string>;
|
||||
highlighter: HighlighterGeneric<any, any>;
|
||||
}
|
||||
|
||||
@@ -40,4 +40,5 @@ export class AdevDocsRenderer extends Renderer {
|
||||
override text = textRender;
|
||||
override heading = headingRender;
|
||||
override codespan = codespanRender;
|
||||
override code = codeRender;
|
||||
}
|
||||
|
||||
@@ -9,9 +9,10 @@
|
||||
import {Tokens} from 'marked';
|
||||
import {AdevDocsRenderer} from '../renderer.mjs';
|
||||
import {getSymbolUrl} from '../../linking.mjs';
|
||||
import {codeToHtml} from '../../shiki.mjs';
|
||||
|
||||
export function codespanRender(this: AdevDocsRenderer, token: Tokens.Codespan) {
|
||||
const apiLink = getSymbolUrl(token.text, this.context.apiEntries);
|
||||
const apiLink = getSymbolUrl(token.text, this.context.apiEntries ?? {});
|
||||
if (apiLink) {
|
||||
const htmlToken: Tokens.HTML = {
|
||||
type: 'html',
|
||||
@@ -31,3 +32,15 @@ export function codespanRender(this: AdevDocsRenderer, token: Tokens.Codespan) {
|
||||
}
|
||||
return this.defaultRenderer.codespan(token);
|
||||
}
|
||||
|
||||
export function codeRender(this: AdevDocsRenderer, {text, lang}: Tokens.Code): string {
|
||||
const highlightResult = codeToHtml(this.context.highlighter, text, lang)
|
||||
// remove spaces/line-breaks between elements to not mess-up `pre` style
|
||||
.replace(/>\s+</g, '><');
|
||||
|
||||
return `
|
||||
<div class="docs-code" role="group">
|
||||
${highlightResult}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ export function headingRender(this: AdevDocsRenderer, {depth, tokens}: Tokens.He
|
||||
|
||||
export function formatHeading(
|
||||
{text, depth}: {text: string; depth: number},
|
||||
markdownFilePath: string,
|
||||
markdownFilePath?: string,
|
||||
): string {
|
||||
if (depth === 1) {
|
||||
return `
|
||||
@@ -55,14 +55,18 @@ export function formatHeading(
|
||||
const GITHUB_EDIT_CONTENT_URL = 'https://github.com/angular/angular/edit/main';
|
||||
|
||||
/** Get the page title with edit button to modify the page source. */
|
||||
export function getPageTitle(text: string, filePath: string): string {
|
||||
export function getPageTitle(text: string, filePath?: string): string {
|
||||
return `
|
||||
<!-- Page title -->
|
||||
<div class="docs-page-title">
|
||||
<h1 tabindex="-1">${text}</h1>
|
||||
<a class="docs-github-links" target="_blank" href="${GITHUB_EDIT_CONTENT_URL}/${filePath}" title="Edit this page" aria-label="Edit this page">
|
||||
${
|
||||
filePath
|
||||
? `<a class="docs-github-links" target="_blank" href="${GITHUB_EDIT_CONTENT_URL}/${filePath}" title="Edit this page" aria-label="Edit this page">
|
||||
<!-- Pencil -->
|
||||
<docs-icon role="presentation">edit</docs-icon>
|
||||
</a>
|
||||
</a>`
|
||||
: ''
|
||||
}
|
||||
</div>`;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user