1
- import { Renderer } from 'marked' ;
1
+ import { Renderer , Slugger } from 'marked' ;
2
2
import { basename , extname } from 'path' ;
3
3
4
- /** Regular expression that matches whitespace. */
5
- const whitespaceRegex = / \W + / g;
6
-
7
4
/** Regular expression that matches example comments. */
8
5
const exampleCommentRegex = / < ! - - \s * e x a m p l e \( ( [ ^ ) ] + ) \) \s * - - > / g;
9
6
@@ -13,15 +10,23 @@ const exampleCommentRegex = /<!--\s*example\(([^)]+)\)\s*-->/g;
13
10
*/
14
11
export class DocsMarkdownRenderer extends Renderer {
15
12
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
+
16
22
/**
17
23
* Transforms a markdown heading into the corresponding HTML output. In our case, we
18
24
* want to create a header-link for each H3 and H4 heading. This allows users to jump to
19
25
* specific parts of the docs.
20
26
*/
21
- heading ( label : string , level : number , _raw : string ) {
27
+ heading ( label : string , level : number , raw : string ) {
22
28
if ( level === 3 || level === 4 ) {
23
- const headingId = label . toLowerCase ( ) . replace ( whitespaceRegex , '-' ) ;
24
-
29
+ const headingId = this . _slugger . slug ( raw ) ;
25
30
return `
26
31
<h${ level } id="${ headingId } " class="docs-header-link">
27
32
<span header-link="${ headingId } "></span>
@@ -42,6 +47,11 @@ export class DocsMarkdownRenderer extends Renderer {
42
47
return super . link ( `guide/${ basename ( href , extname ( href ) ) } ` , title , text ) ;
43
48
}
44
49
50
+ // Keep track of all fragments discovered in a file.
51
+ if ( href . startsWith ( '#' ) ) {
52
+ this . _referencedFragments . add ( href . substr ( 1 ) ) ;
53
+ }
54
+
45
55
return super . link ( href , title , text ) ;
46
56
}
47
57
@@ -90,7 +100,26 @@ export class DocsMarkdownRenderer extends Renderer {
90
100
* Method that will be called after a markdown file has been transformed to HTML. This method
91
101
* can be used to finalize the content (e.g. by adding an additional wrapper HTML element)
92
102
*/
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
+
94
123
return `<div class="docs-markdown">${ output } </div>` ;
95
124
}
96
125
}
0 commit comments