fix(parser): preserve fatal error across top-level await reparse#23677
Open
ammubhave wants to merge 1 commit into
Open
fix(parser): preserve fatal error across top-level await reparse#23677ammubhave wants to merge 1 commit into
ammubhave wants to merge 1 commit into
Conversation
Merging this PR will not alter performance
Comparing Footnotes
|
Member
|
this will fix #23681 too |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fixes #23673.
oxfmtpanicked withstring must start with a quote(oxc_formatter/src/utils/string.rs) on input likeawait(E);import. The root cause is in the parser: a fatal parse error was silently discarded, so a file that should be rejected was reported as parsed successfully with a malformed AST.Problem
await(E);importis invalid — the trailingimportis incomplete, which is a fatal error. On its own (import, orx;import) the parser reports it correctly. Butawait(E)is a top-levelawait: in unambiguous mode the parser first parsesawaitas an identifier, records a checkpoint, and once theimportproves the file is a module, reparses that statement withawaitenabled (reparse_potential_top_level_awaits).That reparse calls
rewind(checkpoint), andrewindrestores all saved state — includingself.fatal_error. The checkpoint was taken before theimport, so rewinding resetfatal_errorback toNone, erasing it. The parser then returned no diagnostics and a malformedImportDeclarationwhosesourceis a syntheticStringLiteral { span: 0..0, value: "", raw: None }. Becauseoxc_formatter::formatonly runs when parsing reports no diagnostics, this malformed program reached the formatter, which sliced the empty source text and tripped the assertion (and would index-underflow in release).Fix
Stash
self.fatal_errorbefore the reparse loop and restore it afterward. Reparsing only re-derives the AST for already-validawaitstatements, so a fatal error from a later statement is unrelated to them and must outlive the reparse. A fatal error newly surfaced by reparsing comes earlier in source order and takes priority; otherwise the first-pass error is restored.This cannot cause false positives: the reparse only triggers after an
import/exportparsed successfully (parsing never stopped earlier), so any survivingfatal_errornecessarily came from a statement after the reparsed awaits — which the reparse cannot fix anyway. It also fixes the class of bug generally, not just the empty-import-source symptom.Example
Before: parsed "successfully" →
oxfmtpanics. After:Expectedfrombut foundEOF``, and valid files likeawait(E); import x from "y";still format normally.Testing
tasks/coverage/misc/fail/oxc-23673.js(negative parser fixture).cargo coverage -- parser: 100% AST-parsed, no regressions; the only snapshot change is the new fixture counted as a correctly-failing test (Negative Passed: 149 → 150).cargo test -p oxc_parser, clippy, and rustfmt pass.🤖 Generated with Claude Code