Skip to content

Commit c17b150

Browse files
devversionmmalerba
authored andcommitted
build: generate proper anchor ids for headings in markdown (#19371)
1 parent ce136a9 commit c17b150

File tree

4 files changed

+50
-20
lines changed

4 files changed

+50
-20
lines changed

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@
9191
"@types/gulp": "3.8.32",
9292
"@types/inquirer": "^0.0.43",
9393
"@types/jasmine": "^3.5.4",
94-
"@types/marked": "^0.4.2",
94+
"@types/marked": "^0.7.4",
9595
"@types/merge2": "^0.3.30",
9696
"@types/minimist": "^1.2.0",
9797
"@types/node": "^12.11.1",
@@ -132,7 +132,7 @@
132132
"karma-sauce-launcher": "^2.0.2",
133133
"karma-sourcemap-loader": "^0.3.7",
134134
"madge": "^3.4.4",
135-
"marked": "^0.6.2",
135+
"marked": "^1.0.0",
136136
"merge2": "^1.2.3",
137137
"minimatch": "^3.0.4",
138138
"minimist": "^1.2.0",

tools/markdown-to-html/docs-marked-renderer.ts

Lines changed: 37 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,6 @@
1-
import {Renderer} from 'marked';
1+
import {Renderer, Slugger} from 'marked';
22
import {basename, extname} from 'path';
33

4-
/** Regular expression that matches whitespace. */
5-
const whitespaceRegex = /\W+/g;
6-
74
/** Regular expression that matches example comments. */
85
const exampleCommentRegex = /<!--\s*example\(([^)]+)\)\s*-->/g;
96

@@ -13,15 +10,23 @@ const exampleCommentRegex = /<!--\s*example\(([^)]+)\)\s*-->/g;
1310
*/
1411
export 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
}

tools/markdown-to-html/transform-markdown.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,8 @@ if (require.main === module) {
2727
// Bazel bin directory.
2828
inputFiles.forEach(inputPath => {
2929
const outputPath = join(bazelBinPath, inputPath.replace(markdownExtension, '.html'));
30-
const htmlOutput = markdownRenderer.finalizeOutput(marked(readFileSync(inputPath, 'utf8')));
30+
const htmlOutput = markdownRenderer.finalizeOutput(
31+
marked(readFileSync(inputPath, 'utf8')), inputPath);
3132

3233
writeFileSync(outputPath, htmlOutput);
3334
});

yarn.lock

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1310,10 +1310,10 @@
13101310
resolved "https://registry.yarnpkg.com/@types/long/-/long-4.0.0.tgz#719551d2352d301ac8b81db732acb6bdc28dbdef"
13111311
integrity sha512-1w52Nyx4Gq47uuu0EVcsHBxZFJgurQ+rTKS3qMHxR1GY2T8c2AJYd6vZoZ9q1rupaDjU0yT+Jc2XTyXkjeMA+Q==
13121312

1313-
"@types/marked@^0.4.2":
1314-
version "0.4.2"
1315-
resolved "https://registry.yarnpkg.com/@types/marked/-/marked-0.4.2.tgz#64a89e53ea37f61cc0f3ee1732c555c2dbf6452f"
1316-
integrity sha512-cDB930/7MbzaGF6U3IwSQp6XBru8xWajF5PV2YZZeV8DyiliTuld11afVztGI9+yJZ29il5E+NpGA6ooV/Cjkg==
1313+
"@types/marked@^0.7.4":
1314+
version "0.7.4"
1315+
resolved "https://registry.yarnpkg.com/@types/marked/-/marked-0.7.4.tgz#607685669bb1bbde2300bc58ba43486cbbee1f0a"
1316+
integrity sha512-fdg0NO4qpuHWtZk6dASgsrBggY+8N4dWthl1bAQG9ceKUNKFjqpHaDKCAhRUI6y8vavG7hLSJ4YBwJtZyZEXqw==
13171317

13181318
"@types/merge2@^0.3.30":
13191319
version "0.3.30"
@@ -7724,16 +7724,16 @@ marked@^0.3.2:
77247724
resolved "https://registry.yarnpkg.com/marked/-/marked-0.3.19.tgz#5d47f709c4c9fc3c216b6d46127280f40b39d790"
77257725
integrity sha512-ea2eGWOqNxPcXv8dyERdSr/6FmzvWwzjMxpfGB/sbMccXoct+xY+YukPD+QTUZwyvK7BZwcr4m21WBOW41pAkg==
77267726

7727-
marked@^0.6.2:
7728-
version "0.6.2"
7729-
resolved "https://registry.yarnpkg.com/marked/-/marked-0.6.2.tgz#c574be8b545a8b48641456ca1dbe0e37b6dccc1a"
7730-
integrity sha512-LqxwVH3P/rqKX4EKGz7+c2G9r98WeM/SW34ybhgNGhUQNKtf1GmmSkJ6cDGJ/t6tiyae49qRkpyTw2B9HOrgUA==
7731-
77327727
marked@^0.7.0:
77337728
version "0.7.0"
77347729
resolved "https://registry.yarnpkg.com/marked/-/marked-0.7.0.tgz#b64201f051d271b1edc10a04d1ae9b74bb8e5c0e"
77357730
integrity sha512-c+yYdCZJQrsRjTPhUx7VKkApw9bwDkNbHUKo1ovgcfDjb2kc8rLuRbIFyXL5WOEUwzSSKo3IXpph2K6DqB/KZg==
77367731

7732+
marked@^1.0.0:
7733+
version "1.0.0"
7734+
resolved "https://registry.yarnpkg.com/marked/-/marked-1.0.0.tgz#d35784245a04871e5988a491e28867362e941693"
7735+
integrity sha512-Wo+L1pWTVibfrSr+TTtMuiMfNzmZWiOPeO7rZsQUY5bgsxpHesBEcIWJloWVTFnrMXnf/TL30eTFSGJddmQAng==
7736+
77377737
matchdep@^2.0.0:
77387738
version "2.0.0"
77397739
resolved "https://registry.yarnpkg.com/matchdep/-/matchdep-2.0.0.tgz#c6f34834a0d8dbc3b37c27ee8bbcb27c7775582e"

0 commit comments

Comments
 (0)