1- import { Renderer } from 'marked' ;
1+ import { Renderer , Slugger } from 'marked' ;
22import { basename , extname } from 'path' ;
33
4- /** Regular expression that matches whitespace. */
5- const whitespaceRegex = / \W + / g;
6-
74/** Regular expression that matches example comments. */
85const exampleCommentRegex = / < ! - - \s * e x a m p l e \( ( [ ^ ) ] + ) \) \s * - - > / g;
96
@@ -13,15 +10,23 @@ const exampleCommentRegex = /<!--\s*example\(([^)]+)\)\s*-->/g;
1310 */
1411export class DocsMarkdownRenderer extends Renderer {
1512
13+ /** Set of fragment links discovered in the currently rendered file. */
14+ private _referencedFragments = new Set < string > ( ) ;
15+
16+ /**
17+ * Slugger provided by the `marked` package. Can be used to create unique
18+ * ids for headings.
19+ */
20+ private _slugger = new Slugger ( ) ;
21+
1622 /**
1723 * Transforms a markdown heading into the corresponding HTML output. In our case, we
1824 * want to create a header-link for each H3 and H4 heading. This allows users to jump to
1925 * specific parts of the docs.
2026 */
21- heading ( label : string , level : number , _raw : string ) {
27+ heading ( label : string , level : number , raw : string ) {
2228 if ( level === 3 || level === 4 ) {
23- const headingId = label . toLowerCase ( ) . replace ( whitespaceRegex , '-' ) ;
24-
29+ const headingId = this . _slugger . slug ( raw ) ;
2530 return `
2631 <h${ level } id="${ headingId } " class="docs-header-link">
2732 <span header-link="${ headingId } "></span>
@@ -42,6 +47,11 @@ export class DocsMarkdownRenderer extends Renderer {
4247 return super . link ( `guide/${ basename ( href , extname ( href ) ) } ` , title , text ) ;
4348 }
4449
50+ // Keep track of all fragments discovered in a file.
51+ if ( href . startsWith ( '#' ) ) {
52+ this . _referencedFragments . add ( href . substr ( 1 ) ) ;
53+ }
54+
4555 return super . link ( href , title , text ) ;
4656 }
4757
@@ -90,7 +100,26 @@ export class DocsMarkdownRenderer extends Renderer {
90100 * Method that will be called after a markdown file has been transformed to HTML. This method
91101 * can be used to finalize the content (e.g. by adding an additional wrapper HTML element)
92102 */
93- finalizeOutput ( output : string ) : string {
103+ finalizeOutput ( output : string , fileName : string ) : string {
104+ const failures : string [ ] = [ ] ;
105+
106+ // Collect any fragment links that do not resolve to existing fragments in the
107+ // rendered file. We want to error for broken fragment links.
108+ this . _referencedFragments . forEach ( id => {
109+ if ( this . _slugger . seen [ id ] === undefined ) {
110+ failures . push ( `Found link to "${ id } ". This heading does not exist.` ) ;
111+ }
112+ } ) ;
113+
114+ if ( failures . length ) {
115+ console . error ( `Could not process file: ${ fileName } . Please fix the following errors:` ) ;
116+ failures . forEach ( message => console . error ( ` - ${ message } ` ) ) ;
117+ process . exit ( 1 ) ;
118+ }
119+
120+ this . _slugger . seen = { } ;
121+ this . _referencedFragments . clear ( ) ;
122+
94123 return `<div class="docs-markdown">${ output } </div>` ;
95124 }
96125}
0 commit comments