sugar-high 1.3.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +128 -43
- package/lib/core.d.ts +117 -0
- package/lib/core.js +202 -0
- package/lib/index.d.ts +39 -24
- package/lib/index.js +15 -1007
- package/lib/lang.d.ts +14 -0
- package/lib/lang.js +132 -0
- package/lib/presets/lang/cpp.js +22 -0
- package/lib/presets/lang/csharp.js +21 -0
- package/lib/presets/lang/css.js +5 -0
- package/lib/presets/lang/diff.js +12 -6
- package/lib/presets/lang/dockerfile.js +5 -0
- package/lib/presets/lang/graphql.js +5 -0
- package/lib/presets/lang/hash-comment-base.js +7 -0
- package/lib/presets/lang/hcl.js +4 -0
- package/lib/presets/lang/html.js +14 -0
- package/lib/presets/lang/javascript-runtime.js +781 -0
- package/lib/presets/lang/javascript.js +6 -0
- package/lib/presets/lang/json.js +4 -0
- package/lib/presets/lang/kotlin.js +5 -0
- package/lib/presets/lang/markdown.js +15 -0
- package/lib/presets/lang/php.js +5 -0
- package/lib/presets/lang/plain-base.js +4 -0
- package/lib/presets/lang/powershell.js +5 -0
- package/lib/presets/lang/shell.js +10 -0
- package/lib/presets/lang/sql.js +31 -0
- package/lib/presets/lang/swift.js +5 -0
- package/lib/presets/lang/toml.js +5 -0
- package/lib/presets/lang/typescript.js +7 -0
- package/lib/presets/lang/yaml.js +10 -0
- package/lib/shared.js +149 -0
- package/package.json +15 -10
- package/lib/presets/index.d.ts +0 -18
- package/lib/presets/index.js +0 -8
package/lib/index.js
CHANGED
|
@@ -1,1017 +1,25 @@
|
|
|
1
1
|
// @ts-check
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
'if',
|
|
9
|
-
'else',
|
|
10
|
-
'return',
|
|
11
|
-
'function',
|
|
12
|
-
'var',
|
|
13
|
-
'let',
|
|
14
|
-
'const',
|
|
15
|
-
'true',
|
|
16
|
-
'false',
|
|
17
|
-
'undefined',
|
|
18
|
-
'this',
|
|
19
|
-
'new',
|
|
20
|
-
'delete',
|
|
21
|
-
'typeof',
|
|
22
|
-
'in',
|
|
23
|
-
'instanceof',
|
|
24
|
-
'void',
|
|
25
|
-
'break',
|
|
26
|
-
'continue',
|
|
27
|
-
'switch',
|
|
28
|
-
'case',
|
|
29
|
-
'default',
|
|
30
|
-
'throw',
|
|
31
|
-
'try',
|
|
32
|
-
'catch',
|
|
33
|
-
'finally',
|
|
34
|
-
'debugger',
|
|
35
|
-
'with',
|
|
36
|
-
'yield',
|
|
37
|
-
'async',
|
|
38
|
-
'await',
|
|
39
|
-
'class',
|
|
40
|
-
'extends',
|
|
41
|
-
'super',
|
|
42
|
-
'import',
|
|
43
|
-
'export',
|
|
44
|
-
'from',
|
|
45
|
-
'static',
|
|
46
|
-
])
|
|
3
|
+
import {
|
|
4
|
+
parse,
|
|
5
|
+
render,
|
|
6
|
+
} from './core.js'
|
|
7
|
+
import { languages } from './lang.js'
|
|
47
8
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
'
|
|
51
|
-
'interface',
|
|
52
|
-
'enum',
|
|
53
|
-
'implements',
|
|
54
|
-
'readonly',
|
|
55
|
-
'abstract',
|
|
56
|
-
'declare',
|
|
57
|
-
'namespace',
|
|
58
|
-
'module',
|
|
59
|
-
'private',
|
|
60
|
-
'protected',
|
|
61
|
-
'public',
|
|
62
|
-
'override',
|
|
63
|
-
'keyof',
|
|
64
|
-
'infer',
|
|
65
|
-
'is',
|
|
66
|
-
'asserts',
|
|
67
|
-
'satisfies',
|
|
68
|
-
'as',
|
|
69
|
-
'unknown',
|
|
70
|
-
'never',
|
|
71
|
-
'any',
|
|
72
|
-
// Built-in type names (annotations / type positions)
|
|
73
|
-
'number',
|
|
74
|
-
'string',
|
|
75
|
-
'boolean',
|
|
76
|
-
'bigint',
|
|
77
|
-
'symbol',
|
|
78
|
-
'object',
|
|
79
|
-
])
|
|
80
|
-
|
|
81
|
-
const Signs = new Set([
|
|
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
|
-
...JSXBrackets,
|
|
110
|
-
])
|
|
111
|
-
|
|
112
|
-
const DefaultOptions = {
|
|
113
|
-
keywords: Keywords_Js,
|
|
114
|
-
onCommentStart: isCommentStart_Js,
|
|
115
|
-
onCommentEnd: isCommentEnd_Js,
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
/**
|
|
119
|
-
* Fast, heuristic TS detection. It intentionally prefers speed over full parsing.
|
|
120
|
-
* @param {string} code
|
|
121
|
-
* @returns {boolean}
|
|
122
|
-
*/
|
|
123
|
-
function isLikelyTypeScript(code) {
|
|
124
|
-
let tsScore = 0
|
|
125
|
-
|
|
126
|
-
// TS-only declarations and operators.
|
|
127
|
-
if (/\binterface\s+[A-Za-z_$][\w$]*/.test(code)) tsScore += 2
|
|
128
|
-
if (/\btype\s+[A-Za-z_$][\w$]*\s*=/.test(code)) tsScore += 2
|
|
129
|
-
if (/\benum\s+[A-Za-z_$][\w$]*/.test(code)) tsScore += 2
|
|
130
|
-
if (/\b(?:implements|readonly|declare|namespace|satisfies|infer|keyof|asserts)\b/.test(code)) tsScore += 2
|
|
131
|
-
|
|
132
|
-
// Common TS annotations/signatures.
|
|
133
|
-
if (/:\s*[A-Za-z_$][\w$]*(?:<[^>\n]+>)?(?:\[\])?(?=\s*[,)=;{])/m.test(code)) tsScore += 1
|
|
134
|
-
if (/\b(?:const|let|var)\s+[A-Za-z_$][\w$]*\s*:\s*/.test(code)) tsScore += 1
|
|
135
|
-
if (/\)\s*:\s*[A-Za-z_$][\w$]*(?:<[^>\n]+>)?(?:\[\])?\s*(?:=>|\{)/.test(code)) tsScore += 1
|
|
136
|
-
|
|
137
|
-
return tsScore >= 2
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
/**
|
|
141
|
-
* Detects `<T, U = ...>(` style generic parameter lists so they are not
|
|
142
|
-
* treated as JSX tags.
|
|
143
|
-
* @param {string} code
|
|
144
|
-
* @param {number} startIndex
|
|
145
|
-
* @returns {boolean}
|
|
146
|
-
*/
|
|
147
|
-
function isTypeParameterListStart(code, startIndex) {
|
|
148
|
-
if (code[startIndex] !== '<') return false
|
|
149
|
-
|
|
150
|
-
let depth = 0
|
|
151
|
-
let sawIdentifierStart = false
|
|
152
|
-
|
|
153
|
-
for (let i = startIndex; i < code.length; i++) {
|
|
154
|
-
const ch = code[i]
|
|
155
|
-
|
|
156
|
-
if (ch === '<') {
|
|
157
|
-
depth++
|
|
158
|
-
continue
|
|
159
|
-
}
|
|
160
|
-
if (ch === '>') {
|
|
161
|
-
depth--
|
|
162
|
-
if (depth === 0) {
|
|
163
|
-
let next = i + 1
|
|
164
|
-
while (next < code.length && /\s/.test(code[next])) next++
|
|
165
|
-
if (!(sawIdentifierStart && code[next] === '(')) return false
|
|
166
|
-
|
|
167
|
-
// Focus this heuristic on generic arrow functions:
|
|
168
|
-
// const fn = <T>(arg) => ...
|
|
169
|
-
const tail = code.slice(next, next + 320)
|
|
170
|
-
return /\)\s*(?::[\s\S]{0,120}?)?=>/.test(tail)
|
|
171
|
-
}
|
|
172
|
-
continue
|
|
173
|
-
}
|
|
174
|
-
if (depth === 0) continue
|
|
175
|
-
|
|
176
|
-
if (/[$A-Za-z_]/.test(ch)) {
|
|
177
|
-
sawIdentifierStart = true
|
|
178
|
-
continue
|
|
179
|
-
}
|
|
180
|
-
if (/[\s,\.\=\?\:\|\&\[\]]/.test(ch)) continue
|
|
181
|
-
|
|
182
|
-
return false
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
return false
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
/**
|
|
189
|
-
*
|
|
190
|
-
* 0 - identifier
|
|
191
|
-
* 1 - keyword
|
|
192
|
-
* 2 - string
|
|
193
|
-
* 3 - Class, number and null
|
|
194
|
-
* 4 - property
|
|
195
|
-
* 5 - entity
|
|
196
|
-
* 6 - jsx literals
|
|
197
|
-
* 7 - sign
|
|
198
|
-
* 8 - comment
|
|
199
|
-
* 9 - break
|
|
200
|
-
* 10 - space
|
|
201
|
-
*
|
|
202
|
-
*/
|
|
203
|
-
const TokenTypes = /** @type {const} */ ([
|
|
204
|
-
'identifier',
|
|
205
|
-
'keyword',
|
|
206
|
-
'string',
|
|
207
|
-
'class',
|
|
208
|
-
'property',
|
|
209
|
-
'entity',
|
|
210
|
-
'jsxliterals',
|
|
211
|
-
'sign',
|
|
212
|
-
'comment',
|
|
213
|
-
'break',
|
|
214
|
-
'space',
|
|
215
|
-
])
|
|
216
|
-
const [
|
|
217
|
-
T_IDENTIFIER,
|
|
218
|
-
T_KEYWORD,
|
|
219
|
-
T_STRING,
|
|
220
|
-
T_CLS_NUMBER,
|
|
221
|
-
T_PROPERTY,
|
|
222
|
-
T_ENTITY,
|
|
223
|
-
T_JSX_LITERALS,
|
|
224
|
-
T_SIGN,
|
|
225
|
-
T_COMMENT,
|
|
226
|
-
T_BREAK,
|
|
227
|
-
T_SPACE,
|
|
228
|
-
] = /** @types {const} */ TokenTypes.map((_, i) => i)
|
|
229
|
-
|
|
230
|
-
function isSpaces(str) {
|
|
231
|
-
return /^[^\S\r\n]+$/g.test(str)
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
function isSign(ch) {
|
|
235
|
-
return Signs.has(ch)
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
const HtmlEntities = /** @type {Record<string, string>} */ ({
|
|
239
|
-
'&': '&',
|
|
240
|
-
'<': '<',
|
|
241
|
-
'>': '>',
|
|
242
|
-
'"': '"',
|
|
243
|
-
"'": ''',
|
|
244
|
-
})
|
|
245
|
-
|
|
246
|
-
/** @param {string} str */
|
|
247
|
-
function encode(str) {
|
|
248
|
-
if (!/[&<>"']/.test(str)) return str
|
|
249
|
-
return str.replace(/[&<>"']/g, chr => HtmlEntities[chr])
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
function isWord(chr) {
|
|
253
|
-
return /^[\w_]+$/.test(chr) || hasUnicode(chr)
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
function isCls(str) {
|
|
257
|
-
const chr0 = str[0]
|
|
258
|
-
return isWord(chr0) &&
|
|
259
|
-
chr0 === chr0.toUpperCase() ||
|
|
260
|
-
str === 'null'
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
function hasUnicode(s) {
|
|
264
|
-
return /[^\u0000-\u007f]/.test(s);
|
|
265
|
-
}
|
|
266
|
-
|
|
267
|
-
function isAlpha(chr) {
|
|
268
|
-
return /^[a-zA-Z]$/.test(chr)
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
function isIdentifierChar(chr) {
|
|
272
|
-
return isAlpha(chr) || hasUnicode(chr)
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
function isIdentifier(str) {
|
|
276
|
-
return isIdentifierChar(str[0]) && (str.length === 1 || isWord(str.slice(1)))
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
function isStrTemplateChr(chr) {
|
|
280
|
-
return chr === '`'
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
function isSingleQuotes(chr) {
|
|
284
|
-
return chr === '"' || chr === "'"
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
function isStringQuotation(chr) {
|
|
288
|
-
return isSingleQuotes(chr) || isStrTemplateChr(chr)
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
/** @returns {0|1|2} */
|
|
292
|
-
function isCommentStart_Js(curr, next) {
|
|
293
|
-
const str = curr + next
|
|
294
|
-
if (str === '/*') return 2
|
|
295
|
-
return str === '//' ? 1 : 0
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
/** @returns {0|1|2} */
|
|
299
|
-
function isCommentEnd_Js(prev, curr) {
|
|
300
|
-
return (prev + curr) === '*/'
|
|
301
|
-
? 2
|
|
302
|
-
: curr === '\n' ? 1 : 0
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
function isRegexStart(str) {
|
|
306
|
-
return str[0] === '/' && !isCommentStart_Js(str[0], str[1])
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
function isPropertyKey(code, quoteEnd) {
|
|
310
|
-
let i = quoteEnd + 1
|
|
311
|
-
while (i < code.length && /\s/.test(code[i])) i++
|
|
312
|
-
return code[i] === ':'
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
/**
|
|
316
|
-
* @param {string} code
|
|
317
|
-
* @param {{
|
|
318
|
-
* keywords?: Set<string>
|
|
319
|
-
* typeKeywords?: Set<string>
|
|
320
|
-
* onCommentStart?: (curr: string, next: string) => number | boolean
|
|
321
|
-
* onCommentEnd?: (prev: string, curr: string) => number | boolean
|
|
322
|
-
* onQuote?: (curr: string, i: number, code: string) => number | null | undefined
|
|
323
|
-
* quotedKeys?: boolean
|
|
324
|
-
* lineClassName?: (line: string, index: number) => string | null | undefined
|
|
325
|
-
* } | undefined} options
|
|
326
|
-
* Optional `onQuote(curr, i, code)` at `code[i] === "'"`: return length to consume from `i` (>= 1),
|
|
327
|
-
* or null/undefined/below 1 for default JS single-quoted strings. No substring allocation.
|
|
328
|
-
* @return {Array<[number, string]>}
|
|
329
|
-
*/
|
|
330
|
-
function tokenize(code, options) {
|
|
331
|
-
const mergedOptions = { ...DefaultOptions, ...options }
|
|
332
|
-
const hasCustomKeywords = !!(options && options.keywords instanceof Set)
|
|
333
|
-
const isTs = isLikelyTypeScript(code)
|
|
334
|
-
const resolvedKeywords = hasCustomKeywords
|
|
335
|
-
? mergedOptions.keywords
|
|
336
|
-
: (isTs ? Keywords_Ts : Keywords_Js)
|
|
337
|
-
|
|
338
|
-
const {
|
|
339
|
-
onCommentStart,
|
|
340
|
-
onCommentEnd,
|
|
341
|
-
} = mergedOptions
|
|
342
|
-
|
|
343
|
-
const resolvedTypeKeywords =
|
|
344
|
-
mergedOptions.typeKeywords instanceof Set ? mergedOptions.typeKeywords : null
|
|
345
|
-
|
|
346
|
-
let current = ''
|
|
347
|
-
let type = -1
|
|
348
|
-
/** @type {[number, string]} */
|
|
349
|
-
let last = [-1, '']
|
|
350
|
-
/** @type {[number, string]} */
|
|
351
|
-
let beforeLast = [-2, '']
|
|
352
|
-
/** @type {Array<[number, string]>} */
|
|
353
|
-
const tokens = []
|
|
354
|
-
|
|
355
|
-
/**
|
|
356
|
-
* TS generics (`Map<string>`) and JSX (`<div>`) share the same `<Name …>` lexical shape. We use
|
|
357
|
-
* one tag-lexer mode (__jsxTag + __jsxStack) for both when we enter it; isTsTypeArgStart and
|
|
358
|
-
* isTypeParameterListStart only decide when *not* to enter (e.g. `foo<T>`, `<T>(x)=>`).
|
|
359
|
-
* __jsxEnter gates that mode so a latched __jsxTag (from `<` in `"a<b"`) does not run the tag
|
|
360
|
-
* lexer until we are in real JSX/TSX surface (not inside strings).
|
|
361
|
-
* @type {boolean}
|
|
362
|
-
*/
|
|
363
|
-
let __jsxEnter = false
|
|
364
|
-
/** @type {0 | 1 | 2} 0 = none; 1 = inside `<open`; 2 = inside `</close` */
|
|
365
|
-
let __jsxTag = 0
|
|
366
|
-
let __jsxExpr = false
|
|
367
|
-
|
|
368
|
-
/** Nested `<open>…</open>` depth (content between tags, including nested elements). */
|
|
369
|
-
let __jsxStack = 0
|
|
370
|
-
|
|
371
|
-
const __jsxChild = () => __jsxEnter && !__jsxExpr && !__jsxTag
|
|
372
|
-
// < __content__ >
|
|
373
|
-
const inJsxTag = () => __jsxTag && !__jsxChild()
|
|
374
|
-
// {'__content__'}
|
|
375
|
-
const inJsxLiterals = () => !__jsxTag && __jsxChild() && !__jsxExpr && __jsxStack > 0
|
|
376
|
-
|
|
377
|
-
/** @type {string | null} */
|
|
378
|
-
let __strQuote = null
|
|
379
|
-
let __strTokenStart = 0
|
|
380
|
-
let __regexQuoteStart = false
|
|
381
|
-
let __strTemplateExprStack = 0
|
|
382
|
-
let __strTemplateQuoteStack = 0
|
|
383
|
-
const inStringQuotes = () => __strQuote !== null
|
|
384
|
-
const inRegexQuotes = () => __regexQuoteStart
|
|
385
|
-
const inStrTemplateLiterals = () => (__strTemplateQuoteStack > __strTemplateExprStack)
|
|
386
|
-
const inStrTemplateExpr = () => __strTemplateQuoteStack > 0 && (__strTemplateQuoteStack === __strTemplateExprStack)
|
|
387
|
-
const inStringContent = () => inStringQuotes() || inStrTemplateLiterals()
|
|
388
|
-
|
|
389
|
-
/**
|
|
390
|
-
*
|
|
391
|
-
* @param {string} token
|
|
392
|
-
* @returns {number}
|
|
393
|
-
*/
|
|
394
|
-
function classify(token) {
|
|
395
|
-
const isLineBreak = token === '\n'
|
|
396
|
-
// First checking if they're attributes values
|
|
397
|
-
if (inJsxTag()) {
|
|
398
|
-
if (inStringQuotes()) {
|
|
399
|
-
return T_STRING
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
const [, lastToken] = last
|
|
403
|
-
if (isIdentifier(token)) {
|
|
404
|
-
// classify jsx open tag
|
|
405
|
-
if ((lastToken === '<' || lastToken === '</'))
|
|
406
|
-
return T_ENTITY
|
|
407
|
-
}
|
|
408
|
-
}
|
|
409
|
-
// Then determine if they're jsx literals
|
|
410
|
-
const isJsxLiterals = inJsxLiterals()
|
|
411
|
-
if (isJsxLiterals) return T_JSX_LITERALS
|
|
412
|
-
|
|
413
|
-
// Determine strings first before other types
|
|
414
|
-
if (inStringQuotes() || inStrTemplateLiterals()) {
|
|
415
|
-
return T_STRING
|
|
416
|
-
} else if (resolvedTypeKeywords && resolvedTypeKeywords.has(token)) {
|
|
417
|
-
return last[1] === '.' ? T_IDENTIFIER : T_CLS_NUMBER
|
|
418
|
-
} else if (resolvedKeywords.has(token)) {
|
|
419
|
-
return last[1] === '.' ? T_IDENTIFIER : T_KEYWORD
|
|
420
|
-
} else if (isLineBreak) {
|
|
421
|
-
return T_BREAK
|
|
422
|
-
} else if (isSpaces(token)) {
|
|
423
|
-
return T_SPACE
|
|
424
|
-
} else if (token.split('').every(isSign)) {
|
|
425
|
-
return T_SIGN
|
|
426
|
-
} else if (isCls(token)) {
|
|
427
|
-
return inJsxTag() ? T_IDENTIFIER : T_CLS_NUMBER
|
|
428
|
-
} else {
|
|
429
|
-
if (isIdentifier(token)) {
|
|
430
|
-
const isLastPropDot = last[1] === '.' && isIdentifier(beforeLast[1])
|
|
431
|
-
|
|
432
|
-
if (!inStringContent() && !isLastPropDot) return T_IDENTIFIER
|
|
433
|
-
if (isLastPropDot) return T_PROPERTY
|
|
434
|
-
}
|
|
435
|
-
return T_STRING
|
|
436
|
-
}
|
|
437
|
-
}
|
|
438
|
-
|
|
439
|
-
/**
|
|
440
|
-
*
|
|
441
|
-
* @param {number | undefined} [type_]
|
|
442
|
-
* @param {string | undefined} [token_]
|
|
443
|
-
*/
|
|
444
|
-
const append = (type_, token_) => {
|
|
445
|
-
if (token_) {
|
|
446
|
-
current = token_
|
|
447
|
-
}
|
|
448
|
-
if (current) {
|
|
449
|
-
type = typeof type_ === 'number' ? type_ : classify(current)
|
|
450
|
-
/** @type [number, string] */
|
|
451
|
-
const pair = [type, current]
|
|
452
|
-
if (type !== T_SPACE && type !== T_BREAK) {
|
|
453
|
-
beforeLast = last
|
|
454
|
-
last = pair
|
|
455
|
-
}
|
|
456
|
-
tokens.push(pair)
|
|
457
|
-
}
|
|
458
|
-
current = ''
|
|
459
|
-
}
|
|
460
|
-
for (let i = 0; i < code.length; i++) {
|
|
461
|
-
const curr = code[i]
|
|
462
|
-
const prev = code[i - 1]
|
|
463
|
-
const next = code[i + 1]
|
|
464
|
-
const p_c = prev + curr // previous and current
|
|
465
|
-
const c_n = curr + next // current and next
|
|
466
|
-
|
|
467
|
-
// onQuote(curr, i, code): length from i; end = i + len (capped).
|
|
468
|
-
if (
|
|
469
|
-
typeof mergedOptions.onQuote === 'function' &&
|
|
470
|
-
curr === "'" &&
|
|
471
|
-
!inStringQuotes() &&
|
|
472
|
-
!inJsxLiterals() &&
|
|
473
|
-
!inStrTemplateLiterals()
|
|
474
|
-
) {
|
|
475
|
-
const rawLen = mergedOptions.onQuote(curr, i, code)
|
|
476
|
-
if (
|
|
477
|
-
typeof rawLen === 'number' &&
|
|
478
|
-
rawLen >= 1 &&
|
|
479
|
-
!Number.isNaN(rawLen)
|
|
480
|
-
) {
|
|
481
|
-
const len = Math.min(rawLen, code.length - i)
|
|
482
|
-
const end = i + len
|
|
483
|
-
append()
|
|
484
|
-
current = code.slice(i, end)
|
|
485
|
-
append(T_IDENTIFIER)
|
|
486
|
-
i = end - 1
|
|
487
|
-
continue
|
|
488
|
-
}
|
|
489
|
-
}
|
|
490
|
-
|
|
491
|
-
// Determine string quotation outside of jsx literals and template literals.
|
|
492
|
-
// Inside jsx literals or template literals, string quotation is still part of it.
|
|
493
|
-
if (isSingleQuotes(curr) && !inJsxLiterals() && !inStrTemplateLiterals()) {
|
|
494
|
-
append()
|
|
495
|
-
let isStringClose = false
|
|
496
|
-
if (prev !== `\\`) {
|
|
497
|
-
if (__strQuote && curr === __strQuote) {
|
|
498
|
-
__strQuote = null
|
|
499
|
-
isStringClose = true
|
|
500
|
-
} else if (!__strQuote) {
|
|
501
|
-
__strQuote = curr
|
|
502
|
-
__strTokenStart = tokens.length
|
|
503
|
-
}
|
|
504
|
-
}
|
|
505
|
-
|
|
506
|
-
append(T_STRING, curr)
|
|
507
|
-
if (mergedOptions.quotedKeys && isStringClose && isPropertyKey(code, i)) {
|
|
508
|
-
for (let tokenIndex = __strTokenStart; tokenIndex < tokens.length; tokenIndex++) {
|
|
509
|
-
tokens[tokenIndex][0] = T_PROPERTY
|
|
510
|
-
}
|
|
511
|
-
}
|
|
512
|
-
continue
|
|
513
|
-
}
|
|
514
|
-
|
|
515
|
-
if (!inStrTemplateLiterals()) {
|
|
516
|
-
if (prev !== '\\n' && isStrTemplateChr(curr)) {
|
|
517
|
-
append()
|
|
518
|
-
append(T_STRING, curr)
|
|
519
|
-
__strTemplateQuoteStack++
|
|
520
|
-
continue
|
|
521
|
-
}
|
|
522
|
-
}
|
|
523
|
-
|
|
524
|
-
if (inStrTemplateLiterals()) {
|
|
525
|
-
if (prev !== '\\n' && isStrTemplateChr(curr)) {
|
|
526
|
-
if (__strTemplateQuoteStack > 0) {
|
|
527
|
-
append()
|
|
528
|
-
__strTemplateQuoteStack--
|
|
529
|
-
append(T_STRING, curr)
|
|
530
|
-
continue
|
|
531
|
-
}
|
|
532
|
-
}
|
|
533
|
-
|
|
534
|
-
if (c_n === '${') {
|
|
535
|
-
__strTemplateExprStack++
|
|
536
|
-
append(T_STRING)
|
|
537
|
-
append(T_SIGN, c_n)
|
|
538
|
-
i++
|
|
539
|
-
continue
|
|
540
|
-
}
|
|
541
|
-
}
|
|
542
|
-
|
|
543
|
-
if (inStrTemplateExpr() && curr === '}') {
|
|
544
|
-
append()
|
|
545
|
-
__strTemplateExprStack--
|
|
546
|
-
append(T_SIGN, curr)
|
|
547
|
-
continue
|
|
548
|
-
}
|
|
549
|
-
|
|
550
|
-
if (__jsxChild()) {
|
|
551
|
-
if (curr === '{') {
|
|
552
|
-
append()
|
|
553
|
-
append(T_SIGN, curr)
|
|
554
|
-
__jsxExpr = true
|
|
555
|
-
continue
|
|
556
|
-
}
|
|
557
|
-
}
|
|
558
|
-
|
|
559
|
-
if (__jsxEnter) {
|
|
560
|
-
// <: open tag sign
|
|
561
|
-
// new '<' not inside jsx
|
|
562
|
-
if (!__jsxTag && curr === '<') {
|
|
563
|
-
append()
|
|
564
|
-
if (next === '/') {
|
|
565
|
-
// close tag
|
|
566
|
-
__jsxTag = 2
|
|
567
|
-
current = c_n
|
|
568
|
-
i++
|
|
569
|
-
} else {
|
|
570
|
-
// open tag
|
|
571
|
-
__jsxTag = 1
|
|
572
|
-
current = curr
|
|
573
|
-
}
|
|
574
|
-
append(T_SIGN)
|
|
575
|
-
continue
|
|
576
|
-
}
|
|
577
|
-
if (__jsxTag) {
|
|
578
|
-
// >: open tag close sign or closing tag closing sign
|
|
579
|
-
// and it's not `=>` or `/>`
|
|
580
|
-
// `curr` could be `>` or `/`
|
|
581
|
-
if ((curr === '>' && !'/='.includes(prev))) {
|
|
582
|
-
append()
|
|
583
|
-
if (__jsxTag === 1) {
|
|
584
|
-
__jsxTag = 0
|
|
585
|
-
__jsxStack++
|
|
586
|
-
} else {
|
|
587
|
-
__jsxTag = 0
|
|
588
|
-
__jsxEnter = false
|
|
589
|
-
}
|
|
590
|
-
append(T_SIGN, curr)
|
|
591
|
-
continue
|
|
592
|
-
}
|
|
593
|
-
|
|
594
|
-
// >: tag self close sign or close tag sign
|
|
595
|
-
if (c_n === '/>' || c_n === '</') {
|
|
596
|
-
// if current token is not part of close tag sign, push it first
|
|
597
|
-
if (current !== '<' && current !== '/') {
|
|
598
|
-
append()
|
|
599
|
-
}
|
|
600
|
-
|
|
601
|
-
if (c_n === '/>') {
|
|
602
|
-
__jsxTag = 0
|
|
603
|
-
} else {
|
|
604
|
-
// is '</'
|
|
605
|
-
__jsxStack--
|
|
606
|
-
}
|
|
607
|
-
|
|
608
|
-
if (!__jsxStack)
|
|
609
|
-
__jsxEnter = false
|
|
610
|
-
|
|
611
|
-
current = c_n
|
|
612
|
-
i++
|
|
613
|
-
append(T_SIGN)
|
|
614
|
-
continue
|
|
615
|
-
}
|
|
616
|
-
|
|
617
|
-
// <: open tag sign
|
|
618
|
-
if (curr === '<') {
|
|
619
|
-
append()
|
|
620
|
-
current = curr
|
|
621
|
-
append(T_SIGN)
|
|
622
|
-
continue
|
|
623
|
-
}
|
|
624
|
-
|
|
625
|
-
// jsx property
|
|
626
|
-
// `-` in data-prop
|
|
627
|
-
if (next === '-' && !inStringContent() && !inJsxLiterals()) {
|
|
628
|
-
if (current) {
|
|
629
|
-
append(T_PROPERTY, current + curr + next)
|
|
630
|
-
i++
|
|
631
|
-
continue
|
|
632
|
-
}
|
|
633
|
-
}
|
|
634
|
-
// `=` in property=<value>
|
|
635
|
-
if (next === '=' && !inStringContent()) {
|
|
636
|
-
// if current is not a space, ensure `prop` is a property
|
|
637
|
-
if (!isSpaces(curr)) {
|
|
638
|
-
// If there're leading spaces, append them first
|
|
639
|
-
if (isSpaces(current)) {
|
|
640
|
-
append()
|
|
641
|
-
}
|
|
642
|
-
|
|
643
|
-
// Now check if the accumulated token is a property
|
|
644
|
-
const prop = current + curr
|
|
645
|
-
if (isIdentifier(prop)) {
|
|
646
|
-
append(T_PROPERTY, prop)
|
|
647
|
-
continue
|
|
648
|
-
}
|
|
649
|
-
}
|
|
650
|
-
}
|
|
651
|
-
}
|
|
652
|
-
}
|
|
653
|
-
|
|
654
|
-
// if it's not in a jsx tag declaration or a string, close child if next is jsx close tag
|
|
655
|
-
if (!__jsxTag && (curr === '<' && isIdentifierChar(next) || c_n === '</')) {
|
|
656
|
-
let prevNonSpace = i - 1
|
|
657
|
-
while (prevNonSpace >= 0 && /\s/.test(code[prevNonSpace])) prevNonSpace--
|
|
658
|
-
const prevChar = prevNonSpace >= 0 ? code[prevNonSpace] : ''
|
|
659
|
-
|
|
660
|
-
const [lastType, lastTok] = last
|
|
661
|
-
// Without a space before `<`, the LHS is often still in `current` (not flushed), so `last` is stale.
|
|
662
|
-
let typeArgFromPending = false
|
|
663
|
-
let jsxFromPending = false
|
|
664
|
-
if (current && !isSpaces(current)) {
|
|
665
|
-
const w = current
|
|
666
|
-
// Unflushed LHS before `<`: numbers/null/Upper (isCls), and booleans, behave like `foo<` (type
|
|
667
|
-
// args / `<`). Any other keyword (`return`, `void`, …) is JSX-friendly — no keyword allowlist.
|
|
668
|
-
if (isCls(w) || w === 'true' || w === 'false') {
|
|
669
|
-
typeArgFromPending = true
|
|
670
|
-
} else if (resolvedKeywords.has(w) && isIdentifier(w)) {
|
|
671
|
-
jsxFromPending = true
|
|
672
|
-
} else if (isIdentifier(w)) {
|
|
673
|
-
typeArgFromPending = true
|
|
674
|
-
}
|
|
675
|
-
}
|
|
676
|
-
|
|
677
|
-
const isTsTypeArgStart =
|
|
678
|
-
curr === '<' &&
|
|
679
|
-
/[$\w\]\)]/.test(prevChar) &&
|
|
680
|
-
(typeArgFromPending ||
|
|
681
|
-
(!jsxFromPending &&
|
|
682
|
-
(lastType === T_IDENTIFIER ||
|
|
683
|
-
lastType === T_CLS_NUMBER ||
|
|
684
|
-
(lastType === T_SIGN && (lastTok === ')' || lastTok === ']')))))
|
|
685
|
-
const isTsGenericStart = curr === '<' && isTypeParameterListStart(code, i)
|
|
686
|
-
if (!isTsTypeArgStart && !isTsGenericStart) {
|
|
687
|
-
__jsxTag = next === '/' ? 2 : 1
|
|
688
|
-
}
|
|
689
|
-
|
|
690
|
-
if (curr === '<' && (next === '/' || isAlpha(next))) {
|
|
691
|
-
if (
|
|
692
|
-
!isTsTypeArgStart &&
|
|
693
|
-
!isTsGenericStart &&
|
|
694
|
-
!inStringContent() &&
|
|
695
|
-
!inJsxLiterals() &&
|
|
696
|
-
!inRegexQuotes()
|
|
697
|
-
) {
|
|
698
|
-
__jsxEnter = true
|
|
699
|
-
}
|
|
700
|
-
}
|
|
701
|
-
}
|
|
702
|
-
|
|
703
|
-
const isQuotationChar = isStringQuotation(curr)
|
|
704
|
-
const isStringTemplateLiterals = inStrTemplateLiterals()
|
|
705
|
-
const isRegexChar = !__jsxEnter && isRegexStart(c_n)
|
|
706
|
-
const isJsxLiterals = inJsxLiterals()
|
|
707
|
-
|
|
708
|
-
// string quotation
|
|
709
|
-
if (isQuotationChar || isStringTemplateLiterals || isSingleQuotes(__strQuote)) {
|
|
710
|
-
current += curr
|
|
711
|
-
} else if (isRegexChar) {
|
|
712
|
-
append()
|
|
713
|
-
const [lastType, lastToken] = last
|
|
714
|
-
// Special cases that are not considered as regex:
|
|
715
|
-
// * (expr1) / expr2: `)` before `/` operator is still in expression
|
|
716
|
-
// * <non comment start>/ expr: non comment start before `/` is not regex
|
|
717
|
-
if (
|
|
718
|
-
isRegexChar &&
|
|
719
|
-
lastType !== -1 &&
|
|
720
|
-
!(
|
|
721
|
-
(lastType === T_SIGN && ')' !== lastToken) ||
|
|
722
|
-
lastType === T_COMMENT
|
|
723
|
-
)
|
|
724
|
-
) {
|
|
725
|
-
current = curr
|
|
726
|
-
append()
|
|
727
|
-
continue
|
|
728
|
-
}
|
|
729
|
-
|
|
730
|
-
__regexQuoteStart = true
|
|
731
|
-
const start = i++
|
|
732
|
-
|
|
733
|
-
// end of line of end of file
|
|
734
|
-
const isEof = () => i >= code.length
|
|
735
|
-
const isEol = () => isEof() || code[i] === '\n'
|
|
736
|
-
|
|
737
|
-
let foundClose = false
|
|
738
|
-
|
|
739
|
-
// `/` is literal inside regex character classes, e.g. `[/]`.
|
|
740
|
-
let inCharClass = false
|
|
741
|
-
|
|
742
|
-
// traverse to find closing regex slash
|
|
743
|
-
for (; !isEol(); i++) {
|
|
744
|
-
const ch = code[i]
|
|
745
|
-
const escaped = code[i - 1] === '\\'
|
|
746
|
-
if (!escaped && ch === '[') inCharClass = true
|
|
747
|
-
if (!escaped && ch === ']') inCharClass = false
|
|
748
|
-
if (ch === '/' && !inCharClass && !escaped) {
|
|
749
|
-
foundClose = true
|
|
750
|
-
// end of regex, append regex flags
|
|
751
|
-
while (start !== i && /^[a-z]$/.test(code[i + 1]) && !isEol()) {
|
|
752
|
-
i++
|
|
753
|
-
}
|
|
754
|
-
break
|
|
755
|
-
}
|
|
756
|
-
}
|
|
757
|
-
__regexQuoteStart = false
|
|
758
|
-
|
|
759
|
-
if (start !== i && foundClose) {
|
|
760
|
-
// If current line is fully closed with string quotes or regex slashes,
|
|
761
|
-
// add them to tokens
|
|
762
|
-
current = code.slice(start, i + 1)
|
|
763
|
-
append(T_STRING)
|
|
764
|
-
} else {
|
|
765
|
-
// If it doesn't match any of the above, just leave it as operator and move on
|
|
766
|
-
current = curr
|
|
767
|
-
append()
|
|
768
|
-
i = start
|
|
769
|
-
}
|
|
770
|
-
} else if (onCommentStart(curr, next)) {
|
|
771
|
-
append()
|
|
772
|
-
const start = i
|
|
773
|
-
const startCommentType = onCommentStart(curr, next)
|
|
774
|
-
|
|
775
|
-
// just match the comment, commentType === true
|
|
776
|
-
// inline comment, commentType === 1
|
|
777
|
-
// block comment, commentType === 2
|
|
778
|
-
if (startCommentType) {
|
|
779
|
-
for (; i < code.length; i++) {
|
|
780
|
-
const endCommentType = onCommentEnd(code[i - 1], code[i])
|
|
781
|
-
if (endCommentType == startCommentType) break
|
|
782
|
-
}
|
|
783
|
-
}
|
|
784
|
-
current = code.slice(start, i + 1)
|
|
785
|
-
append(T_COMMENT)
|
|
786
|
-
} else if (curr === ' ' || curr === '\n') {
|
|
787
|
-
if (
|
|
788
|
-
curr === ' ' &&
|
|
789
|
-
(
|
|
790
|
-
(isSpaces(current) || !current) ||
|
|
791
|
-
isJsxLiterals
|
|
792
|
-
)
|
|
793
|
-
) {
|
|
794
|
-
let end = i + 1
|
|
795
|
-
while (code[end] === ' ') end++
|
|
796
|
-
current += code.slice(i, end)
|
|
797
|
-
i = end - 1
|
|
798
|
-
if (code[end] === '<') {
|
|
799
|
-
append()
|
|
800
|
-
}
|
|
801
|
-
} else {
|
|
802
|
-
append()
|
|
803
|
-
current = curr
|
|
804
|
-
append()
|
|
805
|
-
}
|
|
806
|
-
} else {
|
|
807
|
-
if (__jsxExpr && curr === '}') {
|
|
808
|
-
append()
|
|
809
|
-
current = curr
|
|
810
|
-
append()
|
|
811
|
-
__jsxExpr = false
|
|
812
|
-
} else if (
|
|
813
|
-
// it's jsx literals and is not a jsx bracket
|
|
814
|
-
(isJsxLiterals && !JSXBrackets.has(curr)) ||
|
|
815
|
-
// it's template literal content (including quotes)
|
|
816
|
-
inStrTemplateLiterals() ||
|
|
817
|
-
// same type char as previous one in current token
|
|
818
|
-
((isWord(curr) === isWord(current[current.length - 1]) || __jsxChild()) && !Signs.has(curr))
|
|
819
|
-
) {
|
|
820
|
-
current += curr
|
|
821
|
-
} else {
|
|
822
|
-
if (p_c === '</') {
|
|
823
|
-
current = p_c
|
|
824
|
-
}
|
|
825
|
-
append()
|
|
826
|
-
|
|
827
|
-
if (p_c !== '</') {
|
|
828
|
-
current = curr
|
|
829
|
-
|
|
830
|
-
}
|
|
831
|
-
if ((c_n === '</' || c_n === '/>')) {
|
|
832
|
-
current = c_n
|
|
833
|
-
append()
|
|
834
|
-
i++
|
|
835
|
-
}
|
|
836
|
-
else if (JSXBrackets.has(curr)) append()
|
|
837
|
-
}
|
|
838
|
-
}
|
|
839
|
-
}
|
|
840
|
-
|
|
841
|
-
append()
|
|
842
|
-
|
|
843
|
-
return tokens
|
|
9
|
+
/** @param {string | undefined} name */
|
|
10
|
+
function configFor(name) {
|
|
11
|
+
return languages.find(({ id }) => id === (name || 'javascript'))?.config
|
|
844
12
|
}
|
|
845
13
|
|
|
846
|
-
/**
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
* @return {Array<{type: string, tagName: string, children: any[], properties: Record<string, string>}>}
|
|
852
|
-
*/
|
|
853
|
-
function generate(tokens, options) {
|
|
854
|
-
const lines = []
|
|
855
|
-
const lineClassName = options && typeof options.lineClassName === 'function'
|
|
856
|
-
? options.lineClassName
|
|
857
|
-
: null
|
|
858
|
-
let lineIndex = 0
|
|
859
|
-
/**
|
|
860
|
-
* @param {any} children
|
|
861
|
-
* @param {string} text
|
|
862
|
-
* @return {{type: string, tagName: string, children: any[], properties: Record<string, string>}}
|
|
863
|
-
*/
|
|
864
|
-
const createLine = (children, text) => {
|
|
865
|
-
const extraClassName = lineClassName ? lineClassName(text, lineIndex) : ''
|
|
866
|
-
lineIndex++
|
|
867
|
-
|
|
868
|
-
return ({
|
|
869
|
-
type: 'element',
|
|
870
|
-
tagName: 'span',
|
|
871
|
-
children,
|
|
872
|
-
properties: {
|
|
873
|
-
className: extraClassName ? `sh__line ${extraClassName}` : 'sh__line',
|
|
874
|
-
},
|
|
875
|
-
})
|
|
876
|
-
}
|
|
877
|
-
|
|
878
|
-
/**
|
|
879
|
-
* @param {Array<[number, string]>} tokens
|
|
880
|
-
* @returns {void}
|
|
881
|
-
*/
|
|
882
|
-
function flushLine(tokens) {
|
|
883
|
-
const lineText = tokens.map(([, value]) => value).join('')
|
|
884
|
-
/** @type {Array<any>} */
|
|
885
|
-
const lineTokens = (
|
|
886
|
-
tokens
|
|
887
|
-
.map(([type, value]) => {
|
|
888
|
-
const tokenType = TokenTypes[type]
|
|
889
|
-
return {
|
|
890
|
-
type: 'element',
|
|
891
|
-
tagName: 'span',
|
|
892
|
-
children: [{
|
|
893
|
-
type: 'text', // text node
|
|
894
|
-
value, // to encode
|
|
895
|
-
}],
|
|
896
|
-
properties: {
|
|
897
|
-
className: `sh__token--${tokenType}`,
|
|
898
|
-
style: { color: `var(--sh-${tokenType})` },
|
|
899
|
-
},
|
|
900
|
-
}
|
|
901
|
-
})
|
|
902
|
-
)
|
|
903
|
-
lines.push(createLine(lineTokens, lineText))
|
|
904
|
-
}
|
|
905
|
-
/** @type {Array<[number, string]>} */
|
|
906
|
-
const lineTokens = []
|
|
907
|
-
let lastWasBreak = false
|
|
908
|
-
|
|
909
|
-
for (let i = 0; i < tokens.length; i++) {
|
|
910
|
-
const token = tokens[i]
|
|
911
|
-
const [type, value] = token
|
|
912
|
-
const isLastToken = i === tokens.length - 1
|
|
913
|
-
|
|
914
|
-
if (type !== T_BREAK) {
|
|
915
|
-
// Divide multi-line token into multi-line code
|
|
916
|
-
if (value.includes('\n')) {
|
|
917
|
-
const lines = value.split('\n')
|
|
918
|
-
for (let j = 0; j < lines.length; j++) {
|
|
919
|
-
lineTokens.push([type, lines[j]])
|
|
920
|
-
if (j < lines.length - 1) {
|
|
921
|
-
flushLine(lineTokens)
|
|
922
|
-
lineTokens.length = 0
|
|
923
|
-
}
|
|
924
|
-
}
|
|
925
|
-
} else {
|
|
926
|
-
lineTokens.push(token)
|
|
927
|
-
}
|
|
928
|
-
lastWasBreak = false
|
|
929
|
-
} else {
|
|
930
|
-
if (lastWasBreak) {
|
|
931
|
-
// Consecutive break - create empty line
|
|
932
|
-
flushLine([])
|
|
933
|
-
} else {
|
|
934
|
-
// First break after content - flush current line
|
|
935
|
-
flushLine(lineTokens)
|
|
936
|
-
lineTokens.length = 0
|
|
937
|
-
}
|
|
938
|
-
|
|
939
|
-
// If this is the last token and it's a break, create an empty line
|
|
940
|
-
if (isLastToken) {
|
|
941
|
-
flushLine([])
|
|
942
|
-
}
|
|
943
|
-
|
|
944
|
-
lastWasBreak = true
|
|
945
|
-
}
|
|
946
|
-
}
|
|
947
|
-
|
|
948
|
-
// Flush remaining tokens if any
|
|
949
|
-
if (lineTokens.length) {
|
|
950
|
-
flushLine(lineTokens)
|
|
951
|
-
}
|
|
952
|
-
|
|
953
|
-
return lines
|
|
14
|
+
/** @param {string} code @param {HighlightOptions | undefined} options */
|
|
15
|
+
function highlight(code, options) {
|
|
16
|
+
const { lang, cx, mark, markLine } = options || {}
|
|
17
|
+
const parsed = parse(code, configFor(lang))
|
|
18
|
+
return render(parsed, { cx, mark, markLine })
|
|
954
19
|
}
|
|
955
20
|
|
|
956
|
-
|
|
957
|
-
const propsToString = (props) => {
|
|
958
|
-
let str = `class="${props.className}"`
|
|
959
|
-
|
|
960
|
-
if (props.style) {
|
|
961
|
-
const style = Object.entries(props.style)
|
|
962
|
-
.map(([key, value]) => `${key}:${value}`)
|
|
963
|
-
.join(';')
|
|
964
|
-
str += ` style="${style}"`
|
|
965
|
-
}
|
|
966
|
-
return str
|
|
967
|
-
}
|
|
968
|
-
|
|
969
|
-
function toHtml(lines) {
|
|
970
|
-
return lines
|
|
971
|
-
.map(line => {
|
|
972
|
-
const { tagName: lineTag } = line
|
|
973
|
-
const tokens = line.children
|
|
974
|
-
.map(child => {
|
|
975
|
-
const { tagName, children, properties } = child
|
|
976
|
-
return `<${tagName} ${propsToString(properties)}>${encode(children[0].value)}</${tagName}>`
|
|
977
|
-
})
|
|
978
|
-
.join('')
|
|
979
|
-
return `<${lineTag} class="${line.properties.className}">${tokens}</${lineTag}>`
|
|
980
|
-
})
|
|
981
|
-
.join('\n')
|
|
982
|
-
}
|
|
21
|
+
export { highlight }
|
|
983
22
|
|
|
984
23
|
/**
|
|
985
|
-
*
|
|
986
|
-
* @param {string} code
|
|
987
|
-
* @param {{
|
|
988
|
-
* keywords?: Set<string>
|
|
989
|
-
* typeKeywords?: Set<string>
|
|
990
|
-
* onCommentStart?: (curr: string, next: string) => number | boolean
|
|
991
|
-
* onCommentEnd?: (curr: string, prev: string) => number | boolean
|
|
992
|
-
* onQuote?: (curr: string, i: number, code: string) => number | null | undefined
|
|
993
|
-
* quotedKeys?: boolean
|
|
994
|
-
* lineClassName?: (line: string, index: number) => string | null | undefined
|
|
995
|
-
* } | undefined} options
|
|
996
|
-
* `onQuote` same as `tokenize`.
|
|
997
|
-
* @returns {string}
|
|
24
|
+
* @typedef {import('./core.js').DisplayOptions & { lang?: string }} HighlightOptions
|
|
998
25
|
*/
|
|
999
|
-
function highlight(code, options) {
|
|
1000
|
-
const tokens = tokenize(code, options)
|
|
1001
|
-
const lines = generate(tokens, options)
|
|
1002
|
-
const output = toHtml(lines)
|
|
1003
|
-
return output
|
|
1004
|
-
}
|
|
1005
|
-
|
|
1006
|
-
// namespace
|
|
1007
|
-
const SugarHigh = /** @type {const} */ {
|
|
1008
|
-
TokenTypes,
|
|
1009
|
-
TokenMap: new Map(TokenTypes.map((type, i) => [type, i])),
|
|
1010
|
-
}
|
|
1011
|
-
|
|
1012
|
-
export {
|
|
1013
|
-
highlight,
|
|
1014
|
-
tokenize,
|
|
1015
|
-
generate,
|
|
1016
|
-
SugarHigh,
|
|
1017
|
-
}
|