Skip to content

Commit

Permalink
Fix parsing of theme() inside calc() when there are no spaces aro…
Browse files Browse the repository at this point in the history
…und operators (#11157)

* Refactor

* Don’t resolve functions for anything not using theme or screen

* Normalize math operators inside calc when handling functions

* Inline postcss-value-parser

* Treat all functions the same as calc

* Remove workaround for calc + operators without spaces

* Remove `postcss-value-parser` dependency

* Update lockfile

* Update sourcemaps

* Update changelog

* Update `value-parser` formatting

* Stop prettier from complaining
  • Loading branch information
thecrypticace authored May 4, 2023
1 parent cdca9cb commit 960cb40
Show file tree
Hide file tree
Showing 17 changed files with 893 additions and 145 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

- Fix issue where some pseudo-element variants generated the wrong selector ([#10943](https://github.com/tailwindlabs/tailwindcss/pull/10943), [#10962](https://github.com/tailwindlabs/tailwindcss/pull/10962))
- Make font settings propagate into buttons, inputs, etc. ([#10940](https://github.com/tailwindlabs/tailwindcss/pull/10940))
- Fix parsing of `theme()` inside `calc()` when there are no spaces around operators ([#11157](https://github.com/tailwindlabs/tailwindcss/pull/11157))

### Added

Expand Down
2 changes: 0 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion package-lock.stable.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@
"postcss-load-config": "^4.0.1",
"postcss-nested": "^6.0.1",
"postcss-selector-parser": "^6.0.11",
"postcss-value-parser": "^4.2.0",
"resolve": "^1.22.2",
"sucrase": "^3.32.0"
},
Expand Down
1 change: 0 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,6 @@
"postcss-load-config": "^4.0.1",
"postcss-nested": "^6.0.1",
"postcss-selector-parser": "^6.0.11",
"postcss-value-parser": "^4.2.0",
"resolve": "^1.22.2",
"sucrase": "^3.32.0"
},
Expand Down
1 change: 0 additions & 1 deletion package.stable.json
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,6 @@
"postcss-load-config": "^4.0.1",
"postcss-nested": "^6.0.1",
"postcss-selector-parser": "^6.0.11",
"postcss-value-parser": "^4.2.0",
"resolve": "^1.22.2",
"sucrase": "^3.32.0"
},
Expand Down
5 changes: 4 additions & 1 deletion src/lib/evaluateTailwindFunctions.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import dlv from 'dlv'
import didYouMean from 'didyoumean'
import transformThemeValue from '../util/transformThemeValue'
import parseValue from 'postcss-value-parser'
import parseValue from '../value-parser/index'
import { normalizeScreens } from '../util/normalizeScreens'
import buildMediaQuery from '../util/buildMediaQuery'
import { toPath } from '../util/toPath'
Expand Down Expand Up @@ -146,6 +146,9 @@ function resolveVNode(node, vNode, functions) {
}

function resolveFunctions(node, input, functions) {
let hasAnyFn = Object.keys(functions).some((fn) => input.includes(`${fn}(`))
if (!hasAnyFn) return input

return parseValue(input)
.walk((vNode) => {
resolveVNode(node, vNode, functions)
Expand Down
20 changes: 15 additions & 5 deletions src/util/dataTypes.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,10 +49,22 @@ export function normalize(value, isRoot = true) {
value = value.trim()
}

// Add spaces around operators inside math functions like calc() that do not follow an operator
// or '('.
value = value.replace(/(calc|min|max|clamp)\(.+\)/g, (match) => {
value = normalizeMathOperatorSpacing(value)

return value
}

/**
* Add spaces around operators inside math functions
* like calc() that do not follow an operator or '('.
*
* @param {string} value
* @returns {string}
*/
function normalizeMathOperatorSpacing(value) {
return value.replace(/(calc|min|max|clamp)\(.+\)/g, (match) => {
let vars = []

return match
.replace(/var\((--.+?)[,)]/g, (match, g1) => {
vars.push(g1)
Expand All @@ -61,8 +73,6 @@ export function normalize(value, isRoot = true) {
.replace(/(-?\d*\.?\d(?!\b-\d.+[,)](?![^+\-/*])\D)(?:%|[a-z]+)?|\))([+\-/*])/g, '$1 $2 ')
.replace(placeholderRe, () => vars.shift())
})

return value
}

export function url(value) {
Expand Down
22 changes: 22 additions & 0 deletions src/value-parser/LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
Copyright (c) Bogdan Chadkin <[email protected]>

Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:

The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
3 changes: 3 additions & 0 deletions src/value-parser/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# postcss-value-parser (forked + inlined)

This is a customized version of of [PostCSS Value Parser](https://github.com/TrySound/postcss-value-parser) to fix some bugs around parsing CSS functions.
177 changes: 177 additions & 0 deletions src/value-parser/index.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
declare namespace postcssValueParser {
interface BaseNode {
/**
* The offset, inclusive, inside the CSS value at which the node starts.
*/
sourceIndex: number

/**
* The offset, exclusive, inside the CSS value at which the node ends.
*/
sourceEndIndex: number

/**
* The node's characteristic value
*/
value: string
}

interface ClosableNode {
/**
* Whether the parsed CSS value ended before the node was properly closed
*/
unclosed?: true
}

interface AdjacentAwareNode {
/**
* The token at the start of the node
*/
before: string

/**
* The token at the end of the node
*/
after: string
}

interface CommentNode extends BaseNode, ClosableNode {
type: 'comment'
}

interface DivNode extends BaseNode, AdjacentAwareNode {
type: 'div'
}

interface FunctionNode extends BaseNode, ClosableNode, AdjacentAwareNode {
type: 'function'

/**
* Nodes inside the function
*/
nodes: Node[]
}

interface SpaceNode extends BaseNode {
type: 'space'
}

interface StringNode extends BaseNode, ClosableNode {
type: 'string'

/**
* The quote type delimiting the string
*/
quote: '"' | "'"
}

interface UnicodeRangeNode extends BaseNode {
type: 'unicode-range'
}

interface WordNode extends BaseNode {
type: 'word'
}

/**
* Any node parsed from a CSS value
*/
type Node =
| CommentNode
| DivNode
| FunctionNode
| SpaceNode
| StringNode
| UnicodeRangeNode
| WordNode

interface CustomStringifierCallback {
/**
* @param node The node to stringify
* @returns The serialized CSS representation of the node
*/
(nodes: Node): string | undefined
}

interface WalkCallback {
/**
* @param node The currently visited node
* @param index The index of the node in the series of parsed nodes
* @param nodes The series of parsed nodes
* @returns Returning `false` will prevent traversal of descendant nodes (only applies if `bubble` was set to `true` in the `walk()` call)
*/
(node: Node, index: number, nodes: Node[]): void | boolean
}

/**
* A CSS dimension, decomposed into its numeric and unit parts
*/
interface Dimension {
number: string
unit: string
}

/**
* A wrapper around a parsed CSS value that allows for inspecting and walking nodes
*/
interface ParsedValue {
/**
* The series of parsed nodes
*/
nodes: Node[]

/**
* Walk all parsed nodes, applying a callback
*
* @param callback A visitor callback that will be executed for each node
* @param bubble When set to `true`, walking will be done inside-out instead of outside-in
*/
walk(callback: WalkCallback, bubble?: boolean): this
}

interface ValueParser {
/**
* Decompose a CSS dimension into its numeric and unit part
*
* @param value The dimension to decompose
* @returns An object representing `number` and `unit` part of the dimension or `false` if the decomposing fails
*/
unit(value: string): Dimension | false

/**
* Serialize a series of nodes into a CSS value
*
* @param nodes The nodes to stringify
* @param custom A custom stringifier callback
* @returns The generated CSS value
*/
stringify(nodes: Node | Node[], custom?: CustomStringifierCallback): string

/**
* Walk a series of nodes, applying a callback
*
* @param nodes The nodes to walk
* @param callback A visitor callback that will be executed for each node
* @param bubble When set to `true`, walking will be done inside-out instead of outside-in
*/
walk(nodes: Node[], callback: WalkCallback, bubble?: boolean): void

/**
* Parse a CSS value into a series of nodes to operate on
*
* @param value The value to parse
*/
new (value: string): ParsedValue

/**
* Parse a CSS value into a series of nodes to operate on
*
* @param value The value to parse
*/
(value: string): ParsedValue
}
}

declare const postcssValueParser: postcssValueParser.ValueParser

export = postcssValueParser
28 changes: 28 additions & 0 deletions src/value-parser/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
var parse = require('./parse')
var walk = require('./walk')
var stringify = require('./stringify')

function ValueParser(value) {
if (this instanceof ValueParser) {
this.nodes = parse(value)
return this
}
return new ValueParser(value)
}

ValueParser.prototype.toString = function () {
return Array.isArray(this.nodes) ? stringify(this.nodes) : ''
}

ValueParser.prototype.walk = function (cb, bubble) {
walk(this.nodes, cb, bubble)
return this
}

ValueParser.unit = require('./unit')

ValueParser.walk = walk

ValueParser.stringify = stringify

module.exports = ValueParser
Loading

0 comments on commit 960cb40

Please sign in to comment.