@terrazzo/parser
Version:
Parser/validator for the Design Tokens Community Group (DTCG) standard.
188 lines • 8.68 kB
JavaScript
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