Skip candidates with invalid theme() calls (#14437)

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
<div class="underline rounded-[theme(--radius-does-not-exist)]"></div>
```

```css
/* No CSS was generated because an error was thrown */
/* Error: Invalid theme key: --radius-does-not-exist */
```

After:
```html
<div class="underline rounded-[theme(--radius-does-not-exist)]"></div>
```

```css
.underline {
  text-decoration-line: underline;
}
```

---------

Co-authored-by: Adam Wathan <adam.wathan@gmail.com>
This commit is contained in:
Jordan Pittman 2024-09-17 12:46:42 -04:00 • committed by GitHub
parent 23cee5c6e0
commit cb5b7b405a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 54 additions and 6 deletions

View file

@ -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

View file

@ -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) {

View file

@ -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', () => {

View file

@ -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