reslop 0.1.4 → 0.1.6

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.
@@ -0,0 +1,90 @@
1
+ 'use strict';
2
+
3
+ const { SQUOTE, emit, skipQuoted } = require('./core.js');
4
+
5
+ const langs = ['html'];
6
+
7
+ const aliases = { htm: 'html' };
8
+
9
+ const colorHtmlTag = (tag) => {
10
+ const out = [];
11
+ let i = 1;
12
+ const n = tag.length;
13
+ emit(out, 'punct', '<');
14
+ if (tag[i] === '/') {
15
+ emit(out, 'punct', '/');
16
+ i += 1;
17
+ }
18
+ let j = i;
19
+ while (j < n && /[A-Za-z0-9:-]/.test(tag[j])) j += 1;
20
+ emit(out, 'tag', tag.slice(i, j));
21
+ i = j;
22
+ while (i < n) {
23
+ if (tag[i] === '>' || (tag[i] === '/' && tag[i + 1] === '>')) {
24
+ emit(out, 'punct', tag.slice(i));
25
+ break;
26
+ }
27
+ if (/\s/.test(tag[i])) {
28
+ emit(out, 'plain', tag[i]);
29
+ i += 1;
30
+ continue;
31
+ }
32
+ if (tag[i] === '"' || tag[i] === SQUOTE) {
33
+ const q = tag[i];
34
+ let k = i + 1;
35
+ while (k < n && tag[k] !== q) k += 1;
36
+ k = Math.min(k + 1, n);
37
+ emit(out, 'string', tag.slice(i, k));
38
+ i = k;
39
+ continue;
40
+ }
41
+ if (/[A-Za-z_:]/.test(tag[i])) {
42
+ let k = i + 1;
43
+ while (k < n && /[A-Za-z0-9_:.-]/.test(tag[k])) k += 1;
44
+ emit(out, 'attr', tag.slice(i, k));
45
+ i = k;
46
+ continue;
47
+ }
48
+ emit(out, 'punct', tag[i]);
49
+ i += 1;
50
+ }
51
+ return out;
52
+ };
53
+
54
+ const highlight = (source) => {
55
+ const out = [];
56
+ let i = 0;
57
+ const n = source.length;
58
+ while (i < n) {
59
+ if (source.startsWith('<!--', i)) {
60
+ let j = source.indexOf('-->', i + 4);
61
+ j = j === -1 ? n : j + 3;
62
+ emit(out, 'comment', source.slice(i, j));
63
+ i = j;
64
+ continue;
65
+ }
66
+ if (source[i] === '<') {
67
+ let j = i + 1;
68
+ while (j < n && source[j] !== '>') {
69
+ if (source[j] === '"' || source[j] === SQUOTE) {
70
+ j = skipQuoted(source, j);
71
+ continue;
72
+ }
73
+ j += 1;
74
+ }
75
+ if (j < n) j += 1;
76
+ for (const t of colorHtmlTag(source.slice(i, j))) out.push(t);
77
+ i = j;
78
+ continue;
79
+ }
80
+ let j = i + 1;
81
+ while (j < n && source[j] !== '<' && !source.startsWith('<!--', j)) {
82
+ j += 1;
83
+ }
84
+ emit(out, 'plain', source.slice(i, j));
85
+ i = j;
86
+ }
87
+ return out;
88
+ };
89
+
90
+ module.exports = { langs, aliases, highlight };
@@ -0,0 +1,571 @@
1
+ 'use strict';
2
+
3
+ const hl = require('./core.js');
4
+ const {
5
+ QUOTES,
6
+ EXPECT_STYLE,
7
+ emit,
8
+ tokensText,
9
+ wordStyle,
10
+ peekIdent,
11
+ skipWs,
12
+ readUntilNl,
13
+ readBlockComment,
14
+ readJsNumber,
15
+ readJsOperator,
16
+ readString,
17
+ readTemplateInterp,
18
+ styleConstant,
19
+ stylePascal,
20
+ styleCall,
21
+ } = hl;
22
+
23
+ const langs = ['js', 'mjs', 'ts', 'jsx', 'tsx'];
24
+
25
+ const aliases = {
26
+ javascript: 'js',
27
+ typescript: 'ts',
28
+ cjs: 'js',
29
+ dts: 'ts',
30
+ };
31
+
32
+ const OPTIONS = {
33
+ js: {},
34
+ mjs: {},
35
+ ts: { ts: true },
36
+ jsx: { jsx: true },
37
+ tsx: { ts: true, jsx: true },
38
+ };
39
+
40
+ const STORAGE = [
41
+ 'const',
42
+ 'let',
43
+ 'var',
44
+ 'function',
45
+ 'class',
46
+ 'extends',
47
+ 'static',
48
+ 'async',
49
+ 'get',
50
+ 'set',
51
+ 'constructor',
52
+ 'new',
53
+ 'typeof',
54
+ 'instanceof',
55
+ 'void',
56
+ 'delete',
57
+ 'yield',
58
+ 'await',
59
+ 'import',
60
+ 'export',
61
+ 'from',
62
+ 'as',
63
+ 'default',
64
+ 'type',
65
+ 'interface',
66
+ 'implements',
67
+ 'enum',
68
+ 'namespace',
69
+ 'module',
70
+ 'declare',
71
+ 'abstract',
72
+ 'readonly',
73
+ 'private',
74
+ 'public',
75
+ 'protected',
76
+ 'override',
77
+ 'satisfies',
78
+ ];
79
+
80
+ const CONTROL = [
81
+ 'if',
82
+ 'else',
83
+ 'for',
84
+ 'while',
85
+ 'do',
86
+ 'switch',
87
+ 'case',
88
+ 'break',
89
+ 'continue',
90
+ 'return',
91
+ 'throw',
92
+ 'try',
93
+ 'catch',
94
+ 'finally',
95
+ 'with',
96
+ 'of',
97
+ 'in',
98
+ ];
99
+
100
+ const LITERALS = [
101
+ 'true',
102
+ 'false',
103
+ 'null',
104
+ 'undefined',
105
+ 'NaN',
106
+ 'Infinity',
107
+ 'this',
108
+ 'super',
109
+ ];
110
+
111
+ const TYPES = [
112
+ 'string',
113
+ 'number',
114
+ 'boolean',
115
+ 'symbol',
116
+ 'bigint',
117
+ 'object',
118
+ 'any',
119
+ 'unknown',
120
+ 'never',
121
+ 'void',
122
+ 'keyof',
123
+ 'infer',
124
+ 'unique',
125
+ 'asserts',
126
+ 'is',
127
+ ];
128
+
129
+ const BUILTINS = [
130
+ 'console',
131
+ 'Math',
132
+ 'JSON',
133
+ 'Date',
134
+ 'Array',
135
+ 'Object',
136
+ 'String',
137
+ 'Number',
138
+ 'Boolean',
139
+ 'Map',
140
+ 'Set',
141
+ 'WeakMap',
142
+ 'WeakSet',
143
+ 'Promise',
144
+ 'Error',
145
+ 'RegExp',
146
+ 'Symbol',
147
+ 'Proxy',
148
+ 'Reflect',
149
+ 'Intl',
150
+ 'Buffer',
151
+ 'process',
152
+ 'module',
153
+ 'exports',
154
+ 'require',
155
+ 'global',
156
+ 'globalThis',
157
+ 'window',
158
+ 'document',
159
+ 'parseInt',
160
+ 'parseFloat',
161
+ 'isNaN',
162
+ 'isFinite',
163
+ 'encodeURI',
164
+ 'decodeURI',
165
+ 'encodeURIComponent',
166
+ 'decodeURIComponent',
167
+ 'setTimeout',
168
+ 'setInterval',
169
+ 'clearTimeout',
170
+ 'clearInterval',
171
+ 'fetch',
172
+ 'URL',
173
+ 'URLSearchParams',
174
+ ];
175
+
176
+ const NEXT_EXPECT = {
177
+ function: 'function',
178
+ class: 'class',
179
+ interface: 'class',
180
+ enum: 'class',
181
+ type: 'type',
182
+ };
183
+
184
+ const typeOverlay = TYPES.filter((word) => STORAGE.includes(word));
185
+
186
+ const WORD_STYLE = {
187
+ ...wordStyle(CONTROL, 'control'),
188
+ ...wordStyle(LITERALS, 'literal'),
189
+ ...wordStyle(BUILTINS, 'className'),
190
+ ...wordStyle(STORAGE, 'storage'),
191
+ ...wordStyle(typeOverlay, 'type'),
192
+ };
193
+
194
+ const TS_WORD_STYLE = {
195
+ ...WORD_STYLE,
196
+ ...wordStyle(TYPES, 'type'),
197
+ };
198
+
199
+ const plainTail = (out) => tokensText(out).slice(-8);
200
+
201
+ const styleExpected = ({ expectName }) => EXPECT_STYLE[expectName];
202
+
203
+ const styleKeyword = ({ word, ts }) => {
204
+ const table = ts ? TS_WORD_STYLE : WORD_STYLE;
205
+ if (!Object.hasOwn(table, word)) return null;
206
+ return table[word];
207
+ };
208
+
209
+ const styleAssign = ({ after, source, k }) => {
210
+ if (after !== '=') return null;
211
+ const k2 = skipWs(source, k + 1);
212
+ const rhs = source.slice(k2, k2 + 8);
213
+ if (rhs.startsWith('async')) return 'function';
214
+ if (rhs.startsWith('function')) return 'function';
215
+ if (source[k2] === '(') return 'function';
216
+ return 'variable';
217
+ };
218
+
219
+ const styleProperty = ({ out }) => {
220
+ const prev = plainTail(out);
221
+ return /\.$/.test(prev) ? 'property' : null;
222
+ };
223
+
224
+ const IDENT_RULES = [
225
+ styleExpected,
226
+ styleKeyword,
227
+ styleConstant,
228
+ stylePascal,
229
+ styleCall,
230
+ styleAssign,
231
+ styleProperty,
232
+ ];
233
+
234
+ const identStyle = (ctx) => {
235
+ for (const rule of IDENT_RULES) {
236
+ const style = rule(ctx);
237
+ if (style) return style;
238
+ }
239
+ return 'variable';
240
+ };
241
+
242
+ const readRegex = (source, i) => {
243
+ let j = i + 1;
244
+ const n = source.length;
245
+ while (j < n) {
246
+ if (source[j] === '\\') {
247
+ j += 2;
248
+ continue;
249
+ }
250
+ if (source[j] === '[') {
251
+ j += 1;
252
+ while (j < n && source[j] !== ']') {
253
+ if (source[j] === '\\') j += 1;
254
+ j += 1;
255
+ }
256
+ j += 1;
257
+ continue;
258
+ }
259
+ if (source[j] === '/') {
260
+ j += 1;
261
+ while (j < n && /[a-z]/i.test(source[j])) j += 1;
262
+ break;
263
+ }
264
+ if (source[j] === '\n') break;
265
+ j += 1;
266
+ }
267
+ return { text: source.slice(i, j), end: j };
268
+ };
269
+
270
+ const isRegexContext = (out) => {
271
+ const prev = tokensText(out).trimEnd();
272
+ if (!prev) return true;
273
+ const afterOperator = /[=(:,[?!&|{};]$/.test(prev);
274
+ const afterKeyword = /(?:return|case|throw|=>|typeof|void)$/.test(prev);
275
+ return afterOperator || afterKeyword;
276
+ };
277
+
278
+ const highlightTemplate = (source, i, highlightExpr) => {
279
+ const n = source.length;
280
+ let j = i + 1;
281
+ const parts = [];
282
+ emit(parts, 'template', '`');
283
+ let buf = '';
284
+ const flush = () => {
285
+ if (buf) {
286
+ emit(parts, 'template', buf);
287
+ buf = '';
288
+ }
289
+ };
290
+ while (j < n) {
291
+ if (source[j] === '\\') {
292
+ flush();
293
+ emit(parts, 'escape', source.slice(j, j + 2));
294
+ j += 2;
295
+ continue;
296
+ }
297
+ if (source[j] === '`') {
298
+ flush();
299
+ emit(parts, 'template', '`');
300
+ j += 1;
301
+ break;
302
+ }
303
+ if (source[j] === '$' && source[j + 1] === '{') {
304
+ flush();
305
+ emit(parts, 'interpolation', '${');
306
+ j += 2;
307
+ const interp = readTemplateInterp(source, j);
308
+ const inner = highlightExpr(interp.expr);
309
+ for (const t of inner) parts.push(t);
310
+ j = interp.end;
311
+ if (source[j] === '}') {
312
+ emit(parts, 'interpolation', '}');
313
+ j += 1;
314
+ }
315
+ continue;
316
+ }
317
+ buf += source[j];
318
+ j += 1;
319
+ }
320
+ flush();
321
+ return { tokens: parts, end: j };
322
+ };
323
+
324
+ const isJsxTagStart = (source, i) => {
325
+ if (source[i] !== '<') return false;
326
+ if (i > 0 && /[A-Za-z0-9_$]/.test(source[i - 1])) return false;
327
+ const next = source[i + 1];
328
+ if (next === '>' || next === '/') return true;
329
+ if (!/[A-Za-z_$]/.test(next || '')) return false;
330
+ let j = i + 1;
331
+ while (j < source.length && /[A-Za-z0-9_$.:-]/.test(source[j])) j += 1;
332
+ const after = source[skipWs(source, j)];
333
+ if (after === '>' || after === '/' || after === '{') return true;
334
+ return /[A-Za-z_$]/.test(after || '');
335
+ };
336
+
337
+ const jsxNameStyle = (name) => {
338
+ if (/^[A-Z]/.test(name) || name.includes('.')) return 'className';
339
+ return 'tag';
340
+ };
341
+
342
+ const readJsxName = (source, i) => {
343
+ let j = i;
344
+ while (j < source.length && /[A-Za-z0-9_$.:-]/.test(source[j])) j += 1;
345
+ return { text: source.slice(i, j), end: j };
346
+ };
347
+
348
+ const highlightJsxExpr = (source, i, inner) => {
349
+ const out = [];
350
+ emit(out, 'interpolation', '{');
351
+ const interp = readTemplateInterp(source, i + 1);
352
+ const tokens = inner(interp.expr);
353
+ for (const t of tokens) out.push(t);
354
+ let end = interp.end;
355
+ if (source[end] === '}') {
356
+ emit(out, 'interpolation', '}');
357
+ end += 1;
358
+ }
359
+ return { tokens: out, end };
360
+ };
361
+
362
+ const highlightJsxTagTail = (source, i, inner, out) => {
363
+ const n = source.length;
364
+ let selfClosing = false;
365
+ while (i < n) {
366
+ const ch = source[i];
367
+ if (ch === '>') {
368
+ emit(out, 'punct', '>');
369
+ i += 1;
370
+ break;
371
+ }
372
+ if (ch === '/' && source[i + 1] === '>') {
373
+ emit(out, 'punct', '/>');
374
+ i += 2;
375
+ selfClosing = true;
376
+ break;
377
+ }
378
+ if (/\s/.test(ch)) {
379
+ emit(out, 'plain', ch);
380
+ i += 1;
381
+ continue;
382
+ }
383
+ if (ch === '{') {
384
+ const expr = highlightJsxExpr(source, i, inner);
385
+ for (const t of expr.tokens) out.push(t);
386
+ i = expr.end;
387
+ continue;
388
+ }
389
+ if (QUOTES.includes(ch)) {
390
+ const { text, end } = readString(source, i);
391
+ emit(out, 'string', text);
392
+ i = end;
393
+ continue;
394
+ }
395
+ if (ch === '=') {
396
+ emit(out, 'punct', '=');
397
+ i += 1;
398
+ continue;
399
+ }
400
+ if (/[A-Za-z_$]/.test(ch)) {
401
+ const { text, end } = readJsxName(source, i);
402
+ emit(out, 'attr', text);
403
+ i = end;
404
+ continue;
405
+ }
406
+ emit(out, 'punct', ch);
407
+ i += 1;
408
+ }
409
+ return { end: i, selfClosing };
410
+ };
411
+
412
+ const highlightJsxElement = (source, i, inner) => {
413
+ const out = [];
414
+ const n = source.length;
415
+ emit(out, 'punct', '<');
416
+ i += 1;
417
+ let closing = false;
418
+ if (source[i] === '/') {
419
+ emit(out, 'punct', '/');
420
+ i += 1;
421
+ closing = true;
422
+ }
423
+ const nameRead = readJsxName(source, i);
424
+ const name = nameRead.text;
425
+ if (name) {
426
+ emit(out, jsxNameStyle(name), name);
427
+ i = nameRead.end;
428
+ }
429
+ const tail = highlightJsxTagTail(source, i, inner, out);
430
+ i = tail.end;
431
+ if (closing || tail.selfClosing) return { tokens: out, end: i };
432
+ while (i < n) {
433
+ if (source.startsWith('</', i)) {
434
+ const close = highlightJsxElement(source, i, inner);
435
+ for (const t of close.tokens) out.push(t);
436
+ return { tokens: out, end: close.end };
437
+ }
438
+ if (source[i] === '{') {
439
+ const expr = highlightJsxExpr(source, i, inner);
440
+ for (const t of expr.tokens) out.push(t);
441
+ i = expr.end;
442
+ continue;
443
+ }
444
+ if (source[i] === '<' && isJsxTagStart(source, i)) {
445
+ const child = highlightJsxElement(source, i, inner);
446
+ for (const t of child.tokens) out.push(t);
447
+ i = child.end;
448
+ continue;
449
+ }
450
+ let j = i;
451
+ while (j < n && source[j] !== '<' && source[j] !== '{') j += 1;
452
+ if (j === i) {
453
+ emit(out, 'plain', source[i]);
454
+ i += 1;
455
+ continue;
456
+ }
457
+ emit(out, 'plain', source.slice(i, j));
458
+ i = j;
459
+ }
460
+ return { tokens: out, end: i };
461
+ };
462
+
463
+ const highlightJsFamily = (source, options = {}) => {
464
+ const ts = options.ts === true;
465
+ const jsx = options.jsx === true;
466
+ const inner = (expr) => highlightJsFamily(expr, options);
467
+ const out = [];
468
+ let i = 0;
469
+ const n = source.length;
470
+ let expectName = null;
471
+
472
+ while (i < n) {
473
+ const ch = source[i];
474
+ const next = source[i + 1];
475
+
476
+ if (ch === '/' && next === '/') {
477
+ const { text, end } = readUntilNl(source, i);
478
+ emit(out, 'comment', text);
479
+ i = end;
480
+ continue;
481
+ }
482
+
483
+ if (ch === '/' && next === '*') {
484
+ const { text, end } = readBlockComment(source, i);
485
+ emit(out, 'comment', text);
486
+ i = end;
487
+ continue;
488
+ }
489
+
490
+ if (ch === '`') {
491
+ const r = highlightTemplate(source, i, inner);
492
+ for (const t of r.tokens) out.push(t);
493
+ i = r.end;
494
+ continue;
495
+ }
496
+
497
+ if (QUOTES.includes(ch)) {
498
+ const { text, end } = readString(source, i);
499
+ emit(out, 'string', text);
500
+ i = end;
501
+ continue;
502
+ }
503
+
504
+ if (ch === '/' && isRegexContext(out)) {
505
+ const { text, end } = readRegex(source, i);
506
+ emit(out, 'regex', text);
507
+ i = end;
508
+ continue;
509
+ }
510
+
511
+ if (/[0-9]/.test(ch) || (ch === '.' && /[0-9]/.test(next || ''))) {
512
+ const { text, end } = readJsNumber(source, i);
513
+ emit(out, 'number', text);
514
+ i = end;
515
+ continue;
516
+ }
517
+
518
+ if (ch === '@') {
519
+ let j = i + 1;
520
+ while (j < n && /[A-Za-z0-9_$]/.test(source[j])) j += 1;
521
+ emit(out, 'decorator', source.slice(i, j));
522
+ i = j;
523
+ expectName = null;
524
+ continue;
525
+ }
526
+
527
+ if (/[A-Za-z_$]/.test(ch)) {
528
+ const word = peekIdent(source, i);
529
+ const end = i + word.length;
530
+ const k = skipWs(source, end);
531
+ const after = source[k];
532
+ const ctx = { word, ts, after, source, k, out, expectName };
533
+ emit(out, identStyle(ctx), word);
534
+ if (EXPECT_STYLE[expectName]) expectName = null;
535
+ else expectName = NEXT_EXPECT[word] ?? null;
536
+ i = end;
537
+ continue;
538
+ }
539
+
540
+ if (jsx && ch === '<' && isJsxTagStart(source, i)) {
541
+ const node = highlightJsxElement(source, i, inner);
542
+ for (const t of node.tokens) out.push(t);
543
+ i = node.end;
544
+ expectName = null;
545
+ continue;
546
+ }
547
+
548
+ if (/[=<>!+\-*/%&|^~?:]/.test(ch)) {
549
+ const { text, end } = readJsOperator(source, i);
550
+ emit(out, 'operator', text);
551
+ i = end;
552
+ expectName = null;
553
+ continue;
554
+ }
555
+ if (/[{}()[\];,.]/.test(ch)) {
556
+ emit(out, 'punct', ch);
557
+ i += 1;
558
+ continue;
559
+ }
560
+ emit(out, 'plain', ch);
561
+ i += 1;
562
+ }
563
+ return out;
564
+ };
565
+
566
+ const highlight = (source, lang) => {
567
+ const options = OPTIONS[lang] ?? {};
568
+ return highlightJsFamily(source, options);
569
+ };
570
+
571
+ module.exports = { langs, aliases, highlight };
@@ -0,0 +1,54 @@
1
+ 'use strict';
2
+
3
+ const { emit, readString } = require('./core.js');
4
+
5
+ const langs = ['json'];
6
+
7
+ const JSON_LITERAL = {
8
+ true: 'literal',
9
+ false: 'literal',
10
+ null: 'literal',
11
+ };
12
+
13
+ const highlight = (source) => {
14
+ const out = [];
15
+ let i = 0;
16
+ const n = source.length;
17
+ while (i < n) {
18
+ const ch = source[i];
19
+ if (ch === '"') {
20
+ const { text, end } = readString(source, i);
21
+ let k = end;
22
+ while (k < n && /\s/.test(source[k])) k += 1;
23
+ const kind = source[k] === ':' ? 'property' : 'string';
24
+ emit(out, kind, text);
25
+ i = end;
26
+ continue;
27
+ }
28
+ if (/[0-9-]/.test(ch)) {
29
+ let j = i;
30
+ while (j < n && /[0-9.eE+-]/.test(source[j])) j += 1;
31
+ emit(out, 'number', source.slice(i, j));
32
+ i = j;
33
+ continue;
34
+ }
35
+ if (/[a-z]/.test(ch)) {
36
+ let j = i;
37
+ while (j < n && /[a-z]/.test(source[j])) j += 1;
38
+ const word = source.slice(i, j);
39
+ emit(out, JSON_LITERAL[word] ?? 'plain', word);
40
+ i = j;
41
+ continue;
42
+ }
43
+ if (/[{}[\],:]/.test(ch)) {
44
+ emit(out, 'punct', ch);
45
+ i += 1;
46
+ continue;
47
+ }
48
+ emit(out, 'plain', ch);
49
+ i += 1;
50
+ }
51
+ return out;
52
+ };
53
+
54
+ module.exports = { langs, highlight };