Skip to content

fix(es/parser): Respect line breaks after declare - #12353

Merged
Donny/강동윤 (kdy1) merged 2 commits into
swc-project:mainfrom
Dev-next-gen:fix/es-parser-declare-line-break
Sep 14, 2026
Merged

Donny/강동윤 (kdy1) merged 2 commits into
swc-project:mainfrom
Dev-next-gen:fix/es-parser-declare-line-break

Conversation

@Dev-next-gen

Copy link
Copy Markdown
Contributor

Description:

I noticed that SWC treats declare as a modifier even when a line break separates it from the declaration that follows. TypeScript applies ASI there, so this input:

declare
class C {}

is the expression statement declare; followed by an ordinary class. SWC parsed it as an ambient class instead, which the TypeScript strip then removes from the output, so C silently disappears at runtime. With function it fails outright: declare\nfunction f() {} is rejected with "An implementation cannot be declared in ambient contexts" although it is valid TypeScript.

The fix returns early from the declare arm of parse_ts_expr_stmt when the next token is on a new line, which is what the module arm of parse_ts_decl already does (declare\nmodule M {} was the one case that parsed correctly). Babel's tsTryParseDeclare has the same isLineTerminator() check. I left Flow alone.

I compared SWC's statement kinds against ts.createSourceFile (TypeScript 6.0.3) for declare followed by a newline and class, function, const, let, var, enum, const enum, namespace, module, type, interface, abstract class, global and a multiline comment. Before the change only module matched, and after it all of them do. declare enum E {} and the other same-line forms are unchanged, and so is the export declare path.

On whether this is safe: the only inputs that change are the ones where a line break follows declare, and for those the old AST contradicted what tsc produces. The repository already carries TypeScript's own conformance test tests/tsc/asiPreventsParsingAsInterface04.ts, whose comments say declare, interface and I should each be identifiers followed by a block, but its snapshot recorded an ambient interface. I regenerated that snapshot, which now matches those comments. I also added a declare-line-break fixture. It fails without the change and passes with it.

cargo test -p swc_ecma_parser --test typescript passes apart from type_only_import_specifier/invalid_type_only, which fails the same way on main in my environment.

Related issue (if exists):

Same family as #12341 (line breaks before enum names).

AI tools used

TypeScript applies ASI when a line break follows `declare`, so

    declare
    class C {}

is the expression statement `declare;` followed by a regular class.
SWC parsed it as an ambient class, which the TypeScript strip then
removes, and rejected `declare\nfunction f() {}` with "An implementation
cannot be declared in ambient contexts".

Return early from the `declare` arm of `parse_ts_expr_stmt` when the next
token is on a new line, like the `module` arm already does. Flow is left
unchanged.
@changeset-bot

changeset-bot Bot commented Sep 13, 2026 •

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: f235855

The changes in this PR will be included in the next version bump.

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@codspeed

codspeed Bot commented Sep 13, 2026 •

Copy link
Copy Markdown

Merging this PR will improve performance by 2.01%

⚠️ Different runtime environments detected

Some benchmarks with significant performance changes were compared across different runtime environments,
which may affect the accuracy of the results.

Open the report in CodSpeed to investigate

⚡ 1 improved benchmark
✅ 199 untouched benchmarks
⏩ 67 skipped benchmarks1

Performance Changes

Benchmark BASE HEAD Efficiency
⚡ es/lints/libs/three 50 ms 49.1 ms +2.01%

Tip

Curious why performance improved? Comment @codspeedbot explain why performance improved on this PR, or directly use the CodSpeed MCP with your agent.


Comparing Dev-next-gen:fix/es-parser-declare-line-break (f235855) with main (5a2096f)

Open in CodSpeed

Footnotes

  1. 67 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports. ↩

…terface04

With the line break after `declare` respected, `interface` on the next
line is parsed as an expression, so the swc crate's tsc snapshots now
also report "Expression expected" there.
@Dev-next-gen

Copy link
Copy Markdown
Contributor Author

The red Test - swc jobs were my fault. The swc crate keeps its own snapshots of asiPreventsParsingAsInterface04.ts under crates/swc/tests/tsc-references, and I had only regenerated the parser one. With declare no longer swallowing the next line, interface is parsed as an expression and those snapshots gain an "Expression expected" diagnostic on line 5. I pushed f235855 with the updated .1.normal.js and .2.minified.js, and cargo test -p swc --test tsc now passes locally (4579 passed, 0 failed).

The Benchmark failures are unrelated. Benchmark swc_ecma_lints died on curl: (35) OpenSSL SSL_connect: Connection reset by peer while downloading the CodSpeed runner from release-assets.githubusercontent.com, and the other benchmark jobs were cancelled as a consequence.

@CLAassistant

CLAassistant commented Sep 14, 2026 •

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

@kdy1
Donny/강동윤 (kdy1) merged commit fe1dfad into swc-project:main Sep 14, 2026
116 checks passed
@github-actions github-actions Bot added this to the Planned milestone Sep 14, 2026
@github-actions github-actions Bot modified the milestones: Planned, v1.16.12 Sep 29, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

3 participants