diff --git a/CHANGELOG.md b/CHANGELOG.md index 36daaf224..e9bbf948f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Support `borderRadius.*` as an alias for `--radius-*` when using dot notation inside the `theme()` function ([#14436](https://github.com/tailwindlabs/tailwindcss/pull/14436)) - Ensure individual variants from groups are always sorted earlier than stacked variants from the same groups ([#14431](https://github.com/tailwindlabs/tailwindcss/pull/14431)) - Allow `anchor-size(…)` in arbitrary values ([#14394](https://github.com/tailwindlabs/tailwindcss/pull/14394)) +- Skip candidates with invalid `theme()` calls ([#14437](https://github.com/tailwindlabs/tailwindcss/pull/14437)) ## [4.0.0-alpha.24] - 2024-09-11 diff --git a/packages/tailwindcss/src/compile.ts b/packages/tailwindcss/src/compile.ts index 931d16ecd..9b7b3ce46 100644 --- a/packages/tailwindcss/src/compile.ts +++ b/packages/tailwindcss/src/compile.ts @@ -1,5 +1,6 @@ import { decl, rule, walk, WalkAction, type AstNode, type Rule } from './ast' import { type Candidate, type Variant } from './candidate' +import { substituteFunctions } from './css-functions' import { type DesignSystem } from './design-system' import GLOBAL_PROPERTY_ORDER from './property-order' import { asColor, type Utility } from './utilities' @@ -40,6 +41,22 @@ export function compileCandidates( let rules = designSystem.compileAstNodes(candidate) if (rules.length === 0) continue + // Arbitrary values (`text-[theme(--color-red-500)]`) and arbitrary + // properties (`[--my-var:theme(--color-red-500)]`) can contain function + // calls so we need evaluate any functions we find there that weren't in + // the source CSS. + try { + substituteFunctions( + rules.map(({ node }) => node), + designSystem.resolveThemeValue, + ) + } catch (err) { + // If substitution fails then the candidate likely contains a call to + // `theme()` that is invalid which may be because of incorrect usage, + // invalid arguments, or a theme key that does not exist. + continue + } + found = true for (let { node, propertySort } of rules) { diff --git a/packages/tailwindcss/src/css-functions.test.ts b/packages/tailwindcss/src/css-functions.test.ts index 65c6dbca0..b40dc9a2b 100644 --- a/packages/tailwindcss/src/css-functions.test.ts +++ b/packages/tailwindcss/src/css-functions.test.ts @@ -606,6 +606,42 @@ describe('theme function', () => { }" `) }) + + test("values that don't exist don't produce candidates", async () => { + // This guarantees that valid candidates still make it through when some are invalid + expect( + await compileCss( + css` + @tailwind utilities; + @theme reference { + --radius-sm: 2rem; + } + `, + [ + 'rounded-[theme(--radius-sm)]', + 'rounded-[theme(i.do.not.exist)]', + 'rounded-[theme(--i-do-not-exist)]', + ], + ), + ).toMatchInlineSnapshot(` + ".rounded-\\[theme\\(--radius-sm\\)\\] { + border-radius: 2rem; + }" + `) + + // This guarantees no output for the following candidates + expect( + await compileCss( + css` + @tailwind utilities; + @theme reference { + --radius-sm: 2rem; + } + `, + ['rounded-[theme(i.do.not.exist)]', 'rounded-[theme(--i-do-not-exist)]'], + ), + ).toEqual('') + }) }) describe('in @media queries', () => { diff --git a/packages/tailwindcss/src/index.ts b/packages/tailwindcss/src/index.ts index 8e6854ce0..ad441084c 100644 --- a/packages/tailwindcss/src/index.ts +++ b/packages/tailwindcss/src/index.ts @@ -385,12 +385,6 @@ export async function compile( return compiledCss } - // Arbitrary values (`text-[theme(--color-red-500)]`) and arbitrary - // properties (`[--my-var:theme(--color-red-500)]`) can contain function - // calls so we need evaluate any functions we find there that weren't in - // the source CSS. - substituteFunctions(newNodes, designSystem.resolveThemeValue) - previousAstNodeCount = newNodes.length tailwindUtilitiesNode.nodes = newNodes