From db00b1a91de7fdb0b9961d263f56a6c761821842 Mon Sep 17 00:00:00 2001 From: Juan Pablo Garcia Ripa Date: Tue, 22 Nov 2022 23:23:55 +0100 Subject: [PATCH] add DocCodeSpanLink to allow links with backticks Signed-off-by: Juan Pablo Garcia Ripa --- .../src/commands/api-reports/api-extractor.ts | 101 ++++++++++++++++-- 1 file changed, 90 insertions(+), 11 deletions(-) diff --git a/packages/repo-tools/src/commands/api-reports/api-extractor.ts b/packages/repo-tools/src/commands/api-reports/api-extractor.ts index d434aae0a6..f74e97381c 100644 --- a/packages/repo-tools/src/commands/api-reports/api-extractor.ts +++ b/packages/repo-tools/src/commands/api-reports/api-extractor.ts @@ -39,9 +39,17 @@ import { DocNode, IDocNodeContainerParameters, TSDocTagSyntaxKind, + TSDocConfiguration, + DocPlainText, + DocLinkTag, } from '@microsoft/tsdoc'; import { TSDocConfigFile } from '@microsoft/tsdoc-config'; -import { ApiPackage, ApiModel, ApiItem } from '@microsoft/api-extractor-model'; +import { + ApiPackage, + ApiModel, + ApiItem, + ApiItemKind, +} from '@microsoft/api-extractor-model'; import { IMarkdownDocumenterOptions, MarkdownDocumenter, @@ -827,6 +835,16 @@ export async function buildDocs({ } } + // This class only propose is to have a different kind and be able to render links with backticks + class DocCodeSpanLink extends DocLinkTag { + static kind = 'DocCodeSpanLink'; + + /** @override */ + public get kind(): string { + return DocCodeSpanLink.kind; + } + } + // This is where we actually write the markdown and where we can hook // in the rendering of our own nodes. class CustomCustomMarkdownEmitter extends CustomMarkdownEmitter { @@ -845,7 +863,7 @@ export async function buildDocs({ /** @override */ protected writeNode( docNode: DocNode, - context: IMarkdownEmitterContext, + context: IMarkdownEmitterContext, docNodeSiblings: boolean, ): void { switch (docNode.kind) { @@ -861,6 +879,26 @@ export async function buildDocs({ context.writer.writeLine(); break; } + case DocCodeSpanLink.kind: { + const node = docNode as DocLinkTag; + + if (node.codeDestination) { + // TODO @sarabadu understand if we need `codeDestination` at all on this custom DocCodeSpanLink + super.writeLinkTagWithCodeDestination(node, context); + } else if (node.urlDestination) { + const linkText = + node.linkText !== undefined ? node.linkText : node.urlDestination; + const encodedLinkText = this.getEscapedText( + linkText.replace(/\s+/g, ' '), + ); + context.writer.write('['); + context.writer.write(`\`${encodedLinkText}\``); + context.writer.write(`](${node.urlDestination})`); + } else if (node.linkText) { + this.writePlainText(node.linkText, context); + } + break; + } default: super.writeNode(docNode, context, docNodeSiblings); } @@ -884,13 +922,22 @@ export async function buildDocs({ super(options); // It's a strict model, we gotta register the allowed usage of our new node - this._tsdocConfiguration.docNodeManager.registerDocNodes( - '@backstage/docs', - [{ docNodeKind: DocFrontMatter.kind, constructor: DocFrontMatter }], - ); - this._tsdocConfiguration.docNodeManager.registerAllowableChildren( - 'Paragraph', - [DocFrontMatter.kind], + ( + this._tsdocConfiguration as TSDocConfiguration + ).docNodeManager.registerDocNodes('@backstage/docs', [ + { docNodeKind: DocFrontMatter.kind, constructor: DocFrontMatter }, + ]); + ( + this._tsdocConfiguration as TSDocConfiguration + ).docNodeManager.registerDocNodes('@backstage/docs', [ + { docNodeKind: DocCodeSpanLink.kind, constructor: DocCodeSpanLink }, + ]); + ( + this._tsdocConfiguration as TSDocConfiguration + ).docNodeManager.registerAllowableChildren('Paragraph', [ + DocFrontMatter.kind, + DocCodeSpanLink.kind, + ]); ); this._markdownEmitter = new CustomCustomMarkdownEmitter(newModel); @@ -941,8 +988,40 @@ export async function buildDocs({ }), ); - // Now write the actual breadcrumbs - super._writeBreadcrumb(output, apiItem); + const configuration: TSDocConfiguration = this._tsdocConfiguration; + + output.appendNodeInParagraph( + new DocLinkTag({ + configuration, + tagName: '@link', + linkText: 'Home', + urlDestination: this._getLinkFilenameForApiItem(this._apiModel), + }), + ); + + for (const hierarchyItem of apiItem.getHierarchy()) { + switch (hierarchyItem.kind) { + case ApiItemKind.Model: + case ApiItemKind.EntryPoint: + // We don't show the model as part of the breadcrumb because it is the root-level container. + // We don't show the entry point because today API Extractor doesn't support multiple entry points; + // this may change in the future. + break; + default: + output.appendNodesInParagraph([ + new DocPlainText({ + configuration, + text: ' > ', + }), + new DocCodeSpanLink({ + configuration, + tagName: '@link', + linkText: hierarchyItem.displayName, + urlDestination: this._getLinkFilenameForApiItem(hierarchyItem), + }), + ]); + } + } // We wanna ignore the header that always gets written after the breadcrumb // This otherwise becomes more or less a duplicate of the title in the front matter