UNPKG

@terrazzo/parser

Version:

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

188 lines 8.68 kB
import { getObjMember } from '@terrazzo/json-schema-tools'; import { isAlias } from '@terrazzo/token-tools'; import { docsLink } from '../lib/docs.js'; export const VALID_DIMENSION = 'core/valid-dimension'; const ERROR_FORMAT = 'ERROR_FORMAT'; const ERROR_INVALID_PROP = 'ERROR_INVALID_PROP'; const ERROR_LEGACY = 'ERROR_LEGACY'; const ERROR_UNIT = 'ERROR_UNIT'; const ERROR_VALUE = 'ERROR_VALUE'; const rule = { meta: { messages: { [ERROR_FORMAT]: 'Invalid dimension: {{ value }}. Expected object with "value" and "unit".', [ERROR_LEGACY]: 'Migrate to the new object format: { "value": 10, "unit": "px" }.', [ERROR_UNIT]: 'Unit {{ unit }} not allowed. Expected {{ allowed }}.', [ERROR_INVALID_PROP]: 'Unknown property {{ key }}.', [ERROR_VALUE]: 'Expected number, received {{ value }}.', }, docs: { description: 'Require dimension tokens to follow the format', url: docsLink(VALID_DIMENSION), }, }, defaultOptions: { legacyFormat: false, allowedUnits: ['px', 'em', 'rem'], }, create({ tokens, options, report }) { for (const t of Object.values(tokens)) { if (t.aliasOf || !t.originalValue) { continue; } switch (t.$type) { case 'dimension': { validateDimension(t.originalValue.$value, { node: getObjMember(t.source.node, '$value'), filename: t.source.filename, options, report, }); break; } case 'strokeStyle': { if (typeof t.originalValue.$value === 'object' && Array.isArray(t.originalValue.$value.dashArray)) { const $valueNode = getObjMember(t.source.node, '$value'); const dashArray = getObjMember($valueNode, 'dashArray'); for (let i = 0; i < t.originalValue.$value.dashArray.length; i++) { if (isAlias(t.originalValue.$value.dashArray[i])) { continue; } validateDimension(t.originalValue.$value.dashArray[i], { node: dashArray.elements[i].value, filename: t.source.filename, options, report, }); } } break; } case 'border': { const $valueNode = getObjMember(t.source.node, '$value'); if (typeof t.originalValue.$value === 'object') { if (t.originalValue.$value.width && !isAlias(t.originalValue.$value.width)) { validateDimension(t.originalValue.$value.width, { node: getObjMember($valueNode, 'width'), filename: t.source.filename, options, report, }); } if (typeof t.originalValue.$value.style === 'object' && Array.isArray(t.originalValue.$value.style.dashArray)) { const style = getObjMember($valueNode, 'style'); const dashArray = getObjMember(style, 'dashArray'); for (let i = 0; i < t.originalValue.$value.style.dashArray.length; i++) { if (isAlias(t.originalValue.$value.style.dashArray[i])) { continue; } validateDimension(t.originalValue.$value.style.dashArray[i], { node: dashArray.elements[i].value, filename: t.source.filename, options, report, }); } } } break; } case 'shadow': { if (t.originalValue.$value && typeof t.originalValue.$value === 'object') { const $valueNode = getObjMember(t.source.node, '$value'); const valueArray = Array.isArray(t.originalValue.$value) ? t.originalValue.$value : [t.originalValue.$value]; for (let i = 0; i < valueArray.length; i++) { const node = $valueNode.type === 'Array' ? $valueNode.elements[i].value : $valueNode; for (const property of ['offsetX', 'offsetY', 'blur', 'spread']) { if (isAlias(valueArray[i][property])) { continue; } validateDimension(valueArray[i][property], { node: getObjMember(node, property), filename: t.source.filename, options, report, }); } } } break; } case 'typography': { const $valueNode = getObjMember(t.source.node, '$value'); if (typeof t.originalValue.$value === 'object') { for (const property of ['fontSize', 'lineHeight', 'letterSpacing']) { if (property in t.originalValue.$value) { if (isAlias(t.originalValue.$value[property]) || // special case: lineHeight may be a number (property === 'lineHeight' && typeof t.originalValue.$value[property] === 'number')) { continue; } validateDimension(t.originalValue.$value[property], { node: getObjMember($valueNode, property), filename: t.source.filename, options, report, }); } } } break; } } } }, }; function validateDimension(value, { node, filename, options, report, }) { if (value && typeof value === 'object') { for (const key of Object.keys(value)) { if (!['value', 'unit'].includes(key)) { report({ messageId: ERROR_INVALID_PROP, data: { key: JSON.stringify(key) }, node: getObjMember(node, key) ?? node, filename, }); } } const { unit, value: numValue } = value; if (!('value' in value || 'unit' in value)) { report({ messageId: ERROR_FORMAT, data: { value }, node, filename }); return; } if (!options.allowedUnits.includes(unit)) { report({ messageId: ERROR_UNIT, data: { unit, allowed: new Intl.ListFormat('en-us', { type: 'disjunction' }).format(options.allowedUnits), }, node: getObjMember(node, 'unit') ?? node, filename, }); } if (!Number.isFinite(numValue)) { report({ messageId: ERROR_VALUE, data: { value }, node: getObjMember(node, 'value') ?? node, filename, }); } } else if (typeof value === 'string' && !options.legacyFormat) { report({ messageId: ERROR_LEGACY, node, filename }); } else { report({ messageId: ERROR_FORMAT, data: { value }, node, filename }); } } export default rule; //# sourceMappingURL=valid-dimension.js.map