2
2
// See end of file for extended copyright information.
3
3
4
4
import MarkdownIt from "markdown-it" ;
5
+ import assert from "node:assert" ;
5
6
6
7
let markdownParser = new MarkdownIt ( "commonmark" ) ;
7
8
@@ -10,6 +11,7 @@ export function releasesMarkdownToHTML(releasesMarkdown) {
10
11
let tokens = markdownParser . parse ( releasesMarkdown , env ) ;
11
12
tokens = removeTitle ( tokens ) ;
12
13
tokens = demoteHeadings ( tokens ) ;
14
+ tokens = linkifyVersions ( tokens ) ;
13
15
14
16
let html = markdownParser . renderer . render (
15
17
tokens ,
@@ -54,6 +56,81 @@ function demoteHeadings(markdownTokens) {
54
56
} ) ;
55
57
}
56
58
59
+ function linkifyVersions ( markdownTokens ) {
60
+ markdownTokens = [ ...markdownTokens ] ;
61
+
62
+ // @type Array<[number, number]>
63
+ let versionHeadingSpanIndexes = [ ] ;
64
+ for ( let i = 0 ; i < markdownTokens . length ; ++ i ) {
65
+ let token = markdownTokens [ i ] ;
66
+ if ( token . tag === "h3" ) {
67
+ if ( token . type === "heading_open" ) {
68
+ versionHeadingSpanIndexes . push ( [ i ] ) ;
69
+ }
70
+ if ( token . type === "heading_close" ) {
71
+ versionHeadingSpanIndexes [ versionHeadingSpanIndexes . length - 1 ] . push ( i ) ;
72
+ }
73
+ }
74
+ }
75
+
76
+ versionHeadingSpanIndexes . reverse ( ) ;
77
+ for ( let [ headingBeginIndex , headingEndIndex ] of versionHeadingSpanIndexes ) {
78
+ let textTokens = markdownTokens . slice (
79
+ headingBeginIndex + 1 ,
80
+ headingEndIndex
81
+ ) ;
82
+ let text = textTokens . map ( ( token ) => token . content ) . join ( "" ) ;
83
+ let versionInfo = extractVersionInfoFromHeadingText ( text ) ;
84
+ if ( versionInfo === null ) {
85
+ continue ;
86
+ }
87
+ let currentVersionID = versionInfo . version ;
88
+
89
+ let headingOpenToken = markdownTokens [ headingBeginIndex ] ;
90
+ let headingCloseToken = markdownTokens [ headingEndIndex ] ;
91
+ let newTokens = [
92
+ {
93
+ ...headingOpenToken ,
94
+ attrs : [ ...( headingOpenToken . attrs ?? [ ] ) , [ "id" , currentVersionID ] ] ,
95
+ } ,
96
+ {
97
+ type : "link_open" ,
98
+ tag : "a" ,
99
+ attrs : [ [ "href" , `#${ currentVersionID } ` ] ] ,
100
+ } ,
101
+ ...textTokens ,
102
+ {
103
+ type : "link_close" ,
104
+ tag : "a" ,
105
+ } ,
106
+ headingCloseToken ,
107
+ ] ;
108
+ markdownTokens . splice (
109
+ headingBeginIndex ,
110
+ headingEndIndex - headingBeginIndex ,
111
+ ...newTokens
112
+ ) ;
113
+ }
114
+
115
+ return markdownTokens ;
116
+ }
117
+
118
+ function extractVersionInfoFromHeadingText ( text ) {
119
+ let match = text . match (
120
+ / ^ (?< version > (?: \d + \. ) * \d + ) \( (?< date > \d + - \d + - \d + ) \) $ /
121
+ ) ;
122
+ if ( match === null ) {
123
+ if ( text !== "Unreleased" ) {
124
+ console . warn ( `warning: failed to parse version info from ${ text } ` ) ;
125
+ }
126
+ return null ;
127
+ }
128
+ return {
129
+ version : match . groups . version ,
130
+ date : match . groups . date ,
131
+ } ;
132
+ }
133
+
57
134
// quick-lint-js finds bugs in JavaScript programs.
58
135
// Copyright (C) 2020 Matthew "strager" Glazar
59
136
//
0 commit comments