Skip to content

feat: support @boundary/@error blocks and ++/-- in event listeners (#700) - #713

Merged
Brooooooklyn merged 15 commits into
mainfrom
feat/angular-22-syntax
Nov 12, 2026
Merged

Brooooooklyn merged 15 commits into
mainfrom
feat/angular-22-syntax

Conversation

@Brooooooklyn

@Brooooooklyn Brooooooklyn commented Nov 11, 2026 •

Copy link
Copy Markdown
Member

Closes #700

Adds the two new Angular 22 template features. Output is checked against ngc 22.3.0-next.4.

template ─► html lexer ─► r3 transform ─► ingest ─► phases ─► reify
              @boundary     BoundaryBlock    Boundary*Op   boundary_conditions   ɵɵboundaryCreate
              @error        ErrorBlock                     resolve_boundaries    ɵɵboundaryUpdate / ɵɵgetBoundary

expr lexer ─► expr parser ─► IR unary ─► output emitter
   ++ / --      prefix/postfix   is_prefix    ctx.count++ / --ctx.n
                (listeners only)

@boundary { } @error { }

  • Ported from upstream 54ed62d240 and the v22.3.0-next.4 sources: block parsing and checks, IR ops, boundary_conditions and resolve_boundaries phases, slots/vars/naming, reify.
  • Supports @error (let e; when …), several @error blocks, $error / $reset, nesting, and use inside @for / @if / @defer.
  • Pipes and arrow functions in a when condition match ngc. This includes one ngc quirk we copy for parity: a pipe in when is created twice in the same slot.

++ / --

  • Ported from upstream 7b9c65691c, 76e5beb931, ec69ef76e8, 6ea13efc20.
  • Allowed in event listeners and inside arrow functions. In a binding it gives Bindings cannot contain assignments. Bad targets (a?.b++, 5++, foo()++) give the same errors as upstream.
  • Side fix: (-count).toFixed() used to lose its parentheses (which changed what it did). It now keeps them, as ngc does.

Reference submodule

  • Moved to v22.3.0-next.4 so the specs for both features get extracted.
  • Conformance: 1430 / 1456. All ++/-- and @boundary specs pass. The 26 that fail are other new 22.3 features left for follow-ups: @content, <?xml ?>, template-literal error messages, regex escaped slash, <script> in custom namespaces, CSS nesting.

Known gaps (same as today for =)

  • No template type checker, so writing to a template variable or @let (i++) is not rejected. ngc's type checker rejects it.
  • Host bindings and @if (…) conditions don't report ++ errors.
  • i18n around a @boundary, or element placeholders in an i18n block inside @error, is a compile error. ngtsc 22.3.0-next.4 fails on the same templates, so we match it instead of guessing. i18n that ngtsc compiles there (text, {{x}}, ICU, attributes, the primary view) works.
  • (count++)?.x in legacy ?. mode stores the update in a temp. ngtsc runs count++ twice here; we don't copy that.

Tests

  • boundary_test.rs: all 7 upstream r3_view_compiler_boundaries goldens, plus edge cases, diagnostics and a linker round-trip.
  • increment_decrement_test.rs: the operators.ts golden, plus precedence, errors and the linker.
  • cargo test --all-features: 4187 passed. napi pnpm test: 572 passed.

🤖 Generated with Claude Code

Brooooooklyn and others added 10 commits November 11, 2026 23:02
Angular 22.2 added @boundary/@error blocks and 22.3.0-next added ++/--
in event listeners. Move the reference submodule so their specs are
extracted, and regenerate the fixtures and snapshot.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Angular 22.3 lexes `++`/`--` as operators instead of reporting an error.
`+`/`-` now go through one `scan_plus_or_minus` (like upstream
`scanPlusOrMinus`), which the template-literal scanner shares, so the
longest operator wins: `a+++b` is `a`, `++`, `+`, `b`.

Ported from angular/angular 7b9c65691c.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Add `Increment`/`Decrement` to the output `UnaryOperator` and an
`is_prefix` flag to `UnaryOperatorExpr`, and print postfix operators
after their operand (upstream ec69ef76e8).

ngc prints through TypeScript's printer, which keeps `- --a` apart and
parenthesizes a unary operand of a member access. Our emitter prints
JavaScript directly, so it now does the same: a space between a prefix
`+`/`-` and an operand starting with the same character (`---a` would
parse as `--(-a)`), and parentheses around a unary receiver of a
property, keyed read or call (`(a++).b`, `(-a).toFixed()`).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Angular 22.3 allows the increment and decrement operators in event
listener statements and inside arrow functions; anywhere else they are a
binding error. Port the parser and template pipeline parts:

- `Unary` gets `Increment`/`Decrement` operators and an `is_prefix`
  flag (upstream `Unary.createPrefixUpdate` / `createPostfixUpdate`).
- `parse_prefix` reads `++a`/`--a`, `parse_call_chain` reads `a++`/`a--`
  after the member/call chain, and `validate_increment_decrement_target`
  reports `Bindings cannot contain assignments` outside actions,
  `The '?.' operator cannot be used in the assignment` for safe reads and
  `The expression cannot be used in the assignment` for anything but a
  property or keyed read (parentheses and `!` are looked through, as in
  upstream `unwrapWriteTarget`). Invalid updates parse to an empty
  expression, like upstream.
- `++`/`--` on the left of `**` is allowed (JS update expressions).
- A token left over after an expression is quoted in the chain error
  (`Unexpected token '.'`), as upstream does.
- The IR `UnaryExpr`, ingest, name resolution and reify carry the
  operator and `is_prefix` through to the output AST.
- The conformance unparser/serializer print postfix operators and keep
  adjacent prefix operators apart (`- -foo`, `+ ++foo`).

Conformance: every `++`/`--` lexer, parser and serializer spec passes
(1292 -> 1429 of 1456). Emit tests compare with ngc 22.3.0-next.4 output,
including the `r3_view_compiler/operators.ts` compliance case, nested
views, host listeners, arrow functions in bindings and partial output
round-tripped through the linker.

Ported from angular/angular 76e5beb931 and 6ea13efc20. The linker part
(ead4f0f56d, postfix in the Babel AST factory) needs no port: our linker
runs the same pipeline and emitter.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…zed update tag

- `++;`, `foo(++)`, `[++]` now report `Unexpected token X` as Angular's
  `parsePrimary` does, instead of compiling to `undefined`.
- An invalid update inside a pipe (`a++ | json`, `a | foo:b++`) no longer
  adds a second `Unexpected token |` / `Unexpected end of expression` error.
- `(count++)\`x\`` keeps its parentheses, as ngc prints it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Port Angular 22.3's `@boundary` block and its `@error` blocks.

Parser and R3 AST:
- lex `@boundary` as a block and connect `@error` blocks to it
- add `R3BoundaryBlock` / `R3BoundaryErrorBlock`
- parse `@error` parameters (`let` aliases, `when` condition) with the
  same messages and spans as `createBoundaryBlock`

Template pipeline:
- `BoundaryCreate` / `BoundaryErrorCreate` create ops and a `Boundary`
  update op, plus a `BoundaryState` expression
- new phases `generateBoundaryConditions` and `resolveBoundaries`, in
  the same place as upstream
- `$error` aliases read `bnd.error` in the parent view and the error
  view context inside the `@error` view
- reify to `ɵɵboundaryCreate`, `ɵɵconditionalBranchCreate`,
  `ɵɵboundaryUpdate` and `ɵɵgetBoundary`

Conformance: the r3_transform boundary spec now passes (1293/1456).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- 27 output tests checked against ngc 22.3.0-next.4 (the 7 compliance
  goldens plus edge cases: nesting, @if/@for/@defer, @let, local refs,
  listeners, svg, i18n, ng-template, sibling boundaries)
- each one is also compiled in partial mode and linked; the linked
  template must match the full output
- diagnostics with exact messages and spans from `parseTemplate`
- a pipe used only in a `when` condition

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
 whitespace

- The boundary state variable is now initialized with a separate
  `GetBoundary` IR expression (Angular's `ng.getBoundary(SlotLiteralExpr)`),
  so resolve_boundaries no longer rewrites it into `const bnd_rN = bnd_rN`
  inside arrow functions.
- resolve_boundaries fills its map in `unit.ops()` order (arrow functions,
  create, update), so the update list's variable wins as in Angular.
- `when` conditions are moved into `processed` instead of cloned. Visitors
  reach them twice through `BoundaryUpdateOp::when_exprs`, like Angular's
  shared nodes: one arrow function per condition, var counts and usage counts
  unchanged, and a pipe in a condition is created twice on one slot, as ngc
  does.
- Single-use `bnd` inlining skips reads inside hoisted arrow functions when
  counting and replaces them too, as Angular's shared `bnd.error` node does.
- `@error` parameters use JavaScript's `\s` / `trim` (U+FEFF yes, U+0085 no).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@Brooooooklyn

Copy link
Copy Markdown
Member Author

@codex review

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Nov 11, 2026 •

Copy link
Copy Markdown
Codex Review Summary: ✅ Completed

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-11-12T04:17:34.929193Z 6f2dba1 Manual request
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 2f324aac0b

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Brooooooklyn and others added 2 commits November 12, 2026 01:35
…gation

`(count++)?.toString()` with legacy optional chaining ran `count++` in
both the null check and the read. Treat update operators as having side
effects so the value goes into a temporary. ngc has the same bug here;
we don't copy it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
An i18n block inside an @error view lost its element placeholders,
because the i18n phases never walked into BoundaryErrorCreate views.
ngc skips them too and crashes on such templates.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@Brooooooklyn

Copy link
Copy Markdown
Member Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: e05297f323

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Revert e05297f, which made i18n inside an @error view compile by
guessing at output ngtsc doesn't produce. ngtsc 22.3.0-next.4 fails on
every template where an i18n message has an element or block
placeholder with no value: an i18n block around a @boundary, or element
placeholders in an i18n block inside an @error view. Match that: such a
placeholder is now a compile error instead of an empty string. i18n that
ngtsc compiles (text, interpolation, ICU, attributes, the primary view)
is unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@Brooooooklyn

Copy link
Copy Markdown
Member Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 50a86eda86

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

ngtsc 22.3.0-next.4 reads `count++ / 2` as the start of a regular
expression and fails with the same lexer error we give.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@Brooooooklyn

Copy link
Copy Markdown
Member Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Keep them coming!

Reviewed commit: d437fa4335

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@Brooooooklyn

Copy link
Copy Markdown
Member Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Keep it up!

Reviewed commit: 6f2dba12ed

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@Brooooooklyn
Brooooooklyn merged commit dea5af1 into main Nov 12, 2026
10 checks passed
@Brooooooklyn
Brooooooklyn deleted the feat/angular-22-syntax branch November 12, 2026 06:23
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.

Support newer Angular 22 syntax

1 participant