UNPKG

@terrazzo/parser

Version:

Parser/validator for the Design Tokens Community Group (DTCG) standard.

1,486 lines (1,467 loc) 153 kB
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-