From cb5b7b405a392f2d21b3e6f398576e9a43290a2d Mon Sep 17 00:00:00 2001 From: Jordan Pittman Date: Tue, 17 Sep 2024 12:46:42 -0400 Subject: [PATCH] Skip candidates with invalid `theme()` calls (#14437) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Right now when we encounter a candidates with invalid `theme()` calls we throw an error which stops the build entirely. This is not ideal because, especially in the case of `node_modules`, if one file in one package has an error it will stop the build for an entire project and tracking this down can be quite difficult. Now, after this PR, any candidates that use `theme(…)` with non-existent theme keys (e.g. `rounded-[theme(--radius-does-not-exist)]`) will be skipped instead of breaking the build. Before: ```html
``` ```css /* No CSS was generated because an error was thrown */ /* Error: Invalid theme key: --radius-does-not-exist */ ``` After: ```html
``` ```css .underline { text-decoration-line: underline; } ``` --------- Co-authored-by: Adam Wathan --- CHANGELOG.md | 1 + packages/tailwindcss/src/compile.ts | 17 +++++++++ .../tailwindcss/src/css-functions.test.ts | 36 +++++++++++++++++++ packages/tailwindcss/src/index.ts | 6 ---- 4 files changed, 54 insertions(+), 6 deletions(-) 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