Fix "Cannot read properties of undefined" crash on malformed arbitrary value (#18133)
This PR fixes a crash when an arbitrary value was malformed and crashed
the build.
If you have a utility like `[--btn-border:var(--color-maroon)/90)]`
which is malformed, it will crash the build. It might not be easy to
spot but the easy is the additional `)` after the `90`.
The reason this crashes is because we parse the value
`var(--color-maroon)/90)` and when we see `)` we assume it's the end of
a "function" which also assumes it was preceded by a `(`. This is not
the case and we crash.
This PR fixes that by not assuming the parsed object is available and
uses `?` to be safe and only access `nodes` if it's available.
I'm actually not 100% sure what the best solution is in this scenario
because these candidates could (and will) be returned from Oxide so even
if we throw a more descriptive error, it will still crash the build and
you might not even have control over the candidate.
This candidate will now eventually generate the following CSS:
```css
.\[--btn-border\:var\(--color-maroon\)\/90\)\] {
--btn-border: var(--color-maroon) / ;
}
```
Which still looks odd, but even Lightning CSS doesn't throw an error in
this case (because it's a CSS variable definition), so I think it's the
best we can do. If you open your devtools you will see the weird values,
so it's still debug-able.
<img width="359" alt="image"
src="https://github.com/user-attachments/assets/2eb48662-64de-4417-a2da-1577bf9075b5"
/>
Fixes: #17064
## Test plan
Manually tested the candidate that crashed it, and after the change
generated the above CSS. Then used it in JSFiddle to proof it's fixed
now. https://jsfiddle.net/z850ykew/
Couldn't use Tailwind Play because the candidate will cause a crash
there as well 😅
This commit is contained in:
parent
9cb38993d2
commit
884f02c46b
4 changed files with 35 additions and 17 deletions
|
|
@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
- Upgrade: Improve `pnpm` workspaces support ([#18065](https://github.com/tailwindlabs/tailwindcss/pull/18065))
|
||||
- Upgrade: Migrate deprecated `order-none` to `order-0` ([#18126](https://github.com/tailwindlabs/tailwindcss/pull/18126))
|
||||
- Support Leptos `class:` attributes when extracting classes ([#18093](https://github.com/tailwindlabs/tailwindcss/pull/18093))
|
||||
- Fix "Cannot read properties of undefined" crash on malformed arbitrary value ([#18133](https://github.com/tailwindlabs/tailwindcss/pull/18133))
|
||||
|
||||
## [4.1.7] - 2025-05-15
|
||||
|
||||
|
|
|
|||
|
|
@ -4305,11 +4305,11 @@ it("should error when `layer(…)` is used, but it's not the first param", async
|
|||
|
||||
describe('`@reference "…" imports`', () => {
|
||||
test('recursively removes styles', async () => {
|
||||
let loadStylesheet = async (id: string, base: string) => {
|
||||
let loadStylesheet = async (id: string, base = '/root/foo') => {
|
||||
if (id === './foo/baz.css') {
|
||||
return {
|
||||
base,
|
||||
path: '',
|
||||
base: '/root/foo',
|
||||
content: css`
|
||||
.foo {
|
||||
color: red;
|
||||
|
|
@ -4355,11 +4355,11 @@ describe('`@reference "…" imports`', () => {
|
|||
})
|
||||
|
||||
test('does not generate utilities', async () => {
|
||||
let loadStylesheet = async (id: string, base: string) => {
|
||||
let loadStylesheet = async (id: string, base = '/root/foo') => {
|
||||
if (id === './foo/baz.css') {
|
||||
return {
|
||||
base,
|
||||
path: '',
|
||||
base: '/root/foo',
|
||||
content: css`
|
||||
@layer utilities {
|
||||
@tailwind utilities;
|
||||
|
|
@ -4442,7 +4442,7 @@ describe('`@reference "…" imports`', () => {
|
|||
})
|
||||
matchUtilities(
|
||||
{
|
||||
'match-utility': (value) => ({
|
||||
'match-utility': (_value) => ({
|
||||
'@keyframes match-utilities': { '100%': { opacity: '0' } },
|
||||
}),
|
||||
},
|
||||
|
|
@ -4450,7 +4450,7 @@ describe('`@reference "…" imports`', () => {
|
|||
)
|
||||
matchComponents(
|
||||
{
|
||||
'match-components': (value) => ({
|
||||
'match-components': (_value) => ({
|
||||
'@keyframes match-components': { '100%': { opacity: '0' } },
|
||||
}),
|
||||
},
|
||||
|
|
@ -4474,12 +4474,12 @@ describe('`@reference "…" imports`', () => {
|
|||
})
|
||||
|
||||
test('emits CSS variable fallback and keyframes defined inside @reference-ed files', async () => {
|
||||
let loadStylesheet = async (id: string, base: string) => {
|
||||
let loadStylesheet = async (id: string, base = '/root') => {
|
||||
switch (id) {
|
||||
case './one.css': {
|
||||
return {
|
||||
base,
|
||||
path: '',
|
||||
base: '/root',
|
||||
content: css`
|
||||
@import './two.css' layer(two);
|
||||
`,
|
||||
|
|
@ -4487,8 +4487,8 @@ describe('`@reference "…" imports`', () => {
|
|||
}
|
||||
case './two.css': {
|
||||
return {
|
||||
base,
|
||||
path: '',
|
||||
base: '/root',
|
||||
content: css`
|
||||
@import './three.css' layer(three);
|
||||
`,
|
||||
|
|
@ -4496,8 +4496,8 @@ describe('`@reference "…" imports`', () => {
|
|||
}
|
||||
case './three.css': {
|
||||
return {
|
||||
base,
|
||||
path: '',
|
||||
base: '/root',
|
||||
content: css`
|
||||
.foo {
|
||||
color: red;
|
||||
|
|
@ -5519,7 +5519,7 @@ describe('feature detection', () => {
|
|||
css`
|
||||
@import 'tailwindcss/preflight';
|
||||
`,
|
||||
{ loadStylesheet: async (_, base) => ({ base, content: '' }) },
|
||||
{ loadStylesheet: async (_, base) => ({ base, path: '', content: '' }) },
|
||||
)
|
||||
|
||||
expect(compiler.features & Features.AtImport).toBeTruthy()
|
||||
|
|
@ -5530,7 +5530,7 @@ describe('feature detection', () => {
|
|||
css`
|
||||
@import 'tailwindcss/preflight';
|
||||
`,
|
||||
{ loadStylesheet: async (_, base) => ({ base, content: '' }) },
|
||||
{ loadStylesheet: async (_, base) => ({ base, path: '', content: '' }) },
|
||||
)
|
||||
|
||||
// There's little difference between `@reference` and `@import` on a feature
|
||||
|
|
@ -5551,7 +5551,7 @@ describe('feature detection', () => {
|
|||
color: theme(--color-red);
|
||||
}
|
||||
`,
|
||||
{ loadStylesheet: async (_, base) => ({ base, content: '' }) },
|
||||
{ loadStylesheet: async (_, base) => ({ base, path: '', content: '' }) },
|
||||
)
|
||||
|
||||
expect(compiler.features & Features.ThemeFunction).toBeTruthy()
|
||||
|
|
@ -5562,7 +5562,7 @@ describe('feature detection', () => {
|
|||
css`
|
||||
@plugin "./some-plugin.js";
|
||||
`,
|
||||
{ loadModule: async (_, base) => ({ base, module: () => {} }) },
|
||||
{ loadModule: async (_, base) => ({ base, path: '', module: () => {} }) },
|
||||
)
|
||||
|
||||
expect(compiler.features & Features.JsPluginCompat).toBeTruthy()
|
||||
|
|
@ -5573,7 +5573,7 @@ describe('feature detection', () => {
|
|||
css`
|
||||
@config "./some-config.js";
|
||||
`,
|
||||
{ loadModule: async (_, base) => ({ base, module: {} }) },
|
||||
{ loadModule: async (_, base) => ({ base, path: '', module: {} }) },
|
||||
)
|
||||
|
||||
expect(compiler.features & Features.JsPluginCompat).toBeTruthy()
|
||||
|
|
@ -5605,9 +5605,10 @@ describe('feature detection', () => {
|
|||
@reference "tailwindcss/utilities";
|
||||
`,
|
||||
{
|
||||
async loadStylesheet(id, base) {
|
||||
async loadStylesheet(_id, base) {
|
||||
return {
|
||||
base,
|
||||
path: '',
|
||||
content: css`
|
||||
@tailwind utilities;
|
||||
`,
|
||||
|
|
|
|||
|
|
@ -165,6 +165,22 @@ describe('parse', () => {
|
|||
},
|
||||
])
|
||||
})
|
||||
|
||||
it('should not error when extra `)` was passed', () => {
|
||||
expect(parse('calc(1 + 2))')).toEqual([
|
||||
{
|
||||
kind: 'function',
|
||||
value: 'calc',
|
||||
nodes: [
|
||||
{ kind: 'word', value: '1' },
|
||||
{ kind: 'separator', value: ' ' },
|
||||
{ kind: 'word', value: '+' },
|
||||
{ kind: 'separator', value: ' ' },
|
||||
{ kind: 'word', value: '2' },
|
||||
],
|
||||
},
|
||||
])
|
||||
})
|
||||
})
|
||||
|
||||
describe('toCss', () => {
|
||||
|
|
|
|||
|
|
@ -302,7 +302,7 @@ export function parse(input: string) {
|
|||
// Handle everything before the closing paren a word
|
||||
if (buffer.length > 0) {
|
||||
let node = word(buffer)
|
||||
tail!.nodes.push(node)
|
||||
tail?.nodes.push(node)
|
||||
buffer = ''
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue