Repository navigation
Conversation
`PropertyRule::parse` buffered the `initial-value` and parsed it again
after the rule body with a second parser. That parser had no import
record list, so each `url()` in an initial-value stopped the build with
"Unexpected token: url(", for every syntax. Its positions were relative
to the value, so an error pointed at line 1 of the file.
The value is now parsed on the stylesheet's own parser: where it is read
when `syntax` is known, else after the body from its saved position. A
`url()` is an import record, as in a declaration. A value that a later
`initial-value` replaces leaves no record.
`SyntaxString::parse_value` returns a typed value only when a component
matches all of the value. It returned the first component that matched
the start, and the rest was dropped: `1px 2px` for
`<length> | <length>+` built as `1px`.
The `<string>` syntax name is accepted, as in lightningcss.
|
Updated 9:20 PM PT - Oct 17th, 2026
❌ @robobun, your commit 65f3485 has 3 failures in
🧪 To try this PR locally: bunx bun-pr 45806That installs a local version of the PR into your bun-45806 --bun |
|
Status: open, waiting for CI and review.
|
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info
WalkthroughThe CSS syntax parser adds ChangesRegistered CSS property parsing
Suggested reviewers: Priority: ➖ Normal Merge Risk: ⚪ Minimal · up to
|
|
One more fix for
Without the one token, exactly the new whitespace test and the two |
Problem
bun buildfails witherror: Unexpected token: url(on a valid@propertyrule whoseinitial-valuehas aurl():syntax: "<url>"; initial-value: url("data:,").@sap-theming/theming-base-contenthas 7,192 such rules.PropertyRule::parse(src/css/rules/property.rs:117) parsed the value again with a second parser. That parser had no import records, and its error positions were relative to the value.1px 2pxfor<length> | <length>+built as1px.syntax: "<string>"wasInvalid value.Fix
syntaxis known, else after the body from a saved position.<string>is a syntax name, as in lightningcss.test/js/bun/css/css.test.ts(7 tests),test/bundler/esbuild/css.test.ts(2). Also all oftest/js/bun/css,test/bundler/css. Self-reviewed: 24 concerns raised, 22 addressed (Notes).Background
@propertyregisters a custom property: asyntax(its type) and aninitial-value.url(). Without the list,url()fails.Downsides
<color>withred bluebuilt asred. It is now the build error that each value with no matching component has. A browser drops that rule. 0 of 1,161 real rule bodies change that way.syntaxfirst), 8,940 (initial-valuefirst, 2.3% of real rules). No value: 2,445 -> 2,484..text+256 B.Notes
Behaviour changes
url()in an initial-value was a build error for each syntax,*too. It is now an import record, as aurl()in a declaration: 1 record and 1 asset perurl(). A value that a laterinitial-valuereplaces leaves 0 records.a.css:1:6for a value on line 4.initial-valuewhose start matched a component was cut.<length> | <length>+with1px 2pxbuilt as1px, and<transform-function> | <transform-list>withrotate(1turn) scale(2)built asrotate(1turn). Both are valid and now print whole. lightningcss 1.33.0 cuts them.<color>withred bluebuilt asred, and<length>with10px 20pxas10px. Both are nowUnexpected token, the error that each value with no matching component has on main. Chrome 153 drops the source rule of both, and registered the cut rule.syntax: "<string>"wasInvalid value. It is now a typed syntax (feat: support<string>in@propertysyntax parcel-bundler/lightningcss#1134).syntaxand the lastinitial-valueof a rule win.Real style sheets (release builds of the merge base 017b243 and of this PR)
@propertybodies (42,737 rules) in 1,285 files, from 444 npm packages and 17 GitHub files.url()(7,200 rules:url("data:,")in@sap-theming/theming-base-contentis 7,192 of them, the others are in 3 GitHub files), and 4 with<string>(12 rules,@material/weband packages that copy it).<string>rules whose value is not a string, 4 with a syntax name that no draft has (<ratio>,<line-style>), 2 typed rules with noinitial-value.--minify: 1,124 give the same bytes. 12 fail on main and build here. 0 build on main and fail here. 149 fail both ways for a cause outside this change.@sap-theming/theming-base-content@11.36.5:content/Base/baseLib/sap_horizon/css_variables.css(65@propertyrules) exits 1 on main and builds here.test/js/bun/css/files/*.cssand four files of tailwindcss 4.3.3 (none has@property): 0 of 38 builds differ.Measured against Chrome 153 (headless, the source rule and the built rule, pretty and minified, 57 values)
url()and 5 valid<string>values: build errors on main. Each built rule is registered here.light-dark(): each built rule is the same as on main.Cost (release builds of the merge base and of this PR, gdb on
bun-profile, one call ofNestedRuleParser::parse_block, rule 2 of 4 equal rules)Tokenizer::next_implParser::expect_exhaustedmi_mallocsyntax:'<color>';inherits:false;initial-value:rgb(255 0 0)initial-valuefirstsyntax:'<length>';inherits:false;initial-value:0pxsyntax:'*';inherits:false;initial-value:rgb(from currentColor r g b)syntax:'*';inherits:falsemi_realloc: 2 for the rule withrgb(from currentColor r g b), on main and here. 0 for the others.syntaxcomes beforeinitial-valuein 22,279 of the 22,810 rules that have a value (97.7%). 19,927 of the 42,737 rules have no value.PropertyRule::to_css: 1,622 -> 1,623 instructions for the first rule of the table, 1,186 -> 1,186 for the last.@property(bulma.css):SyntaxString::parse_valuecalls 0 -> 0, output bytes identical.llvm-size -A, stripped):.text81,368,037 -> 81,368,293 B (+256),.rodata22,598,604 -> 22,598,604 B (+0).url()in@property(openat,open,readlink,read,pread64,write,close,getdents64, counted with gdbcatch syscall, because strace is not installed here): 77 -> 77 for the first rule of the table, 121 -> 121 forbulma.css.Not in this PR
rgb(from currentColor r g b),oklch(0 0 0 / 0.97),calc(1 + 2)for<integer>. css: keep a typed @property initial-value as written when no component matches #45807 keeps such a value as written. It is a separate PR because it also changes what a value that is not valid does.@propertythat Bun cannot parse fails the whole build, where a browser drops only that rule: an unknown descriptor, aninheritsother thantrueorfalse, asyntaxthat does not parse, a typed rule with noinitial-value. Main does the same.@propertyinside@media,@layer,@supportsor@scopeis kept as an unknown at-rule with a warning, and gets none of these fixes (432 rules of the sample). lightningcss moved that arm in Allow@propertyto be nested inside at-rules parcel-bundler/lightningcss#1090.bun build --no-bundlehas no import record list for the whole sheet, so aurl()fails there in each rule (bun build --no-bundle: keep css url() references instead of failing to parse #38528, css: keep a url() in an at-rule prelude as written, and never print the resolved path of a url() #45604).url()is resolved against the style sheet, as in a declaration. Chrome 153 resolves a<url>initial-value against the document, and an<image>or*one against the sheet that uses it.@media (min-resolution: calc(1dppx * 2))) through the same typed parsers.Other PRs
toThrowassertions on a typed initial-value stay true after this PR. The two conflict inproperty.rs: the second to merge needs a rebase.<length>is read as px, so<length> | <number>with1.5prints1.5px, on main and here.Bun.color("red blue")is"red", the same cut at another call.Upstream
lightningcss 1.33.0 builds
url()in an initial-value and the<string>syntax. The cases of itstest_propertyare the first test of the@propertyblock.Self-review
24 concerns raised, 22 addressed. The review asked for this split: the as-written fallback and the print without targets are in #45807. Not done: a tracking issue for the descriptor arms and one for the media features. Both are named above.
Suites run (
bun bd test, debug build with ASAN)test/js/bun/css/css.test.ts: 1,321 pass, 0 fail.test/bundler/esbuild/css.test.ts: 58 pass, 0 fail.test/js/bun/cssandtest/bundler/css, with the two files above andtest/regression/issue/27117.test.ts, one file at a time: 2,760 pass, 4 fail. The 4 are incss-fuzz.test.ts, a timing test that does not run in CI: 1,000Bun.buildcalls do not fit its 10 s limit on a debug build.