diff --git a/packages/@stylexjs/babel-plugin/__tests__/evaluation-import-test.js b/packages/@stylexjs/babel-plugin/__tests__/evaluation-import-test.js index dc0fecb89..77c26aeec 100644 --- a/packages/@stylexjs/babel-plugin/__tests__/evaluation-import-test.js +++ b/packages/@stylexjs/babel-plugin/__tests__/evaluation-import-test.js @@ -476,4 +476,264 @@ describe('Evaluation of imported values works based on configuration', () => { `); }); }); + + describe('Arithmetic on imported tokens compiles to calc()', () => { + const varName = (key) => + `var(--${options.classNamePrefix}${hash( + `otherFile.stylex.js//MyTheme.${key}`, + )})`; + + const transformStyle = (value) => + transform(` + import stylex from 'stylex'; + import { MyTheme } from 'otherFile.stylex'; + const styles = stylex.create({ + box: { + zIndex: ${value}, + } + }); + stylex(styles.box); + `).code; + + test('token + token', () => { + expect(transformStyle('MyTheme.a + MyTheme.b')).toContain( + `calc(${varName('a')} + ${varName('b')})`, + ); + }); + + test('token + number and number + token', () => { + expect(transformStyle('MyTheme.a + 4')).toContain( + `calc(${varName('a')} + 4)`, + ); + expect(transformStyle('4 + MyTheme.a')).toContain( + `calc(4 + ${varName('a')})`, + ); + }); + + test('token - number, token * number, token / number', () => { + expect(transformStyle('MyTheme.a - 1')).toContain( + `calc(${varName('a')} - 1)`, + ); + expect(transformStyle('MyTheme.a * 2')).toContain( + `calc(${varName('a')} * 2)`, + ); + expect(transformStyle('MyTheme.a / 2')).toContain( + `calc(${varName('a')} / 2)`, + ); + }); + + test('unary minus on a token', () => { + expect(transformStyle('-MyTheme.a')).toContain( + `calc(-1 * ${varName('a')})`, + ); + }); + + test('nested arithmetic flattens to parens', () => { + expect(transformStyle('MyTheme.a + MyTheme.b * MyTheme.c')).toContain( + `calc(${varName('a')} + (${varName('b')} * ${varName('c')}))`, + ); + expect(transformStyle('(MyTheme.a + MyTheme.b) * 2')).toContain( + `calc((${varName('a')} + ${varName('b')}) * 2)`, + ); + expect( + transformStyle('(MyTheme.a + (MyTheme.b - MyTheme.c)) / 2'), + ).toContain( + `calc((${varName('a')} + (${varName('b')} - ${varName('c')})) / 2)`, + ); + }); + + test('token + jammed string throws instead of emitting broken CSS', () => { + // A unit-string operand is excluded from calc() addition on purpose: + // `token + '4px'` vs `token + ' 4px'` (list shorthand) must not + // silently mean different things. And since the concatenation + // 'var(--x)10px' is invalid CSS, it fails instead. + expect(() => transformStyle("MyTheme.a + '10px'")).toThrow( + /would\s+produce invalid CSS/, + ); + expect(() => transformStyle("MyTheme.a + 'px'")).toThrow( + /would\s+produce invalid CSS/, + ); + expect(() => transformStyle("'10px' + MyTheme.a")).toThrow( + /would\s+produce invalid CSS/, + ); + }); + + test('token + separated string stays list concatenation', () => { + const code = transformStyle("MyTheme.a + ' 4px'"); + expect(code).not.toContain('calc'); + expect(code).toContain(`${varName('a')} 4px`); + }); + + test('string concatenation with non-numeric strings is preserved', () => { + const code = transform(` + import stylex from 'stylex'; + import { MyTheme } from 'otherFile.stylex'; + const styles = stylex.create({ + box: { + fontFamily: 'Arial, ' + MyTheme.font, + } + }); + stylex(styles.box); + `).code; + // The whitespace normalizer removes the space after the comma. + expect(code).toContain(`Arial,${varName('font')}`); + expect(code).not.toContain('calc'); + }); + + test('function-like string concatenation around a token is preserved', () => { + const code = transform(` + import stylex from 'stylex'; + import { MyTheme } from 'otherFile.stylex'; + const styles = stylex.create({ + box: { + transform: 'translateX(' + MyTheme.a + ')', + } + }); + stylex(styles.box); + `).code; + expect(code).toContain(`translateX(${varName('a')})`); + expect(code).not.toContain('calc'); + }); + + test('template literal interpolation uses the same concat rules', () => { + expect(() => + transform(` + import stylex from 'stylex'; + import { MyTheme } from 'otherFile.stylex'; + const styles = stylex.create({ + box: { + width: \`\${MyTheme.a}px\`, + } + }); + stylex(styles.box); + `), + ).toThrow(/would\s+produce invalid CSS/); + + const code = transform(` + import stylex from 'stylex'; + import { MyTheme } from 'otherFile.stylex'; + const styles = stylex.create({ + box: { + margin: \`\${MyTheme.a} 4px\`, + } + }); + stylex(styles.box); + `).code; + expect(code).toContain(`${varName('a')} 4px`); + expect(code).not.toContain('calc'); + }); + + test('unsupported operators throw a compile error', () => { + const unsupported = [ + 'MyTheme.a % 2', + 'MyTheme.a ** 2', + 'MyTheme.a & 1', + '~MyTheme.a', + '!MyTheme.a', + '+MyTheme.a', + ]; + for (const value of unsupported) { + expect(() => transformStyle(value)).toThrow( + /cannot be applied to a StyleX variable or constant/, + ); + } + }); + + test('comparisons on tokens throw a compile error', () => { + expect(() => transformStyle('MyTheme.a > MyTheme.b ? 1 : 2')).toThrow( + /cannot be compared with ">" at compile time/, + ); + expect(() => transformStyle("MyTheme.a === 'red' ? 1 : 2")).toThrow( + /cannot be compared with "===" at compile time/, + ); + }); + + test('null and undefined guards on tokens still compile', () => { + expect(transformStyle('MyTheme.a != null ? 5 : 7')).toContain( + 'z-index:5', + ); + expect(transformStyle('MyTheme.a === undefined ? 5 : 7')).toContain( + 'z-index:7', + ); + expect(transformStyle('MyTheme.a ?? 7')).toContain( + `z-index:${varName('a')}`, + ); + }); + + test('arithmetic in a computed style key throws a compile error', () => { + expect(() => + transform(` + import stylex from 'stylex'; + import { MyTheme } from 'otherFile.stylex'; + const styles = stylex.create({ + box: { + [MyTheme.a + MyTheme.b]: 'red', + } + }); + stylex(styles.box); + `), + ).toThrow(/cannot be used as a style property key/); + }); + + test('token misuse inside a dynamic style throws instead of degrading', () => { + expect(() => + transform(` + import stylex from 'stylex'; + import { MyTheme } from 'otherFile.stylex'; + const styles = stylex.create({ + box: (opacity) => ({ + opacity, + zIndex: MyTheme.a === 'big' ? 1 : 2, + }), + }); + stylex.props(styles.box(0.5)); + `), + ).toThrow(/cannot be compared with "===" at compile time/); + }); + + test('unicode custom property keys work with arithmetic', () => { + const { metadata } = transform(` + import stylex from 'stylex'; + import { MyTheme } from 'otherFile.stylex'; + const styles = stylex.create({ + box: { + zIndex: MyTheme['--größe'] * 2, + } + }); + stylex(styles.box); + `); + expect(metadata.stylex[0][1].ltr).toContain('calc(var(--größe) * 2)'); + }); + + test('arithmetic with a non-numeric operand throws a compile error', () => { + expect(() => transformStyle("MyTheme.a - 'foo'")).toThrow( + /requires the other operand/, + ); + expect(() => transformStyle("MyTheme.a * 'auto'")).toThrow( + /requires the other operand/, + ); + }); + + test('Number() wrapped token arithmetic in a local constant compiles to calc()', () => { + const code = transform(` + import stylex from 'stylex'; + import { MyTheme } from 'otherFile.stylex'; + const PRESENTER_Z_INDEX = Number(MyTheme.dialog) + 1; + const styles = stylex.create({ + box: { + zIndex: PRESENTER_Z_INDEX, + } + }); + stylex(styles.box); + `).code; + + expect(code).toContain(`calc(${varName('dialog')} + 1)`); + }); + + test('global numeric functions on tokens throw instead of coercing to NaN', () => { + expect(() => transformStyle('Math.round(MyTheme.a) + 1')).toThrow( + /"Math.round" function cannot be applied/, + ); + }); + }); }); diff --git a/packages/@stylexjs/babel-plugin/__tests__/transform-process-test.js b/packages/@stylexjs/babel-plugin/__tests__/transform-process-test.js index 58c30ac1f..3d989d163 100644 --- a/packages/@stylexjs/babel-plugin/__tests__/transform-process-test.js +++ b/packages/@stylexjs/babel-plugin/__tests__/transform-process-test.js @@ -1230,4 +1230,116 @@ describe('@stylexjs/babel-plugin', () => { `); }); }); + + describe('[transform] arithmetic on imported defineConsts (#1597)', () => { + function transformCrossFile(mainSource) { + const pluginOpts = { + unstable_moduleResolution: { type: 'haste' }, + }; + + const tokens = transformSync( + ` + import * as stylex from '@stylexjs/stylex'; + export const consts = stylex.defineConsts({ + A: 26, + B: 14, + D: 6, + gutter: '16px', + }); + export const vars = stylex.defineVars({ + gap: '8px', + }); + `, + { + filename: '/src/app/constants.stylex.js', + parserOpts: { flow: 'all' }, + babelrc: false, + plugins: [[stylexPlugin, pluginOpts]], + }, + ); + + const main = transformSync(mainSource, { + filename: '/src/app/main.js', + parserOpts: { flow: 'all' }, + babelrc: false, + plugins: [[stylexPlugin, pluginOpts]], + }); + + return [ + ...(tokens.metadata.stylex || []), + ...(main.metadata.stylex || []), + ]; + } + + test('numeric const arithmetic resolves to calc() with literal values', () => { + const metadata = transformCrossFile(` + import * as stylex from '@stylexjs/stylex'; + import { consts } from 'constants.stylex'; + export const styles = stylex.create({ + box: { + zIndex: consts.A + consts.B - consts.D, + opacity: consts.A / 4, + }, + }); + `); + + const css = stylexPlugin.processStylexRules(metadata, { + useLayers: false, + }); + expect(css).toContain('z-index:calc((26 + 14) - 6)'); + expect(css).toContain('opacity:calc(26 / 4)'); + }); + + test('unit const arithmetic stays as calc() with substituted values', () => { + const metadata = transformCrossFile(` + import * as stylex from '@stylexjs/stylex'; + import { consts } from 'constants.stylex'; + export const styles = stylex.create({ + box: { + paddingTop: consts.gutter * 2, + }, + }); + `); + + const css = stylexPlugin.processStylexRules(metadata, { + useLayers: false, + }); + expect(css).toContain('padding-top:calc(16px * 2)'); + }); + + test('mixed const and defineVars arithmetic keeps the var() in calc()', () => { + const metadata = transformCrossFile(` + import * as stylex from '@stylexjs/stylex'; + import { consts, vars } from 'constants.stylex'; + export const styles = stylex.create({ + box: { + marginTop: consts.A * vars.gap, + }, + }); + `); + + const css = stylexPlugin.processStylexRules(metadata, { + useLayers: false, + }); + expect(css).toMatch(/margin-top:calc\(26 \* var\(--[a-z0-9]+\)\)/); + }); + + test('Number() wrapped const arithmetic in a local constant resolves to calc()', () => { + const metadata = transformCrossFile(` + import * as stylex from '@stylexjs/stylex'; + import { consts } from 'constants.stylex'; + const PRESENTER_Z_INDEX = Number(consts.A) + 1; + export const styles = stylex.create({ + box: { + zIndex: PRESENTER_Z_INDEX, + }, + }); + `); + + const css = stylexPlugin.processStylexRules(metadata, { + useLayers: false, + }); + expect(css).toContain('z-index:calc(26 + 1)'); + }); + }); }); diff --git a/packages/@stylexjs/babel-plugin/__tests__/transform-stylex-defineVars-test.js b/packages/@stylexjs/babel-plugin/__tests__/transform-stylex-defineVars-test.js index 758c011c0..f953098f0 100644 --- a/packages/@stylexjs/babel-plugin/__tests__/transform-stylex-defineVars-test.js +++ b/packages/@stylexjs/babel-plugin/__tests__/transform-stylex-defineVars-test.js @@ -597,6 +597,20 @@ describe('@stylexjs/babel-plugin', () => { `); }); + test('arithmetic on same-group references compiles to calc()', () => { + const { metadata } = transform(` + import * as stylex from '@stylexjs/stylex'; + export const layout = stylex.defineVars({ + gap: '8px', + doubleGap: () => layout.gap * 2, + }); + `); + + expect(metadata.stylex[0][1].ltr).toContain( + '--x1gpkec6:calc(var(--x1kbodq4) * 2)', + ); + }); + test('same-group references can point to later keys', () => { const { code, metadata } = transform(` import * as stylex from '@stylexjs/stylex'; diff --git a/packages/@stylexjs/babel-plugin/__tests__/validation-stylex-create-test.js b/packages/@stylexjs/babel-plugin/__tests__/validation-stylex-create-test.js index 9748d0d56..75393f47a 100644 --- a/packages/@stylexjs/babel-plugin/__tests__/validation-stylex-create-test.js +++ b/packages/@stylexjs/babel-plugin/__tests__/validation-stylex-create-test.js @@ -112,7 +112,7 @@ describe('@stylexjs/babel-plugin', () => { } }); `); - }).toThrow(messages.nonStaticValue('create')); + }).toThrow('Referenced constant is not defined.'); }); /* Style rules */ diff --git a/packages/@stylexjs/babel-plugin/__tests__/validation-stylex-createTheme-test.js b/packages/@stylexjs/babel-plugin/__tests__/validation-stylex-createTheme-test.js index ca02c5f93..c2ded2759 100644 --- a/packages/@stylexjs/babel-plugin/__tests__/validation-stylex-createTheme-test.js +++ b/packages/@stylexjs/babel-plugin/__tests__/validation-stylex-createTheme-test.js @@ -66,7 +66,7 @@ describe('@stylexjs/babel-plugin', () => { import stylex from 'stylex'; const variables = stylex.createTheme(genStyles(), {}); `); - }).toThrow(messages.nonStaticValue('createTheme')); + }).toThrow('Referenced constant is not defined.'); expect(() => { transform(` @@ -82,7 +82,7 @@ describe('@stylexjs/babel-plugin', () => { import stylex from 'stylex'; const variables = stylex.createTheme({__varGroupHash__: 'x568ih9'}, genStyles()); `); - }).toThrow(messages.nonStaticValue('createTheme')); + }).toThrow('Referenced constant is not defined.'); expect(() => { transform(` @@ -102,7 +102,7 @@ describe('@stylexjs/babel-plugin', () => { {__varGroupHash__: 'x568ih9', labelColor: 'var(--labelColorHash)'}, {[labelColor]: 'red',}); `); - }).toThrow(messages.nonStaticValue('createTheme')); + }).toThrow('Referenced constant is not defined.'); }); /* Values */ @@ -139,7 +139,7 @@ describe('@stylexjs/babel-plugin', () => { {labelColor: labelColor,} ); `); - }).toThrow(messages.nonStaticValue('createTheme')); + }).toThrow('Referenced constant is not defined.'); expect(() => { transform(` @@ -149,7 +149,7 @@ describe('@stylexjs/babel-plugin', () => { {labelColor: labelColor(),} ); `); - }).toThrow(messages.nonStaticValue('createTheme')); + }).toThrow('Referenced constant is not defined.'); }); }); }); diff --git a/packages/@stylexjs/babel-plugin/__tests__/validation-stylex-defineConsts-test.js b/packages/@stylexjs/babel-plugin/__tests__/validation-stylex-defineConsts-test.js index 72204f3c7..0cf7ba886 100644 --- a/packages/@stylexjs/babel-plugin/__tests__/validation-stylex-defineConsts-test.js +++ b/packages/@stylexjs/babel-plugin/__tests__/validation-stylex-defineConsts-test.js @@ -98,7 +98,7 @@ describe('@stylexjs/babel-plugin', () => { import * as stylex from '@stylexjs/stylex'; export const constants = stylex.defineConsts(genStyles()); `); - }).toThrow(messages.nonStaticValue('defineConsts')); + }).toThrow('Referenced constant is not defined.'); }); test('valid argument: object', () => { @@ -181,7 +181,7 @@ describe('@stylexjs/babel-plugin', () => { [labelColor]: 'red', }); `); - }).toThrow(messages.nonStaticValue('defineConsts')); + }).toThrow('Referenced constant is not defined.'); }); /* Values */ @@ -194,7 +194,7 @@ describe('@stylexjs/babel-plugin', () => { labelColor: labelColor, }); `); - }).toThrow(messages.nonStaticValue('defineConsts')); + }).toThrow('Referenced constant is not defined.'); expect(() => { transform(` @@ -203,7 +203,7 @@ describe('@stylexjs/babel-plugin', () => { labelColor: labelColor(), }); `); - }).toThrow(messages.nonStaticValue('defineConsts')); + }).toThrow('Referenced constant is not defined.'); }); test('valid value: number', () => { diff --git a/packages/@stylexjs/babel-plugin/__tests__/validation-stylex-defineVars-test.js b/packages/@stylexjs/babel-plugin/__tests__/validation-stylex-defineVars-test.js index 9a33aeb9e..4157d7260 100644 --- a/packages/@stylexjs/babel-plugin/__tests__/validation-stylex-defineVars-test.js +++ b/packages/@stylexjs/babel-plugin/__tests__/validation-stylex-defineVars-test.js @@ -91,7 +91,7 @@ describe('@stylexjs/babel-plugin', () => { import * as stylex from '@stylexjs/stylex'; export const vars = stylex.defineVars(genStyles()); `); - }).toThrow(messages.nonStaticValue('defineVars')); + }).toThrow('Referenced constant is not defined.'); }); test('valid argument: object', () => { @@ -163,7 +163,7 @@ describe('@stylexjs/babel-plugin', () => { [labelColor]: 'red', }); `); - }).toThrow(messages.nonStaticValue('defineVars')); + }).toThrow('Referenced constant is not defined.'); }); /* Values */ @@ -176,7 +176,7 @@ describe('@stylexjs/babel-plugin', () => { labelColor: labelColor, }); `); - }).toThrow(messages.nonStaticValue('defineVars')); + }).toThrow('Referenced constant is not defined.'); expect(() => { transform(` @@ -185,7 +185,7 @@ describe('@stylexjs/babel-plugin', () => { labelColor: labelColor(), }); `); - }).toThrow(messages.nonStaticValue('defineVars')); + }).toThrow('Referenced constant is not defined.'); }); test('valid value: number', () => { @@ -257,7 +257,7 @@ describe('@stylexjs/babel-plugin', () => { textMuted: () => getColor(colors.text), }); `); - }).toThrow(messages.nonStaticValue('defineVars')); + }).toThrow('Referenced constant is not defined.'); }); test('valid function value: returns stylex.types', () => { diff --git a/packages/@stylexjs/babel-plugin/src/shared/utils/transform-value.js b/packages/@stylexjs/babel-plugin/src/shared/utils/transform-value.js index d9340b265..2c50443ed 100644 --- a/packages/@stylexjs/babel-plugin/src/shared/utils/transform-value.js +++ b/packages/@stylexjs/babel-plugin/src/shared/utils/transform-value.js @@ -14,6 +14,11 @@ import normalizeValue from './normalize-value'; /** * Convert a CSS value in JS to the final CSS string value */ +// Numbers are rendered into CSS with at most 4 decimal places. +export function roundForCss(value: number): number { + return Math.round(value * 10000) / 10000; +} + export default function transformValue( key: string, rawValue: string | number, @@ -21,7 +26,7 @@ export default function transformValue( ): string { const value = typeof rawValue === 'number' - ? String(Math.round(rawValue * 10000) / 10000) + getNumberSuffix(key) + ? String(roundForCss(rawValue)) + getNumberSuffix(key) : rawValue; if ( diff --git a/packages/@stylexjs/babel-plugin/src/utils/__tests__/css-calc-test.js b/packages/@stylexjs/babel-plugin/src/utils/__tests__/css-calc-test.js new file mode 100644 index 000000000..2fcbf6c43 --- /dev/null +++ b/packages/@stylexjs/babel-plugin/src/utils/__tests__/css-calc-test.js @@ -0,0 +1,105 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow strict + */ + +import { + isCssVarOrCalc, + isCalcTerm, + buildBinaryCalc, + buildUnaryMinusCalc, +} from '../css-calc'; + +describe('isCssVarOrCalc', () => { + test('matches full-string var() references', () => { + expect(isCssVarOrCalc('var(--x568ih9)')).toBe(true); + expect(isCssVarOrCalc('var(--foreground-x568ih9)')).toBe(true); + }); + + test('matches balanced calc() expressions', () => { + expect(isCssVarOrCalc('calc(var(--a) + var(--b))')).toBe(true); + expect(isCssVarOrCalc('calc((1 + 2) * 3)')).toBe(true); + }); + + test('rejects partial or compound strings', () => { + expect(isCssVarOrCalc('var(--a) var(--b)')).toBe(false); + expect(isCssVarOrCalc('calc(1) + calc(2)')).toBe(false); + expect(isCssVarOrCalc('solid var(--a)')).toBe(false); + expect(isCssVarOrCalc('10px')).toBe(false); + expect(isCssVarOrCalc(10)).toBe(false); + expect(isCssVarOrCalc(null)).toBe(false); + }); +}); + +describe('isCalcTerm', () => { + test('accepts finite numbers', () => { + expect(isCalcTerm(10)).toBe(true); + expect(isCalcTerm(-0.5)).toBe(true); + expect(isCalcTerm(NaN)).toBe(false); + expect(isCalcTerm(Infinity)).toBe(false); + }); + + test('accepts var()/calc() strings', () => { + expect(isCalcTerm('var(--x568ih9)')).toBe(true); + expect(isCalcTerm('calc(var(--a) + 1)')).toBe(true); + }); + + test('accepts numeric strings with CSS units', () => { + expect(isCalcTerm('10px')).toBe(true); + expect(isCalcTerm('1.5rem')).toBe(true); + expect(isCalcTerm('50%')).toBe(true); + expect(isCalcTerm('-2em')).toBe(true); + expect(isCalcTerm('10')).toBe(true); + }); + + test('rejects non-numeric strings', () => { + expect(isCalcTerm('solid ')).toBe(false); + expect(isCalcTerm('auto')).toBe(false); + expect(isCalcTerm('px')).toBe(false); + expect(isCalcTerm('10px 20px')).toBe(false); + }); +}); + +describe('buildBinaryCalc', () => { + test('builds calc() from var operands', () => { + expect(buildBinaryCalc('var(--a)', '+', 'var(--b)')).toBe( + 'calc(var(--a) + var(--b))', + ); + }); + + test('builds calc() from mixed operands', () => { + expect(buildBinaryCalc('var(--a)', '*', 2)).toBe('calc(var(--a) * 2)'); + expect(buildBinaryCalc(4, '-', 'var(--a)')).toBe('calc(4 - var(--a))'); + expect(buildBinaryCalc('var(--a)', '+', '10px')).toBe( + 'calc(var(--a) + 10px)', + ); + }); + + test('rounds numeric operands to 4 decimals', () => { + expect(buildBinaryCalc(1 / 3, '*', 'var(--a)')).toBe( + 'calc(0.3333 * var(--a))', + ); + }); + + test('strips nested calc() operands down to parens', () => { + expect(buildBinaryCalc('calc(var(--a) + var(--b))', '*', 2)).toBe( + 'calc((var(--a) + var(--b)) * 2)', + ); + }); +}); + +describe('buildUnaryMinusCalc', () => { + test('negates a var reference', () => { + expect(buildUnaryMinusCalc('var(--a)')).toBe('calc(-1 * var(--a))'); + }); + + test('negates a calc expression', () => { + expect(buildUnaryMinusCalc('calc(var(--a) + 1)')).toBe( + 'calc(-1 * (var(--a) + 1))', + ); + }); +}); diff --git a/packages/@stylexjs/babel-plugin/src/utils/css-calc.js b/packages/@stylexjs/babel-plugin/src/utils/css-calc.js new file mode 100644 index 000000000..2a77f0201 --- /dev/null +++ b/packages/@stylexjs/babel-plugin/src/utils/css-calc.js @@ -0,0 +1,296 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow strict + */ + +import parser from 'postcss-value-parser'; +import { roundForCss } from '../shared/utils/transform-value'; +import * as errMsgs from './evaluation-errors'; + +// Custom property names may contain any character except ')' — including +// unicode identifiers, which `defineVars`/`defineConsts` pass through +// verbatim for keys that start with '--'. +const CSS_VAR_PATTERN = /^var\(--[^)]+\)$/; + +const CSS_UNITS: Set = new Set([ + // + 'px', + 'em', + 'rem', + 'ex', + 'ch', + 'cap', + 'ic', + 'lh', + 'rlh', + 'vw', + 'vh', + 'vmin', + 'vmax', + 'vb', + 'vi', + 'svw', + 'svh', + 'lvw', + 'lvh', + 'dvw', + 'dvh', + 'cm', + 'mm', + 'q', + 'in', + 'pt', + 'pc', + // + 'deg', + 'grad', + 'rad', + 'turn', + //