From bfd042058dcd91b9630a152df4b47c7737b5295c Mon Sep 17 00:00:00 2001 From: Robin Malfait Date: Wed, 26 Jul 2023 16:26:13 +0200 Subject: [PATCH] Improve normalisation of `calc()`-like functions (#11686) * parse the `calc()`-like expressions and format them * update changelog * Add test case for double negatives wanted to be sure this worked --------- Co-authored-by: Jordan Pittman --- CHANGELOG.md | 2 + src/util/dataTypes.js | 63 ++++++++++++++++++++++++------ tests/normalize-data-types.test.js | 20 ++++++++++ 3 files changed, 74 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 33bd811bd893..fb24ead44308 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed +- Improve normalisation of `calc()`-like functions ([#11686](https://github.com/tailwindlabs/tailwindcss/pull/11686)) + ## [3.3.3] - 2023-07-13 ### Fixed diff --git a/src/util/dataTypes.js b/src/util/dataTypes.js index 2f01aa3d20d0..03f6994e75ed 100644 --- a/src/util/dataTypes.js +++ b/src/util/dataTypes.js @@ -10,9 +10,6 @@ function isCSSFunction(value) { return cssFunctions.some((fn) => new RegExp(`^${fn}\\(.*\\)`).test(value)) } -const placeholder = '--tw-placeholder' -const placeholderRe = new RegExp(placeholder, 'g') - // This is not a data type, but rather a function that can normalize the // correct values. export function normalize(value, isRoot = true) { @@ -63,15 +60,59 @@ export function normalize(value, isRoot = true) { */ function normalizeMathOperatorSpacing(value) { return value.replace(/(calc|min|max|clamp)\(.+\)/g, (match) => { - let vars = [] + let result = '' - return match - .replace(/var\((--.+?)[,)]/g, (match, g1) => { - vars.push(g1) - return match.replace(g1, placeholder) - }) - .replace(/(-?\d*\.?\d(?!\b-\d.+[,)](?![^+\-/*])\D)(?:%|[a-z]+)?|\))([+\-/*])/g, '$1 $2 ') - .replace(placeholderRe, () => vars.shift()) + function lastChar() { + let char = result.trimEnd() + return char[char.length - 1] + } + + for (let i = 0; i < match.length; i++) { + function peek(word) { + return word.split('').every((char, j) => match[i + j] === char) + } + + function consumeUntil(chars) { + let minIndex = Infinity + for (let char of chars) { + let index = match.indexOf(char, i) + if (index !== -1 && index < minIndex) { + minIndex = index + } + } + + let result = match.slice(i, minIndex) + i += result.length - 1 + return result + } + + let char = match[i] + + // Handle `var(--variable)` + if (peek('var')) { + // When we consume until `)`, then we are dealing with this scenario: + // `var(--example)` + // + // When we consume until `,`, then we are dealing with this scenario: + // `var(--example, 1rem)` + // + // In this case we do want to "format", the default value as well + result += consumeUntil([')', ',']) + } + + // Handle operators + else if ( + ['+', '-', '*', '/'].includes(char) && + !['(', '+', '-', '*', '/'].includes(lastChar()) + ) { + result += ` ${char} ` + } else { + result += char + } + } + + // Simplify multiple spaces + return result.replace(/\s+/g, ' ') }) } diff --git a/tests/normalize-data-types.test.js b/tests/normalize-data-types.test.js index eff2fa677bcc..fdcd958a3366 100644 --- a/tests/normalize-data-types.test.js +++ b/tests/normalize-data-types.test.js @@ -42,6 +42,26 @@ let table = [ ['var(--my-var-with-more-than-3-words)', 'var(--my-var-with-more-than-3-words)'], ['var(--width, calc(100%+1rem))', 'var(--width, calc(100% + 1rem))'], + ['calc(1px*(7--12/24))', 'calc(1px * (7 - -12 / 24))'], + ['calc((7-32)/(1400-782))', 'calc((7 - 32) / (1400 - 782))'], + ['calc((7-3)/(1400-782))', 'calc((7 - 3) / (1400 - 782))'], + ['calc((7-32)/(1400-782))', 'calc((7 - 32) / (1400 - 782))'], + ['calc((70-3)/(1400-782))', 'calc((70 - 3) / (1400 - 782))'], + ['calc((70-32)/(1400-782))', 'calc((70 - 32) / (1400 - 782))'], + ['calc((704-3)/(1400-782))', 'calc((704 - 3) / (1400 - 782))'], + ['calc((704-320))', 'calc((704 - 320))'], + ['calc((704-320)/1)', 'calc((704 - 320) / 1)'], + ['calc((704-320)/(1400-782))', 'calc((704 - 320) / (1400 - 782))'], + ['calc(24px+-1rem)', 'calc(24px + -1rem)'], + ['calc(24px+(-1rem))', 'calc(24px + (-1rem))'], + ['calc(24px_+_-1rem)', 'calc(24px + -1rem)'], + ['calc(24px+(-1rem))', 'calc(24px + (-1rem))'], + ['calc(24px_+_(-1rem))', 'calc(24px + (-1rem))'], + [ + 'calc(var(--10-10px,calc(-20px-(-30px--40px)-50px)', + 'calc(var(--10-10px,calc(-20px - (-30px - -40px) - 50px)', + ], + // Misc ['color(0_0_0/1.0)', 'color(0 0 0/1.0)'], ['color(0_0_0_/_1.0)', 'color(0 0 0 / 1.0)'],