-
-
Notifications
You must be signed in to change notification settings - Fork 19
/
Copy pathindex.js
157 lines (133 loc) · 4.21 KB
/
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
/**
* @typedef {import('lowlight').Root} LowlightRoot
* @typedef {import('lowlight/lib/core.js').HighlightSyntax} HighlightSyntax
* @typedef {import('hast').Root} Root
* @typedef {import('hast').Element} Element
* @typedef {Root|Root['children'][number]} Node
*
* @typedef Options
* Configuration.
* @property {string} [prefix='hljs-']
* Prefix to use before classes.
* @property {boolean|Array<string>} [subset]
* Scope of languages to check when auto-detecting (default: all languages).
* Pass `false` to not highlight code without language classes.
* @property {boolean} [ignoreMissing=false]
* Swallow errors for missing languages.
* By default, unregistered syntaxes throw an error when they are used.
* Pass `true` to swallow those errors and thus ignore code with unknown code
* languages.
* @property {Array<string>} [plainText=[]]
* List of plain-text languages.
* Pass any languages you would like to be kept as plain-text instead of
* getting highlighted.
* @property {Record<string, string|Array<string>>} [aliases={}]
* Register more aliases.
* Passed to `lowlight.registerAlias`.
* @property {Record<string, HighlightSyntax>} [languages={}]
* Register more languages.
* Each key/value pair passed as arguments to `lowlight.registerLanguage`.
*/
import {lowlight} from 'lowlight'
import {toText} from 'hast-util-to-text'
import {visit} from 'unist-util-visit'
const own = {}.hasOwnProperty
/**
* Plugin to highlight the syntax of code with lowlight (`highlight.js`).
*
* @type {import('unified').Plugin<[Options?] | Array<void>, Root>}
*/
export default function rehypeHighlight(options = {}) {
const {aliases, languages, prefix, plainText, ignoreMissing, subset} = options
let name = 'hljs'
if (aliases) {
lowlight.registerAlias(aliases)
}
if (languages) {
/** @type {string} */
let key
for (key in languages) {
if (own.call(languages, key)) {
lowlight.registerLanguage(key, languages[key])
}
}
}
if (prefix) {
const pos = prefix.indexOf('-')
name = pos > -1 ? prefix.slice(0, pos) : prefix
}
return (tree) => {
// eslint-disable-next-line complexity
visit(tree, 'element', (node, _, givenParent) => {
const parent = /** @type {Node?} */ (givenParent)
if (
!parent ||
!('tagName' in parent) ||
parent.tagName !== 'pre' ||
node.tagName !== 'code' ||
!node.properties
) {
return
}
const lang = language(node)
if (
lang === false ||
(!lang && subset === false) ||
(lang && plainText && plainText.includes(lang))
) {
return
}
if (!Array.isArray(node.properties.className)) {
node.properties.className = []
}
if (!node.properties.className.includes(name)) {
node.properties.className.unshift(name)
}
/** @type {LowlightRoot} */
let result
try {
result = lang
? lowlight.highlight(lang, toText(parent), {prefix})
: // @ts-expect-error: we checked that `subset` is not a boolean.
lowlight.highlightAuto(toText(parent), {prefix, subset})
} catch (error) {
const exception = /** @type {Error} */ (error)
if (!ignoreMissing || !/Unknown language/.test(exception.message)) {
throw error
}
return
}
if (!lang && result.data.language) {
node.properties.className.push('language-' + result.data.language)
}
if (Array.isArray(result.children) && result.children.length > 0) {
node.children = result.children
}
})
}
}
/**
* Get the programming language of `node`.
*
* @param {Element} node
* @returns {false|string|undefined}
*/
function language(node) {
const className = node.properties && node.properties.className
let index = -1
if (!Array.isArray(className)) {
return
}
while (++index < className.length) {
const value = String(className[index])
if (value === 'no-highlight' || value === 'nohighlight') {
return false
}
if (value.slice(0, 5) === 'lang-') {
return value.slice(5)
}
if (value.slice(0, 9) === 'language-') {
return value.slice(9)
}
}
}