UNPKG

postcss-calc

Version:
114 lines (105 loc) 3.28 kB
// Folds @csstools/css-tokenizer output into the calc() token subset. import { tokenize as tokenizeCss, TokenType as CssType, } from '@csstools/css-tokenizer'; /** * @typedef {'number' | 'dimension' | 'ident' | 'punct' | 'eof'} TokenType * @typedef {object} Token * @property {TokenType} type * @property {string} value * @property {string} [unit] Present on `dimension` tokens; `%` for percentages. * @property {number} pos * @property {boolean} ws Whitespace immediately before — drives the §10.1 `+`/`-` rule. */ const PUNCT_DELIMS = new Set(['+', '-', '*', '/']); const NUMERIC_RAW = /^[+-]?(?:\d+(?:\.\d+)?|\.\d+)(?:[eE][+-]?\d+)?/; /** * @param {string} input * @return {Token[]} */ function tokenize(input) { /** @type {Token[]} */ const tokens = []; let ws = true; // CSS absorbs leading signs (`-5px` is one token); the parser expects // punct sign + unsigned numeric, so split them back out. /** * @param {string} raw * @param {string | undefined} unit * @param {number} pos * @return {void} */ function pushNumeric(raw, unit, pos) { let value = /** @type {RegExpExecArray} */ (NUMERIC_RAW.exec(raw))[0]; const sign = value[0]; if (sign === '+' || sign === '-') { tokens.push({ type: 'punct', value: sign, pos, ws }); value = value.slice(1); pos += 1; ws = false; } if (unit === undefined) { tokens.push({ type: 'number', value, pos, ws }); } else { tokens.push({ type: 'dimension', value, unit, pos, ws }); } ws = false; } for (const t of tokenizeCss({ css: input })) { switch (t[0]) { case CssType.Whitespace: case CssType.Comment: ws = true; continue; case CssType.Number: pushNumeric(t[1], undefined, t[2]); continue; case CssType.Dimension: pushNumeric(t[1], t[4].unit, t[2]); continue; case CssType.Percentage: pushNumeric(t[1], '%', t[2]); continue; case CssType.Ident: tokens.push({ type: 'ident', value: t[4].value, pos: t[2], ws }); break; case CssType.Function: tokens.push({ type: 'ident', value: t[4].value, pos: t[2], ws }); tokens.push({ type: 'punct', value: '(', pos: t[2] + t[1].length - 1, ws: false, }); break; case CssType.OpenParen: tokens.push({ type: 'punct', value: '(', pos: t[2], ws }); break; case CssType.CloseParen: tokens.push({ type: 'punct', value: ')', pos: t[2], ws }); break; case CssType.Comma: tokens.push({ type: 'punct', value: ',', pos: t[2], ws }); break; case CssType.Delim: if (!PUNCT_DELIMS.has(t[4].value)) { throw new Error( `Unexpected character "${t[4].value}" at position ${t[2]}` ); } tokens.push({ type: 'punct', value: t[4].value, pos: t[2], ws }); break; case CssType.EOF: tokens.push({ type: 'eof', value: '', pos: input.length, ws }); break; default: throw new Error( `Unexpected character "${t[1][0] ?? ''}" at position ${t[2]}` ); } ws = false; } return tokens; } export { tokenize };