@tanstack/highlight
Version:
Tiny class-based syntax highlighting for documentation.
87 lines (86 loc) • 3.95 kB
JavaScript
import { blockCommentEnd } from '../internal/nested-comment.js';
import { defineLanguage } from '../core.js';
import { collectPatternRanges } from '../internal/patterns.js';
const patterns = [
[/@[A-Za-z_$][\w$]*/g, 'attr'],
// Named-argument and record labels; the lookbehind keeps ternaries plain.
[/(?<![\w$])(?=[a-z_$])(?<=[(,{]\s*)(?!default:)[\w$]+(?=:)/g, 'property'],
[/(?<![\w$.])(?:(?:async|sync|yield)\*|abstract|as|assert|async|await|break|case|catch|class|const|continue|default|do|else|enum|export|extends|final|finally|for|if|implements|import|in|is|new|operator|rethrow|return|static|super|switch|this|throw|try|typedef|var|while|with|yield)(?![\w$])/g, 'keyword'],
// Built-in and contextual words are keywords only when another word, string or `(` follows.
[/(?<![\w$.])(?:base|covariant|deferred|extension|external|factory|get|hide|interface|late|library|mixin|on|part|required|sealed|set|show|when)(?=\s+(?!i[ns]\b)[\w$'"(])/g, 'keyword'],
[/(?<![\w$.])(?:true|false|null)(?![\w$])/g, 'literal'],
[/\b(?:class|enum|mixin|extension(?:\s+type)?|typedef)\s+([A-Za-z_$][\w$]*)/g, 'type', 1],
[/(?<![\w$])(?:int|double|num|bool|void|dynamic|[_$]*[A-Z][A-Z\d_$]*[a-z][\w$]*)(?![\w$])/g, 'type'],
[/(?<![\w$])[A-Za-z_$][\w$]*(?=\s*\()/g, 'function'],
[/(?<!\.\.)\.\s*([A-Za-z_$][\w$]*)/g, 'property', 1],
[/(?:^|[^\w$.])(0[xX][\da-fA-F_]*|(?:\d[\d_]*(?:\.\d[\d_]*)?|\.\d[\d_]*)(?:[eE][+-]?\d[\d_]*)?)(?![\w$]|\.\d)/g, 'number', 1],
[/\?\?=?|\?\.\.?|\.\.\.?\??|=>|~\/=?|<<=?|>>>?=?|&&|\|\||\+\+|--|[+\-*/%&|^!<>=]=?|[~?:]/g, 'operator'],
];
export const dart = defineLanguage({
name: 'dart',
tokenize(code) {
const ranges = [];
const lexical = /\/\/.*|\/\*|(?:(?<![\w$])r)?['"]/g;
let match;
while ((match = lexical.exec(code))) {
const comment = match[0][0] === '/';
let end = lexical.lastIndex;
if (match[0] === '/*') {
end = blockCommentEnd(code, end);
}
else if (!comment)
end = stringEnd(code, end - 1, match[0][0] === 'r');
ranges.push({ start: match.index, end, className: comment ? 'comment' : 'string' });
lexical.lastIndex = end;
}
return collectPatternRanges(code, patterns, ranges);
},
});
// Open quotes and `${` brace depths live on an explicit stack so deep nesting cannot overflow.
// Raw strings cannot hold holes, so `raw` only ever describes the innermost open string.
function stringEnd(code, index, raw) {
const stack = [];
let open = true;
while (index < code.length) {
const char = code[index];
if (open) {
const quote = code.startsWith(char.repeat(3), index) ? char.repeat(3) : char;
stack.push(quote);
index += quote.length;
open = false;
continue;
}
const top = stack[stack.length - 1];
if (top === undefined)
break;
if (typeof top === 'number') {
if (char === '"' || char === "'") {
open = true;
raw = code[index - 1] === 'r';
continue;
}
if (char === '}' && !top)
stack.pop();
else if (char === '{' || char === '}')
stack[stack.length - 1] = top + (char === '{' ? 1 : -1);
index++;
}
else if (code.startsWith(top, index) || (char === '\n' && top.length < 3)) {
if (char !== '\n')
index += top.length;
stack.pop();
raw = false;
}
else if (raw)
index++;
else if (char === '\\')
index += 2;
else if (char === '$' && code[index + 1] === '{') {
stack.push(0);
index += 2;
}
else
index++;
}
return Math.min(index, code.length);
}