Skip to content

fix(compiler): recover from a rule lightningcss cannot parse - #493

Open
YevheniiKotyrlo wants to merge 4 commits into
nativewind:mainfrom
YevheniiKotyrlo:fix/syntax-error-recovery
Open

YevheniiKotyrlo wants to merge 4 commits into
nativewind:mainfrom
YevheniiKotyrlo:fix/syntax-error-recovery

Conversation

@YevheniiKotyrlo

@YevheniiKotyrlo YevheniiKotyrlo commented Oct 16, 2026 •

Copy link
Copy Markdown
Contributor

Problem

The compiler runs lightningcss without errorRecovery, so one rule or declaration lightningcss cannot parse throws, and the whole stylesheet compiles to nothing. lightningcss 1.30.1 throws on @media (foo bar) - a <general-enclosed> condition, which a browser evaluates as unknown - on @container scroll-state(stuck: top), and on var() with no argument. A browser keeps every rule beside each of them (css-syntax-3 §5).

Solution

Both passes run with errorRecovery: true, so lightningcss drops what it cannot parse and the rest compiles. Each warning it gives for a dropped rule reaches compile().warnings() as syntax, through the channel #430 adds.

Tests

src/__tests__/compiler/error-recovery.test.ts compiles a sheet holding each of the three around two plain rules: the plain rules survive, the dropped rule is reported, and a declaration that fails to parse goes alone. All three fail on #430 without this change.

Verification

On Ubuntu with the workflow's steps - build, lint, typecheck, yarn test, node --test node/__tests__/*.mjs, both library builds, the web export, the unstaged-changes checks and commitlint - everything passes.

Not run: the iOS and Android example builds.

Merge order

It carries #430's commits, whose warning channel is where a dropped rule is reported, so #430 lands first.

It also shares lines with #411 (README.md), #428 (README.md); whichever lands second rebases.

#379 changes lines of metro/metro-transformer.ts that #430's commits change; whichever lands second rebases.

The compiler records every declaration it could not translate, but nothing
read compile().warnings() during a build, so a dropped float or
z-index: auto was silent. The Metro transformer now prints one block per CSS
file: capped in summary mode, listed in full in verbose mode, and printed
again only when the file's warnings change. The warnings option sets the
level, and none silences it.

lightningcss also warns about input it cannot parse, such as an unknown
at-rule or pseudo-element, whose rule then disappears. Those warnings reach a
new syntax channel, kept apart from what React Native cannot express; this
package's own @nativeMapping is not reported.
A value warning was kept as whatever the compiler held, so the report
carried a fallback for objects and null that no producer records. The
one number, a percentage line-height, is now recorded as text where it
is produced, CompilerWarnings.values is string[], and the value entries
sort with the same code-unit order as every other channel. A misspelled
@nativeMapping is pinned as reported, since the filter that keeps the
package's own at-rule quiet matches its name exactly.
The syntax-warning change left a blank line between compile() and the
doc comment that describes it.
@YevheniiKotyrlo
YevheniiKotyrlo marked this pull request as draft October 17, 2026 09:18
lightningcss ran without errorRecovery, so one rule or declaration it
could not parse threw and the whole stylesheet compiled to nothing:
lightningcss 1.30.1 throws on a media query's <general-enclosed>
condition, on scroll-state(), and on var() with no argument, all of
which a browser drops on its own while keeping every sibling. Both
passes now recover, and the warning lightningcss gives for each dropped
rule reaches compile().warnings() as `syntax`.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant