@terrazzo/parser
Version:
Parser/validator for the Design Tokens Community Group (DTCG) standard.
1,486 lines (1,467 loc) • 153 kB
JavaScript
import wcmatch from "wildcard-match";
import pc from "picocolors";
import { merge } from "merge-anything";
import { isAlias, parseAlias, parseColor, pluralize, splitID, tokenToCulori } from "@terrazzo/token-tools";
import { clampChroma, wcagContrast } from "culori";
//#region src/lib/code-frame.ts
/**
* Extract what lines should be marked and highlighted.
*/
function getMarkerLines(loc, source, opts = {}) {
const startLoc = {
column: 0,
line: -1,
...loc.start
};
const endLoc = {
...startLoc,
...loc.end
};
const { linesAbove = 2, linesBelow = 3 } = opts || {};
const startLine = startLoc.line;
const startColumn = startLoc.column;
const endLine = endLoc.line;
const endColumn = endLoc.column;
let start = Math.max(startLine - (linesAbove + 1), 0);
let end = Math.min(source.length, endLine + linesBelow);
if (startLine === -1) start = 0;
if (endLine === -1) end = source.length;
const lineDiff = endLine - startLine;
const markerLines = {};
if (lineDiff) for (let i = 0; i <= lineDiff; i++) {
const lineNumber = i + startLine;
if (!startColumn) markerLines[lineNumber] = true;
else if (i === 0) {
const sourceLength = source[lineNumber - 1].length;
markerLines[lineNumber] = [startColumn, sourceLength - startColumn + 1];
} else if (i === lineDiff) markerLines[lineNumber] = [0, endColumn];
else {
const sourceLength = source[lineNumber - i].length;
markerLines[lineNumber] = [0, sourceLength];
}
}
else if (startColumn === endColumn) if (startColumn) markerLines[startLine] = [startColumn, 0];
else markerLines[startLine] = true;
else markerLines[startLine] = [startColumn, endColumn - startColumn];
return {
start,
end,
markerLines
};
}
/**
* RegExp to test for newlines in terminal.
*/
const NEWLINE = /\r\n|[\n\r\u2028\u2029]/;
function codeFrameColumns(rawLines, loc, opts = {}) {
const lines = rawLines.split(NEWLINE);
const { start, end, markerLines } = getMarkerLines(loc, lines, opts);
const hasColumns = loc.start && typeof loc.start.column === "number";
const numberMaxWidth = String(end).length;
let frame = rawLines.split(NEWLINE, end).slice(start, end).map((line, index) => {
const number = start + 1 + index;
const paddedNumber = ` ${number}`.slice(-numberMaxWidth);
const gutter = ` ${paddedNumber} |`;
const hasMarker = markerLines[number];
const lastMarkerLine = !markerLines[number + 1];
if (hasMarker) {
let markerLine = "";
if (Array.isArray(hasMarker)) {
const markerSpacing = line.slice(0, Math.max(hasMarker[0] - 1, 0)).replace(/[^\t]/g, " ");
const numberOfMarkers = hasMarker[1] || 1;
markerLine = [
"\n ",
gutter.replace(/\d/g, " "),
" ",
markerSpacing,
"^".repeat(numberOfMarkers)
].join("");
if (lastMarkerLine && opts.message) markerLine += ` ${opts.message}`;
}
return [
">",
gutter,
line.length > 0 ? ` ${line}` : "",
markerLine
].join("");
} else return ` ${gutter}${line.length > 0 ? ` ${line}` : ""}`;
}).join("\n");
if (opts.message && !hasColumns) frame = `${" ".repeat(numberMaxWidth + 1)}${opts.message}\n${frame}`;
return frame;
}
//#endregion
//#region src/logger.ts
const LOG_ORDER = [
"error",
"warn",
"info",
"debug"
];
const MESSAGE_COLOR = {
error: pc.red,
warn: pc.yellow
};
const timeFormatter = new Intl.DateTimeFormat("en-us", {
hour: "numeric",
hour12: false,
minute: "numeric",
second: "numeric",
fractionalSecondDigits: 3
});
/**
* @param {Entry} entry
* @param {Severity} severity
* @return {string}
*/
function formatMessage(entry, severity) {
let message = entry.message;
message = `[${entry.group}${entry.label ? `:${entry.label}` : ""}] ${message}`;
if (severity in MESSAGE_COLOR) message = MESSAGE_COLOR[severity](message);
if (entry.src) {
const start = entry.node?.loc?.start ?? {
line: 0,
column: 0
};
const loc = entry.filename ? `${entry.filename?.href.replace(/^file:\/\//, "")}:${start?.line ?? 0}:${start?.column ?? 0}\n\n` : "";
const codeFrame = codeFrameColumns(entry.src, { start }, { highlightCode: false });
message = `${message}\n\n${loc}${codeFrame}`;
}
return message;
}
var Logger = class {
level = "info";
debugScope = "*";
errorCount = 0;
warnCount = 0;
infoCount = 0;
debugCount = 0;
constructor(options) {
if (options?.level) this.level = options.level;
if (options?.debugScope) this.debugScope = options.debugScope;
}
setLevel(level) {
this.level = level;
}
/** Log an error message (always; can’t be silenced) */
error(entry) {
this.errorCount++;
const message = formatMessage(entry, "error");
if (entry.continueOnError) {
console.error(message);
return;
}
if (entry.node) throw new TokensJSONError(message);
else throw new Error(message);
}
/** Log an info message (if logging level permits) */
info(entry) {
this.infoCount++;
if (this.level === "silent" || LOG_ORDER.indexOf(this.level) < LOG_ORDER.indexOf("info")) return;
const message = formatMessage(entry, "info");
console.log(message);
}
/** Log a warning message (if logging level permits) */
warn(entry) {
this.warnCount++;
if (this.level === "silent" || LOG_ORDER.indexOf(this.level) < LOG_ORDER.indexOf("warn")) return;
const message = formatMessage(entry, "warn");
console.warn(message);
}
/** Log a diagnostics message (if logging level permits) */
debug(entry) {
if (this.level === "silent" || LOG_ORDER.indexOf(this.level) < LOG_ORDER.indexOf("debug")) return;
this.debugCount++;
let message = formatMessage(entry, "debug");
const debugPrefix = entry.label ? `${entry.group}:${entry.label}` : entry.group;
if (this.debugScope !== "*" && !wcmatch(this.debugScope)(debugPrefix)) return;
message.replace(/\[config[^\]]+\]/, (match) => pc.green(match)).replace(/\[parser[^\]]+\]/, (match) => pc.magenta(match)).replace(/\[lint[^\]]+\]/, (match) => pc.yellow(match)).replace(/\[plugin[^\]]+\]/, (match) => pc.cyan(match));
message = `${pc.dim(timeFormatter.format(performance.now()))} ${message}`;
if (typeof entry.timing === "number") {
let timing = "";
if (entry.timing < 1e3) timing = `${Math.round(entry.timing * 100) / 100}ms`;
else if (entry.timing < 6e4) timing = `${Math.round(entry.timing * 100) / 1e5}s`;
message = `${message} ${pc.dim(`[${timing}]`)}`;
}
console.log(message);
}
/** Get stats for current logger instance */
stats() {
return {
errorCount: this.errorCount,
warnCount: this.warnCount,
infoCount: this.infoCount,
debugCount: this.debugCount
};
}
};
var TokensJSONError = class extends Error {
constructor(message) {
super(message);
this.name = "TokensJSONError";
}
};
//#endregion
//#region src/build/index.ts
const SINGLE_VALUE = "SINGLE_VALUE";
const MULTI_VALUE = "MULTI_VALUE";
/** Validate plugin setTransform() calls for immediate feedback */
function validateTransformParams({ params, logger, pluginName }) {
const baseMessage = {
group: "plugin",
label: pluginName,
message: ""
};
if (!params.value || typeof params.value !== "string" && typeof params.value !== "object" || Array.isArray(params.value)) logger.error({
...baseMessage,
message: `setTransform() value expected string or object of strings, received ${Array.isArray(params.value) ? "Array" : typeof params.value}`
});
if (typeof params.value === "object" && Object.values(params.value).some((v) => typeof v !== "string")) logger.error({
...baseMessage,
message: "setTransform() value expected object of strings, received some non-string values"
});
}
/** Run build stage */
async function build(tokens, { sources, logger = new Logger(), config }) {
const formats = {};
const result = { outputFiles: [] };
function getTransforms(params) {
if (!params?.format) {
logger.warn({
group: "plugin",
message: "\"format\" missing from getTransforms(), no tokens returned."
});
return [];
}
const tokenMatcher = params.id ? wcmatch(Array.isArray(params.id) ? params.id : [params.id]) : null;
const modeMatcher = params.mode ? wcmatch(params.mode) : null;
return (formats[params.format] ?? []).filter((token) => {
if (params.$type) {
if (typeof params.$type === "string" && token.token.$type !== params.$type) return false;
else if (Array.isArray(params.$type) && !params.$type.some(($type) => token.token.$type === $type)) return false;
}
if (params.id && params.id !== "*" && tokenMatcher && !tokenMatcher(token.token.id)) return false;
if (modeMatcher && !modeMatcher(token.mode)) return false;
return true;
});
}
let transformsLocked = false;
const startTransform = performance.now();
for (const plugin of config.plugins) if (typeof plugin.transform === "function") await plugin.transform({
tokens,
sources,
getTransforms,
setTransform(id, params) {
if (transformsLocked) {
logger.warn({
message: "Attempted to call setTransform() after transform step has completed.",
group: "plugin",
label: plugin.name
});
return;
}
const token = tokens[id];
const cleanValue = typeof params.value === "string" ? params.value : { ...params.value };
if (typeof cleanValue === "object") {
for (const k of Object.keys(cleanValue)) if (cleanValue[k] === void 0) delete cleanValue[k];
}
validateTransformParams({
logger,
params: {
...params,
value: cleanValue
},
pluginName: plugin.name
});
if (!formats[params.format]) formats[params.format] = [];
const foundTokenI = formats[params.format].findIndex((t) => id === t.id && (!params.localID || params.localID === t.localID) && (!params.mode || params.mode === t.mode));
if (foundTokenI === -1) formats[params.format].push({
...params,
id,
value: cleanValue,
type: typeof cleanValue === "string" ? SINGLE_VALUE : MULTI_VALUE,
mode: params.mode || ".",
token: structuredClone(token)
});
else {
formats[params.format][foundTokenI].value = cleanValue;
formats[params.format][foundTokenI].type = typeof cleanValue === "string" ? SINGLE_VALUE : MULTI_VALUE;
}
}
});
transformsLocked = true;
logger.debug({
group: "parser",
label: "transform",
message: "transform() step",
timing: performance.now() - startTransform
});
const startBuild = performance.now();
for (const plugin of config.plugins) if (typeof plugin.build === "function") {
const pluginBuildStart = performance.now();
await plugin.build({
tokens,
sources,
getTransforms,
outputFile(filename, contents) {
const resolved = new URL(filename, config.outDir);
if (result.outputFiles.some((f) => new URL(f.filename, config.outDir).href === resolved.href)) logger.error({
group: "plugin",
message: `Can’t overwrite file "${filename}"`,
label: plugin.name
});
result.outputFiles.push({
filename,
contents,
plugin: plugin.name,
time: performance.now() - pluginBuildStart
});
}
});
}
logger.debug({
group: "parser",
label: "build",
message: "build() step",
timing: performance.now() - startBuild
});
const startBuildEnd = performance.now();
for (const plugin of config.plugins) if (typeof plugin.buildEnd === "function") await plugin.buildEnd({ outputFiles: structuredClone(result.outputFiles) });
logger.debug({
group: "parser",
label: "build",
message: "buildEnd() step",
timing: performance.now() - startBuildEnd
});
return result;
}
//#endregion
//#region src/lint/plugin-core/lib/docs.ts
function docsLink(ruleName) {
return `https://terrazzo.app/docs/cli/lint#${ruleName.replaceAll("/", "")}`;
}
//#endregion
//#region src/lint/plugin-core/rules/a11y-min-contrast.ts
const A11Y_MIN_CONTRAST = "a11y/min-contrast";
const WCAG2_MIN_CONTRAST = {
AA: {
default: 4.5,
large: 3
},
AAA: {
default: 7,
large: 4.5
}
};
const ERROR_INSUFFICIENT_CONTRAST = "INSUFFICIENT_CONTRAST";
const rule$9 = {
meta: {
messages: { [ERROR_INSUFFICIENT_CONTRAST]: "Pair {{ index }} failed; expected {{ expected }}, got {{ actual }} ({{ level }})" },
docs: {
description: "Enforce colors meet minimum contrast checks for WCAG 2.",
url: docsLink(A11Y_MIN_CONTRAST)
}
},
defaultOptions: {
level: "AA",
pairs: []
},
create({ tokens, options, report }) {
for (let i = 0; i < options.pairs.length; i++) {
const { foreground, background, largeText } = options.pairs[i];
if (!tokens[foreground]) throw new Error(`Token ${foreground} does not exist`);
if (tokens[foreground].$type !== "color") throw new Error(`Token ${foreground} isn’t a color`);
if (!tokens[background]) throw new Error(`Token ${background} does not exist`);
if (tokens[background].$type !== "color") throw new Error(`Token ${background} isn’t a color`);
const a = tokenToCulori(tokens[foreground].$value);
const b = tokenToCulori(tokens[background].$value);
const contrast = wcagContrast(a, b);
const min = WCAG2_MIN_CONTRAST[options.level ?? "AA"][largeText ? "large" : "default"];
if (contrast < min) report({
messageId: ERROR_INSUFFICIENT_CONTRAST,
data: {
index: i + 1,
expected: min,
actual: Math.round(contrast * 100) / 100,
level: options.level
}
});
}
}
};
var a11y_min_contrast_default = rule$9;
//#endregion
//#region src/lint/plugin-core/rules/a11y-min-font-size.ts
const A11Y_MIN_FONT_SIZE = "a11y/min-font-size";
const ERROR_TOO_SMALL = "TOO_SMALL";
const rule$8 = {
meta: {
messages: { [ERROR_TOO_SMALL]: "{{ id }} font size too small. Expected minimum of {{ min }}" },
docs: {
description: "Enforce font sizes are no smaller than the given value.",
url: docsLink(A11Y_MIN_FONT_SIZE)
}
},
defaultOptions: {},
create({ tokens, options, report }) {
if (!options.minSizePx && !options.minSizeRem) throw new Error("Must specify at least one of minSizePx or minSizeRem");
const shouldIgnore = options.ignore ? wcmatch(options.ignore) : null;
for (const t of Object.values(tokens)) {
if (shouldIgnore?.(t.id)) continue;
if (t.aliasOf) continue;
if (t.$type === "typography" && "fontSize" in t.$value) {
const fontSize = t.$value.fontSize;
if (fontSize.unit === "px" && options.minSizePx && fontSize.value < options.minSizePx || fontSize.unit === "rem" && options.minSizeRem && fontSize.value < options.minSizeRem) report({
messageId: ERROR_TOO_SMALL,
data: {
id: t.id,
min: options.minSizePx ? `${options.minSizePx}px` : `${options.minSizeRem}rem`
}
});
}
}
}
};
var a11y_min_font_size_default = rule$8;
//#endregion
//#region src/lint/plugin-core/rules/colorspace.ts
const COLORSPACE = "core/colorspace";
const ERROR_COLOR$1 = "COLOR";
const ERROR_BORDER$1 = "BORDER";
const ERROR_GRADIENT$1 = "GRADIENT";
const ERROR_SHADOW$1 = "SHADOW";
const rule$7 = {
meta: {
messages: {
[ERROR_COLOR$1]: "Color {{ id }} not in colorspace {{ colorSpace }}",
[ERROR_BORDER$1]: "Border {{ id }} not in colorspace {{ colorSpace }}",
[ERROR_GRADIENT$1]: "Gradient {{ id }} not in colorspace {{ colorSpace }}",
[ERROR_SHADOW$1]: "Shadow {{ id }} not in colorspace {{ colorSpace }}"
},
docs: {
description: "Enforce that all colors are in a specific colorspace.",
url: docsLink(COLORSPACE)
}
},
defaultOptions: { colorSpace: "srgb" },
create({ tokens, options, report }) {
if (!options.colorSpace) return;
const shouldIgnore = options.ignore ? wcmatch(options.ignore) : null;
for (const t of Object.values(tokens)) {
if (shouldIgnore?.(t.id)) continue;
if (t.aliasOf) continue;
switch (t.$type) {
case "color": {
if (t.$value.colorSpace !== options.colorSpace) report({
messageId: ERROR_COLOR$1,
data: {
id: t.id,
colorSpace: options.colorSpace
},
node: t.source.node
});
break;
}
case "border": {
if (!t.partialAliasOf?.color && t.$value.color.colorSpace !== options.colorSpace) report({
messageId: ERROR_BORDER$1,
data: {
id: t.id,
colorSpace: options.colorSpace
},
node: t.source.node
});
break;
}
case "gradient": {
for (let stopI = 0; stopI < t.$value.length; stopI++) if (!t.partialAliasOf?.[stopI]?.color && t.$value[stopI].color.colorSpace !== options.colorSpace) report({
messageId: ERROR_GRADIENT$1,
data: {
id: t.id,
colorSpace: options.colorSpace
},
node: t.source.node
});
break;
}
case "shadow": {
for (let shadowI = 0; shadowI < t.$value.length; shadowI++) if (!t.partialAliasOf?.[shadowI]?.color && t.$value[shadowI].color.colorSpace !== options.colorSpace) report({
messageId: ERROR_SHADOW$1,
data: {
id: t.id,
colorSpace: options.colorSpace
},
node: t.source.node
});
break;
}
}
}
}
};
var colorspace_default = rule$7;
//#endregion
//#region ../../node_modules/.pnpm/scule@1.3.0/node_modules/scule/dist/index.mjs
const NUMBER_CHAR_RE = /\d/;
const STR_SPLITTERS = [
"-",
"_",
"/",
"."
];
function isUppercase(char = "") {
if (NUMBER_CHAR_RE.test(char)) return void 0;
return char !== char.toLowerCase();
}
function splitByCase(str, separators) {
const splitters = separators ?? STR_SPLITTERS;
const parts = [];
if (!str || typeof str !== "string") return parts;
let buff = "";
let previousUpper;
let previousSplitter;
for (const char of str) {
const isSplitter = splitters.includes(char);
if (isSplitter === true) {
parts.push(buff);
buff = "";
previousUpper = void 0;
continue;
}
const isUpper = isUppercase(char);
if (previousSplitter === false) {
if (previousUpper === false && isUpper === true) {
parts.push(buff);
buff = char;
previousUpper = isUpper;
continue;
}
if (previousUpper === true && isUpper === false && buff.length > 1) {
const lastChar = buff.at(-1);
parts.push(buff.slice(0, Math.max(0, buff.length - 1)));
buff = lastChar + char;
previousUpper = isUpper;
continue;
}
}
buff += char;
previousUpper = isUpper;
previousSplitter = isSplitter;
}
parts.push(buff);
return parts;
}
function upperFirst(str) {
return str ? str[0].toUpperCase() + str.slice(1) : "";
}
function lowerFirst(str) {
return str ? str[0].toLowerCase() + str.slice(1) : "";
}
function pascalCase(str, opts) {
return str ? (Array.isArray(str) ? str : splitByCase(str)).map((p) => upperFirst(opts?.normalize ? p.toLowerCase() : p)).join("") : "";
}
function camelCase(str, opts) {
return lowerFirst(pascalCase(str || "", opts));
}
function kebabCase(str, joiner) {
return str ? (Array.isArray(str) ? str : splitByCase(str)).map((p) => p.toLowerCase()).join(joiner ?? "-") : "";
}
function snakeCase(str) {
return kebabCase(str || "", "_");
}
//#endregion
//#region src/lint/plugin-core/rules/consistent-naming.ts
const CONSISTENT_NAMING = "core/consistent-naming";
const ERROR_WRONG_FORMAT = "ERROR_WRONG_FORMAT";
const rule$6 = {
meta: {
messages: { [ERROR_WRONG_FORMAT]: "{{ id }} doesn’t match format {{ format }}" },
docs: {
description: "Enforce consistent naming for tokens.",
url: docsLink(CONSISTENT_NAMING)
}
},
defaultOptions: { format: "kebab-case" },
create({ tokens, options, report }) {
const basicFormatter = {
"kebab-case": kebabCase,
camelCase,
PascalCase: pascalCase,
snake_case: snakeCase,
SCREAMING_SNAKE_CASE: (name) => snakeCase(name).toLocaleUpperCase()
}[String(options.format)];
for (const t of Object.values(tokens)) if (basicFormatter) {
const parts = t.id.split(".");
if (!parts.every((part) => basicFormatter(part) === part)) report({
messageId: ERROR_WRONG_FORMAT,
data: {
id: t.id,
format: options.format
},
node: t.source.node
});
} else if (typeof options.format === "function") {
const result = options.format(t.id);
if (result) report({
messageId: ERROR_WRONG_FORMAT,
data: {
id: t.id,
format: "(custom)"
},
node: t.source.node
});
}
}
};
var consistent_naming_default = rule$6;
//#endregion
//#region src/lint/plugin-core/rules/descriptions.ts
const DESCRIPTIONS = "core/descriptions";
const ERROR_MISSING_DESCRIPTION = "MISSING_DESCRIPTION";
const rule$5 = {
meta: {
messages: { [ERROR_MISSING_DESCRIPTION]: "{{ id }} missing description" },
docs: {
description: "Enforce tokens have descriptions.",
url: docsLink(DESCRIPTIONS)
}
},
defaultOptions: {},
create({ tokens, options, report }) {
const shouldIgnore = options.ignore ? wcmatch(options.ignore) : null;
for (const t of Object.values(tokens)) {
if (shouldIgnore?.(t.id)) continue;
if (!t.$description) report({
messageId: ERROR_MISSING_DESCRIPTION,
data: { id: t.id },
node: t.source.node
});
}
}
};
var descriptions_default = rule$5;
//#endregion
//#region src/lint/plugin-core/rules/duplicate-values.ts
const DUPLICATE_VALUES = "core/duplicate-values";
const ERROR_DUPLICATE_VALUE = "ERROR_DUPLICATE_VALUE";
const rule$4 = {
meta: {
messages: { [ERROR_DUPLICATE_VALUE]: "{{ id }} declared a duplicate value" },
docs: {
description: "Enforce tokens can’t redeclare the same value (excludes aliases).",
url: docsLink(DUPLICATE_VALUES)
}
},
defaultOptions: {},
create({ report, tokens, options }) {
const values = {};
const shouldIgnore = options.ignore ? wcmatch(options.ignore) : null;
for (const t of Object.values(tokens)) {
if (shouldIgnore?.(t.id)) continue;
if (!values[t.$type]) values[t.$type] = /* @__PURE__ */ new Set();
if (t.$type === "boolean" || t.$type === "duration" || t.$type === "fontWeight" || t.$type === "link" || t.$type === "number" || t.$type === "string") {
if (typeof t.aliasOf === "string" && isAlias(t.aliasOf)) continue;
if (values[t.$type]?.has(t.$value)) report({
messageId: ERROR_DUPLICATE_VALUE,
data: { id: t.id },
node: t.source.node
});
values[t.$type]?.add(t.$value);
} else {
for (const v of values[t.$type].values() ?? []) if (JSON.stringify(t.$value) === JSON.stringify(v)) {
report({
messageId: ERROR_DUPLICATE_VALUE,
data: { id: t.id },
node: t.source.node
});
break;
}
values[t.$type].add(t.$value);
}
}
}
};
var duplicate_values_default = rule$4;
//#endregion
//#region src/lint/plugin-core/rules/max-gamut.ts
const MAX_GAMUT = "core/max-gamut";
const TOLERANCE = 1e-6;
/** is a Culori-parseable color within the specified gamut? */
function isWithinGamut(color, gamut) {
const parsed = tokenToCulori(color);
if (!parsed) return false;
if ([
"rgb",
"hsl",
"hwb"
].includes(parsed.mode)) return true;
const clamped = clampChroma(parsed, parsed.mode, gamut === "srgb" ? "rgb" : gamut);
return isWithinThreshold(parsed, clamped);
}
/** is Color A close enough to Color B? */
function isWithinThreshold(a, b, tolerance = TOLERANCE) {
for (const k in a) {
if (k === "mode" || k === "alpha") continue;
if (!(k in b)) throw new Error(`Can’t compare ${a.mode} to ${b.mode}`);
if (Math.abs(a[k] - b[k]) > tolerance) return false;
}
return true;
}
const ERROR_COLOR = "COLOR";
const ERROR_BORDER = "BORDER";
const ERROR_GRADIENT = "GRADIENT";
const ERROR_SHADOW = "SHADOW";
const rule$3 = {
meta: {
messages: {
[ERROR_COLOR]: "Color {{ id }} is outside {{ gamut }} gamut",
[ERROR_BORDER]: "Border {{ id }} is outside {{ gamut }} gamut",
[ERROR_GRADIENT]: "Gradient {{ id }} is outside {{ gamut }} gamut",
[ERROR_SHADOW]: "Shadow {{ id }} is outside {{ gamut }} gamut"
},
docs: {
description: "Enforce colors are within the specified gamut.",
url: docsLink(MAX_GAMUT)
}
},
defaultOptions: { gamut: "rec2020" },
create({ tokens, options, report }) {
if (!options?.gamut) return;
if (options.gamut !== "srgb" && options.gamut !== "p3" && options.gamut !== "rec2020") throw new Error(`Unknown gamut "${options.gamut}". Options are "srgb", "p3", or "rec2020"`);
const shouldIgnore = options.ignore ? wcmatch(options.ignore) : null;
for (const t of Object.values(tokens)) {
if (shouldIgnore?.(t.id)) continue;
if (t.aliasOf) continue;
switch (t.$type) {
case "color": {
if (!isWithinGamut(t.$value, options.gamut)) report({
messageId: ERROR_COLOR,
data: {
id: t.id,
gamut: options.gamut
},
node: t.source.node
});
break;
}
case "border": {
if (!t.partialAliasOf?.color && !isWithinGamut(t.$value.color, options.gamut)) report({
messageId: ERROR_BORDER,
data: {
id: t.id,
gamut: options.gamut
},
node: t.source.node
});
break;
}
case "gradient": {
for (let stopI = 0; stopI < t.$value.length; stopI++) if (!t.partialAliasOf?.[stopI]?.color && !isWithinGamut(t.$value[stopI].color, options.gamut)) report({
messageId: ERROR_GRADIENT,
data: {
id: t.id,
gamut: options.gamut
},
node: t.source.node
});
break;
}
case "shadow": {
for (let shadowI = 0; shadowI < t.$value.length; shadowI++) if (!t.partialAliasOf?.[shadowI]?.color && !isWithinGamut(t.$value[shadowI].color, options.gamut)) report({
messageId: ERROR_SHADOW,
data: {
id: t.id,
gamut: options.gamut
},
node: t.source.node
});
break;
}
}
}
}
};
var max_gamut_default = rule$3;
//#endregion
//#region src/lint/plugin-core/rules/required-children.ts
const REQUIRED_CHILDREN = "core/required-children";
const ERROR_EMPTY_MATCH = "EMPTY_MATCH";
const ERROR_MISSING_REQUIRED_TOKENS = "MISSING_REQUIRED_TOKENS";
const ERROR_MISSING_REQUIRED_GROUP = "MISSING_REQUIRED_GROUP";
const rule$2 = {
meta: {
messages: {
[ERROR_EMPTY_MATCH]: "No tokens matched {{ matcher }}",
[ERROR_MISSING_REQUIRED_TOKENS]: "Match {{ index }}: some groups missing required token \"{{ token }}\"",
[ERROR_MISSING_REQUIRED_GROUP]: "Match {{ index }}: some tokens missing required group \"{{ group }}\""
},
docs: {
description: "Enforce token groups have specific children, whether tokens and/or groups.",
url: docsLink(REQUIRED_CHILDREN)
}
},
defaultOptions: { matches: [] },
create({ tokens, options, report }) {
if (!options.matches?.length) throw new Error("Invalid config. Missing `matches: […]`");
for (let matchI = 0; matchI < options.matches.length; matchI++) {
const { match, requiredTokens, requiredGroups } = options.matches[matchI];
if (!match.length) throw new Error(`Match ${matchI}: must declare \`match: […]\``);
if (!requiredTokens?.length && !requiredGroups?.length) throw new Error(`Match ${matchI}: must declare either \`requiredTokens: […]\` or \`requiredGroups: […]\``);
const matcher = wcmatch(match);
const matchGroups = [];
const matchTokens = [];
let tokensMatched = false;
for (const t of Object.values(tokens)) {
if (!matcher(t.id)) continue;
tokensMatched = true;
const groups = t.id.split(".");
matchTokens.push(groups.pop());
matchGroups.push(...groups);
}
if (!tokensMatched) {
report({
messageId: ERROR_EMPTY_MATCH,
data: { matcher: JSON.stringify(match) }
});
continue;
}
if (requiredTokens) {
for (const id of requiredTokens) if (!matchTokens.includes(id)) report({
messageId: ERROR_MISSING_REQUIRED_TOKENS,
data: {
index: matchI,
token: id
}
});
}
if (requiredGroups) {
for (const groupName of requiredGroups) if (!matchGroups.includes(groupName)) report({
messageId: ERROR_MISSING_REQUIRED_GROUP,
data: {
index: matchI,
group: groupName
}
});
}
}
}
};
var required_children_default = rule$2;
//#endregion
//#region src/lint/plugin-core/rules/required-modes.ts
const REQUIRED_MODES = "core/required-modes";
const rule$1 = {
meta: { docs: {
description: "Enforce certain tokens have specific modes.",
url: docsLink(REQUIRED_MODES)
} },
defaultOptions: { matches: [] },
create({ tokens, options, report }) {
if (!options?.matches?.length) throw new Error("Invalid config. Missing `matches: […]`");
for (let matchI = 0; matchI < options.matches.length; matchI++) {
const { match, modes } = options.matches[matchI];
if (!match.length) throw new Error(`Match ${matchI}: must declare \`match: […]\``);
if (!modes?.length) throw new Error(`Match ${matchI}: must declare \`modes: […]\``);
const matcher = wcmatch(match);
let tokensMatched = false;
for (const t of Object.values(tokens)) {
if (!matcher(t.id)) continue;
tokensMatched = true;
for (const mode of modes) if (!t.mode?.[mode]) report({
message: `Token ${t.id}: missing required mode "${mode}"`,
node: t.source.node
});
if (!tokensMatched) report({
message: `Match "${matchI}": no tokens matched ${JSON.stringify(match)}`,
node: t.source.node
});
}
}
}
};
var required_modes_default = rule$1;
//#endregion
//#region src/lint/plugin-core/rules/required-typography-properties.ts
const REQUIRED_TYPOGRAPHY_PROPERTIES = "core/required-typography-properties";
const rule = {
meta: { docs: {
description: "Enforce typography tokens have required properties.",
url: docsLink(REQUIRED_TYPOGRAPHY_PROPERTIES)
} },
defaultOptions: { properties: [] },
create({ tokens, options, report }) {
if (!options) return;
if (!options.properties.length) throw new Error(`"properties" can’t be empty`);
const shouldIgnore = options.ignore ? wcmatch(options.ignore) : null;
for (const t of Object.values(tokens)) {
if (shouldIgnore?.(t.id)) continue;
if (t.$type !== "typography") continue;
if (t.aliasOf) continue;
for (const p of options.properties) if (!t.partialAliasOf?.[p] && !(p in t.$value)) report({
message: `${t.id} missing required typographic property "${p}"`,
node: t.source.node
});
}
}
};
var required_typography_properties_default = rule;
//#endregion
//#region src/lint/plugin-core/index.ts
function coreLintPlugin() {
return {
name: "@terrazzo/plugin-lint-core",
lint() {
return {
[COLORSPACE]: colorspace_default,
[CONSISTENT_NAMING]: consistent_naming_default,
[DESCRIPTIONS]: descriptions_default,
[DUPLICATE_VALUES]: duplicate_values_default,
[MAX_GAMUT]: max_gamut_default,
[REQUIRED_CHILDREN]: required_children_default,
[REQUIRED_MODES]: required_modes_default,
[REQUIRED_TYPOGRAPHY_PROPERTIES]: required_typography_properties_default,
[A11Y_MIN_CONTRAST]: a11y_min_contrast_default,
[A11Y_MIN_FONT_SIZE]: a11y_min_font_size_default
};
}
};
}
//#endregion
//#region src/config.ts
const TRAILING_SLASH_RE = /\/*$/;
/**
* Validate and normalize a config
*/
function defineConfig(rawConfig, { logger = new Logger(), cwd } = {}) {
const configStart = performance.now();
if (!cwd) logger.error({
group: "config",
label: "core",
message: "defineConfig() missing `cwd` for JS API"
});
const config = merge({}, rawConfig);
normalizeTokens({
rawConfig,
config,
logger,
cwd
});
normalizeOutDir({
config,
cwd,
logger
});
normalizePlugins({
config,
logger
});
normalizeLint({
config,
logger
});
normalizeIgnore({
config,
logger
});
for (const plugin of config.plugins) plugin.config?.({ ...config });
logger.debug({
group: "parser",
label: "config",
message: "Finish config validation",
timing: performance.now() - configStart
});
return config;
}
/** Normalize config.tokens */
function normalizeTokens({ rawConfig, config, logger, cwd }) {
if (rawConfig.tokens === void 0) config.tokens = ["./tokens.json"];
else if (typeof rawConfig.tokens === "string") config.tokens = [rawConfig.tokens];
else if (Array.isArray(rawConfig.tokens)) {
config.tokens = [];
for (const file of rawConfig.tokens) if (typeof file === "string" || file instanceof URL) config.tokens.push(file);
else logger.error({
group: "config",
label: "tokens",
message: `Expected array of strings, encountered ${JSON.stringify(file)}`
});
} else logger.error({
group: "config",
label: "tokens",
message: `Expected string or array of strings, received ${typeof rawConfig.tokens}`
});
for (let i = 0; i < config.tokens.length; i++) {
const filepath = config.tokens[i];
if (filepath instanceof URL) continue;
try {
config.tokens[i] = new URL(filepath, cwd);
} catch (err) {
logger.error({
group: "config",
label: "tokens",
message: `Invalid URL ${filepath}`
});
}
}
}
/** Normalize config.outDir */
function normalizeOutDir({ config, cwd, logger }) {
if (config.outDir instanceof URL) {} else if (typeof config.outDir === "undefined") config.outDir = new URL("./tokens/", cwd);
else if (typeof config.outDir !== "string") logger.error({
group: "config",
label: "outDir",
message: `Expected string, received ${JSON.stringify(config.outDir)}`
});
else {
config.outDir = new URL(config.outDir, cwd);
config.outDir = new URL(config.outDir.href.replace(TRAILING_SLASH_RE, "/"));
}
}
/** Normalize config.plugins */
function normalizePlugins({ config, logger }) {
if (typeof config.plugins === "undefined") config.plugins = [];
if (!Array.isArray(config.plugins)) logger.error({
group: "config",
label: "plugins",
message: `Expected array of plugins, received ${JSON.stringify(config.plugins)}`
});
config.plugins.push(coreLintPlugin());
for (let n = 0; n < config.plugins.length; n++) {
const plugin = config.plugins[n];
if (typeof plugin !== "object") logger.error({
group: "config",
label: `plugin[${n}]`,
message: `Expected output plugin, received ${JSON.stringify(plugin)}`
});
else if (!plugin.name) logger.error({
group: "config",
label: `plugin[${n}]`,
message: `Missing "name"`
});
}
config.plugins.sort((a, b) => {
if (a.enforce === "pre" && b.enforce !== "pre") return -1;
else if (a.enforce === "post" && b.enforce !== "post") return 1;
return 0;
});
}
function normalizeLint({ config, logger }) {
if (config.lint !== void 0) {
if (config.lint === null || typeof config.lint !== "object" || Array.isArray(config.lint)) logger.error({
group: "config",
label: "lint",
message: "Must be an object"
});
if (!config.lint.build) config.lint.build = { enabled: true };
if (config.lint.build.enabled !== void 0) {
if (typeof config.lint.build.enabled !== "boolean") logger.error({
group: "config",
label: "lint › build › enabled",
message: `Expected boolean, received ${JSON.stringify(config.lint.build)}`
});
} else config.lint.build.enabled = true;
if (config.lint.rules === void 0) config.lint.rules = {};
else {
if (config.lint.rules === null || typeof config.lint.rules !== "object" || Array.isArray(config.lint.rules)) {
logger.error({
group: "config",
label: "lint › rules",
message: `Expected object, received ${JSON.stringify(config.lint.rules)}`
});
return;
}
const allRules = /* @__PURE__ */ new Map();
for (const plugin of config.plugins) {
if (typeof plugin.lint !== "function") continue;
const pluginRules = plugin.lint();
if (!pluginRules || Array.isArray(pluginRules) || typeof pluginRules !== "object") {
logger.error({
group: "config",
label: `plugin › ${plugin.name}`,
message: `Expected object for lint() received ${JSON.stringify(pluginRules)}`
});
continue;
}
for (const rule$10 of Object.keys(pluginRules)) {
if (allRules.get(rule$10) && allRules.get(rule$10) !== plugin.name) logger.error({
group: "config",
label: `plugin › ${plugin.name}`,
message: `Duplicate rule ${rule$10} already registered by plugin ${allRules.get(rule$10)}`
});
allRules.set(rule$10, plugin.name);
}
}
for (const id of Object.keys(config.lint.rules)) {
if (!allRules.has(id)) logger.error({
group: "config",
label: `lint › rule › ${id}`,
message: "Unknown rule. Is the plugin installed?"
});
const value = config.lint.rules[id];
let severity = "off";
let options;
if (typeof value === "number" || typeof value === "string") severity = value;
else if (Array.isArray(value)) {
severity = value[0];
options = value[1];
} else if (value !== void 0) logger.error({
group: "config",
label: `lint › rule › ${id}`,
message: `Invalid eyntax. Expected \`string | number | Array\`, received ${JSON.stringify(value)}}`
});
config.lint.rules[id] = [severity, options];
if (typeof severity === "number") {
if (severity !== 0 && severity !== 1 && severity !== 2) logger.error({
group: "config",
label: `lint › rule › ${id}`,
message: `Invalid number ${severity}. Specify 0 (off), 1 (warn), or 2 (error).`
});
config.lint.rules[id][0] = [
"off",
"warn",
"error"
][severity];
} else if (typeof severity === "string") {
if (severity !== "off" && severity !== "warn" && severity !== "error") logger.error({
group: "config",
label: `lint › rule › ${id}`,
message: `Invalid string ${JSON.stringify(severity)}. Specify "off", "warn", or "error".`
});
} else if (value !== null) logger.error({
group: "config",
label: `lint › rule › ${id}`,
message: `Expected string or number, received ${JSON.stringify(value)}`
});
}
}
} else config.lint = {
build: { enabled: true },
rules: {}
};
}
function normalizeIgnore({ config, logger }) {
if (!config.ignore) config.ignore = {};
config.ignore.tokens ??= [];
config.ignore.deprecated ??= false;
if (!Array.isArray(config.ignore.tokens) || config.ignore.tokens.some((x) => typeof x !== "string")) logger.error({
group: "config",
label: "ignore › tokens",
message: `Expected array of strings, received ${JSON.stringify(config.ignore.tokens)}`
});
if (typeof config.ignore.deprecated !== "boolean") logger.error({
group: "config",
label: "ignore › deprecated",
message: `Expected boolean, received ${JSON.stringify(config.ignore.deprecated)}`
});
}
/** Merge configs */
function mergeConfigs(a, b) {
return merge(a, b);
}
//#endregion
//#region src/lint/index.ts
const listFormat$1 = new Intl.ListFormat("en-us");
async function lintRunner({ tokens, filename, config = {}, src, logger }) {
const { plugins = [], lint } = config;
const unusedLintRules = Object.keys(lint?.rules ?? {});
for (const plugin of plugins) if (typeof plugin.lint === "function") {
const s = performance.now();
const linter = plugin.lint();
const errors = [];
const warnings = [];
await Promise.all(Object.entries(linter).map(async ([id, rule$10]) => {
if (!(id in lint.rules) || lint.rules[id] === null) return;
const [severity, options] = lint.rules[id];
if (severity === "off") return;
await rule$10.create({
id,
report(descriptor) {
let message = "";
if (!descriptor.message && !descriptor.messageId) logger.error({
group: "lint",
label: `${plugin.name} › lint › ${id}`,
message: "Unable to report error: missing message or messageId"
});
if (descriptor.message) message = descriptor.message;
else {
if (!(descriptor.messageId in (rule$10.meta?.messages ?? {}))) logger.error({
group: "lint",
label: `${plugin.name} › lint › ${id}`,
message: `messageId "${descriptor.messageId}" does not exist`
});
message = rule$10.meta?.messages?.[descriptor.messageId] ?? "";
}
if (descriptor.data && typeof descriptor.data === "object") for (const [k, v] of Object.entries(descriptor.data)) {
const formatted = [
"string",
"number",
"boolean"
].includes(typeof v) ? String(v) : JSON.stringify(v);
message = message.replace(/{{[^}]+}}/g, (inner) => {
const key = inner.substring(2, inner.length - 2).trim();
return key === k ? formatted : inner;
});
}
(severity === "error" ? errors : warnings).push({
group: "lint",
label: id,
message,
filename,
node: descriptor.node,
src: descriptor.source?.src
});
},
tokens,
filename,
src,
options: merge(rule$10.meta?.defaultOptions ?? [], rule$10.defaultOptions ?? [], options)
});
const unusedLintRuleI = unusedLintRules.indexOf(id);
if (unusedLintRuleI !== -1) unusedLintRules.splice(unusedLintRuleI, 1);
}));
for (const error of errors) logger.error({
...error,
continueOnError: true
});
for (const warning of warnings) logger.warn(warning);
logger.debug({
group: "lint",
label: plugin.name,
message: "Finished",
timing: performance.now() - s
});
if (errors.length) {
const counts = [pluralize(errors.length, "error", "errors")];
if (warnings.length) counts.push(pluralize(warnings.length, "warning", "warnings"));
logger.error({
group: "lint",
message: `Lint failed with ${listFormat$1.format(counts)}`,
label: plugin.name,
continueOnError: false
});
}
}
for (const unusedRule of unusedLintRules) logger.warn({
group: "lint",
label: "lint",
message: `Unknown lint rule "${unusedRule}"`
});
}
//#endregion
//#region ../../node_modules/.pnpm/@humanwhocodes+momoa@3.3.8/node_modules/@humanwhocodes/momoa/dist/momoa.js
/**
* @fileoverview Character codes.
* @author Nicholas C. Zakas
*/
const CHAR_0 = 48;
const CHAR_1 = 49;
const CHAR_9 = 57;
const CHAR_BACKSLASH = 92;
const CHAR_DOLLAR = 36;
const CHAR_DOT = 46;
const CHAR_DOUBLE_QUOTE = 34;
const CHAR_LOWER_A = 97;
const CHAR_LOWER_E = 101;
const CHAR_LOWER_F = 102;
const CHAR_LOWER_N = 110;
const CHAR_LOWER_T = 116;
const CHAR_LOWER_U = 117;
const CHAR_LOWER_X = 120;
const CHAR_LOWER_Z = 122;
const CHAR_MINUS = 45;
const CHAR_NEWLINE = 10;
const CHAR_PLUS = 43;
const CHAR_RETURN = 13;
const CHAR_SINGLE_QUOTE = 39;
const CHAR_SLASH = 47;
const CHAR_SPACE = 32;
const CHAR_TAB = 9;
const CHAR_UNDERSCORE = 95;
const CHAR_UPPER_A = 65;
const CHAR_UPPER_E = 69;
const CHAR_UPPER_F = 70;
const CHAR_UPPER_N = 78;
const CHAR_UPPER_X = 88;
const CHAR_UPPER_Z = 90;
const CHAR_LOWER_B = 98;
const CHAR_LOWER_R = 114;
const CHAR_LOWER_V = 118;
const CHAR_LINE_SEPARATOR = 8232;
const CHAR_PARAGRAPH_SEPARATOR = 8233;
const CHAR_UPPER_I = 73;
const CHAR_STAR = 42;
const CHAR_VTAB = 11;
const CHAR_FORM_FEED = 12;
const CHAR_NBSP = 160;
const CHAR_BOM = 65279;
const CHAR_NON_BREAKING_SPACE = 160;
const CHAR_EN_QUAD = 8192;
const CHAR_EM_QUAD = 8193;
const CHAR_EN_SPACE = 8194;
const CHAR_EM_SPACE = 8195;
const CHAR_THREE_PER_EM_SPACE = 8196;
const CHAR_FOUR_PER_EM_SPACE = 8197;
const CHAR_SIX_PER_EM_SPACE = 8198;
const CHAR_FIGURE_SPACE = 8199;
const CHAR_PUNCTUATION_SPACE = 8200;
const CHAR_THIN_SPACE = 8201;
const CHAR_HAIR_SPACE = 8202;
const CHAR_NARROW_NO_BREAK_SPACE = 8239;
const CHAR_MEDIUM_MATHEMATICAL_SPACE = 8287;
const CHAR_IDEOGRAPHIC_SPACE = 12288;
/**
* @fileoverview JSON syntax helpers
* @author Nicholas C. Zakas
*/
/** @typedef {import("./typedefs.js").TokenType} TokenType */
const LBRACKET = "[";
const RBRACKET = "]";
const LBRACE = "{";
const RBRACE = "}";
const COLON = ":";
const COMMA = ",";
const TRUE = "true";
const FALSE = "false";
const NULL = "null";
const NAN$1 = "NaN";
const INFINITY$1 = "Infinity";
const QUOTE = "\"";
const escapeToChar = new Map([
[CHAR_DOUBLE_QUOTE, QUOTE],
[CHAR_BACKSLASH, "\\"],
[CHAR_SLASH, "/"],
[CHAR_LOWER_B, "\b"],
[CHAR_LOWER_N, "\n"],
[CHAR_LOWER_F, "\f"],
[CHAR_LOWER_R, "\r"],
[CHAR_LOWER_T, " "]
]);
const json5EscapeToChar = new Map([
...escapeToChar,
[CHAR_LOWER_V, "\v"],
[CHAR_0, "\0"]
]);
const charToEscape = new Map([
[QUOTE, QUOTE],
["\\", "\\"],
["/", "/"],
["\b", "b"],
["\n", "n"],
["\f", "f"],
["\r", "r"],
[" ", "t"]
]);
const json5CharToEscape = new Map([
...charToEscape,
["\v", "v"],
["\0", "0"],
["\u2028", "u2028"],
["\u2029", "u2029"]
]);
/** @type {Map<string,TokenType>} */
const knownTokenTypes = new Map([
[LBRACKET, "LBracket"],
[RBRACKET, "RBracket"],
[LBRACE, "LBrace"],
[RBRACE, "RBrace"],
[COLON, "Colon"],
[COMMA, "Comma"],
[TRUE, "Boolean"],
[FALSE, "Boolean"],
[NULL, "Null"]
]);
/** @type {Map<string,TokenType>} */
const knownJSON5TokenTypes = new Map([
...knownTokenTypes,
[NAN$1, "Number"],
[INFINITY$1, "Number"]
]);
const json5LineTerminators = new Set([
CHAR_NEWLINE,
CHAR_RETURN,
CHAR_LINE_SEPARATOR,
CHAR_PARAGRAPH_SEPARATOR
]);
/**
* @fileoverview JSON tokenization/parsing errors
* @author Nicholas C. Zakas
*/
/** @typedef {import("./typedefs.js").Location} Location */
/** @typedef {import("./typedefs.js").Token} Token */
/**
* Base class that attaches location to an error.
*/
var ErrorWithLocation = class extends Error {
/**
* Creates a new instance.
* @param {string} message The error message to report.
* @param {Location} loc The location information for the error.
*/
constructor(message, { line, column, offset }) {
super(`${message} (${line}:${column})`);
/**
* The line on which the error occurred.
* @type {number}
*/
this.line = line;
/**
* The column on which the error occurred.
* @type {number}
*/
this.column = column;
/**
* The index into the string where the error occurred.
* @type {number}
*/
this.offset = offset;
}
};
/**
* Error thrown when an unexpected character is found during tokenizing.
*/
var UnexpectedChar = class extends ErrorWithLocation {
/**
* Creates a new instance.
* @param {number} unexpected The character that was found.
* @param {Location} loc The location information for the found character.
*/
constructor(unexpected, loc) {
super(`Unexpected character '${String.fromCharCode(unexpected)}' found.`, loc);
}
};
/**
* Error thrown when an unexpected identifier is found during tokenizing.
*/
var UnexpectedIdentifier = class extends ErrorWithLocation {
/**
* Creates a new instance.
* @param {string} unexpected The character that was found.
* @param {Location} loc The location information for the found character.
*/
constructor(unexpected, loc) {
super(`Unexpected identifier '${unexpected}' found.`, loc);
}
};
/**
* Error thrown when an unexpected token is found during parsing.
*/
var UnexpectedToken = class extends ErrorWithLocation {
/**
* Creates a new instance.
* @param {Token} token The token that was found.
*/
constructor(token) {
super(`Unexpected token ${token.type} found.`, token.loc.start);
}
};
/**
* Error thrown when the end of input is found where it isn't expected.
*/
var UnexpectedEOF = class extends ErrorWithLocation {
/**
* Creates a new instance.
* @param {Location} loc The location information for the found character.
*/
constructor(loc) {
super("Unexpected end of input found.", loc);
}
};
const ID_Start = /[\xAA\xB5\xBA\xC0-\xD6\xD8-\xF6\xF8-\u02C1\u02C6-\u02D1\u02E0-\u02E4\u02EC\u02EE\u0370-\u0374\u0376\u0377\u037A-\u037D\u037F\u0386\u0388-\u038A\u038C\u038E-\u03A1\u03A3-\u03F5\u03F7-\u0481\u048A-\u052F\u0531-\u0556\u0559\u0561-\u0587\u05D0-\u05EA\u05F0-\u05F2\u0620-\u064A\u066E\u066F\u0671-\u06D3\u06D5\u06E5\u06E6\u06EE\u06EF\u06FA-\u06FC\u06FF\u0710\u0712-\u072F\u074D-\u07A5\u07B1\u07CA-\u07EA\u07F4\u07F5\u07FA\u0800-\u0815\u081A\u0824\u0828\u0840-\u0858\u0860-\u086A\u08A0-\u08B4\u08B6-\u08BD\u0904-\u0939\u093D\u0950\u0958-\u0961\u0971-\u0980\u0985-\u098C\u098F\u0990\u0993-\u09A8\u09AA-\u09B0\u09B2\u09B6-\u09B9\u09BD\u09CE\u09DC\u09DD\u09DF-\u09E1\u09F0\u09F1\u09FC\u0A05-\u0A0A\u0A0F\u0A10\u0A13-\u0A28\u0A2A-\u0A30\u0A32\u0A33\u0A35\u0A36\u0A38\u0A39\u0A59-\u0A5C\u0A5E\u0A72-\u0A74\u0A85-\u0A8D\u0A8F-\u0A91\u0A93-\u0AA8\u0AAA-\u0AB0\u0AB2\u0AB3\u0AB5-\u0AB9\u0ABD\u0AD0\u0AE0\u0AE1\u0AF9\u0B05-\u0B0C\u0B0F\u0B10\u0B13-\u0B28\u0B2A-\u0B30\u0B32\u0B33\u0B35-\u0B39\u0B3D\u0B5C\u0B5D\u0B5F-\u0B61\u0B71\u0B83\u0B85-\u0B8A\u0B8E-\u0B90\u0B92-\u0B95\u0B99\u0B9A\u0B9C\u0B9E\u0B9F\u0BA3\u0BA4\u0BA8-\u0BAA\u0BAE-\u0BB9\u0BD0\u0C05-\u0C0C\u0C0E-\u0C10\u0C12-\u0C28\u0C2A-\u0C39\u0C3D\u0C58-\u0C5A\u0C60\u0C61\u0C80\u0C85-\u0C8C\u0C8E-\u0C90\u0C92-\u0CA8\u0CAA-\u0CB3\u0CB5-\u0CB9\u0CBD\u0CDE\u0CE0\u0CE1\u0CF1\u0CF2\u0D05-\u0D0C\u0D0E-\u0D10\u0D12-\u0D3A\u0D3D\u0D4E\u0D54-\u0D56\u0D5F-\u0D61\u0D7A-\u0D7F\u0D85-\u0D96\u0D9A-\u0DB1\u0DB3-\u0DBB\u0DBD\u0DC0-\u0DC6\u0E01-\u0E30\u0E32\u0E33\u0E40-\u0E46\u0E81\u0E82\u0E84\u0E87\u0E88\u0E8A\u0E8D\u0E94-\u0E97\u0E99-\u0E9F\u0EA1-\u0EA3\u0EA5\u0EA7\u0EAA\u0EAB\u0EAD-\u0EB0\u0EB2\u0EB3\u0EBD\u0EC0-\u0EC4\u0EC6\u0EDC-\u0EDF\u0F00\u0F40-\u0F47\u0F49-\u0F6C\u0F88-\u0F8C\u1000-\u102A\u103F\u1050-\u1055\u105A-\u105D\u1061\u1065\u1066\u106E-\u1070\u1075-\u1081\u108E\u10A0-\u10C5\u10C7\u10CD\u10D0-\u10FA\u10FC-\u1248\u124A-\u124D\u1250-\u1256\u1258\u125A-\u125D\u1260-\u1288\u128A-\u128D\u1290-\u12B0\u12B2-\u12B5\u12B8-\u12BE\u12C0\u12C2-\u12C5\u12C8-\u12D6\u12D8-\u1310\u1312-\u1315\u1318-\u135A\u1380-\u138F\u13A0-\u13F5\u13F8-\u13FD\u1401-\u166C\u166F-\u167F\u1681-\u169A\u16A0-\u16EA\u16EE-\u16F8\u1700-\u170C\u170E-\u1711\u1720-\u1731\u1740-\u1751\u1760-\u176C\u176E-\u1770\u1780-\u17B3\u17D7\u17DC\u1820-\u1877\u1880-\u1884\u1887-\u18A8\u18AA\u18B0-\u18F5\u1900-\u191E\u1950-\u196D\u1970-\u1974\u1980-\u19AB\u19B0-\u19C9\u1A00-\u1A16\u1A20-\u1A54\u1AA7\u1B05-\u1B33\u1B45-\u1B4B\u1B83-\u1BA0\u1BAE\u1BAF\u1BBA-\u1BE5\u1C00-\u1C23\u1C4D-\u1C4F\u1C5A-\u1C7D\u1C80-\u1C88\u1CE9-\u1CEC\u1CEE-\u1CF1\u1CF5\u1CF6\u1D00-\u1DBF\u1E00-\u1F15\u1F18-\u1F1D\u1F20-\u1F45\u1F48-\u1F4D\u1F50-\u1F57\u1F59\u1F5B\u1F5D\u1F5F-\u1F7D\u1F80-\u1FB4\u1FB6-\u1FBC\u1FBE\u1FC2-\u1FC4\u1FC6-\u1FCC\u1FD0-\u1FD3\u1FD6-\u1FDB\u1FE0-\u1FEC\u1FF2-\u1FF4\u1FF6-\u1FFC\u2071\u207F\u2090-\u209C\u2102\u2107\u210A-\u2113\u2115\u2119-\u211D\u2124\u2126\u2128\u212A-\u212D\u212F-\u2139\u213C-\u213F\u2145-\u2149\u214E\u2160-\u2188\u2C00-\u2C2E\u2C30-\u2C5E\u2C60-\u2CE4\u2CEB-\u2CEE\u2CF2\u2CF3\u2D00-\u2D25\u2D27\u2D2D\u2D30-\u2D67\u2D6F\u2D80-\u2D96\u2DA0-\u2DA6\u2DA8-\u2DAE\u2DB0-\u2DB6\u2DB8-\u2DBE\u2DC0-\u2DC6\u2DC8-\u2DCE\u2DD0-\u2DD6\u2DD8-\u2DDE\u2E2F\u3005-\u3007\u3021-\u3029\u3031-\u3035\u3038-\u303C\u3041-\u3096\u309D-\u309F\u30A1-\u30FA\u30FC-\u30FF\u3105-\u312E\u3131-\u318E\u31A0-\u31BA\u31F0-\u31FF\u3400-