@tanstack/highlight
Version:
Tiny class-based syntax highlighting for documentation.
91 lines (90 loc) • 4.36 kB
JavaScript
import { defineLanguage } from '../core.js';
import { collectPatternRanges } from '../internal/patterns.js';
const patterns = [
{ className: 'attr', regex: /@[A-Za-z_$][\w$]*/g },
// Named-argument and record labels; the lookbehind keeps ternaries plain.
{ className: 'property', regex: /(?<![\w$])(?=[a-z_$])(?<=[(,{]\s*)(?!default:)[\w$]+(?=:)/g },
{ className: 'keyword', regex: /(?<![\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 },
// Built-in and contextual words are keywords only when another word, string or `(` follows.
{ className: 'keyword', regex: /(?<![\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 },
{ className: 'literal', regex: /(?<![\w$.])(?:true|false|null)(?![\w$])/g },
{ className: 'type', regex: /\b(?:class|enum|mixin|extension(?:\s+type)?|typedef)\s+([A-Za-z_$][\w$]*)/g, group: 1 },
{ className: 'type', regex: /(?<![\w$])(?:int|double|num|bool|void|dynamic|[_$]*[A-Z][A-Z\d_$]*[a-z][\w$]*)(?![\w$])/g },
{ className: 'function', regex: /(?<![\w$])[A-Za-z_$][\w$]*(?=\s*\()/g },
{ className: 'property', regex: /(?<!\.\.)\.\s*([A-Za-z_$][\w$]*)/g, group: 1 },
{ className: 'number', regex: /(?:^|[^\w$.])(0[xX][\da-fA-F_]*|(?:\d[\d_]*(?:\.\d[\d_]*)?|\.\d[\d_]*)(?:[eE][+-]?\d[\d_]*)?)(?![\w$]|\.\d)/g, group: 1 },
{ className: 'operator', regex: /\?\?=?|\?\.\.?|\.\.\.?\??|=>|~\/=?|<<=?|>>>?=?|&&|\|\||\+\+|--|[+\-*/%&|^!<>=]=?|[~?:]/g },
];
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] === '/*') {
for (let depth = 1; depth && end < code.length; end++) {
const step = code.startsWith('/*', end) ? 1 : code.startsWith('*/', end) ? -1 : 0;
depth += step;
if (step)
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);
}