Skip to content

fix(angular-compiler): preserve operator precedence in emitted binary expressions#2275

Merged
brandonroberts merged 3 commits intoanalogjs:betafrom
ashley-hunter:fix/angular-compiler-binary-expr-precedence
Apr 10, 2026
Merged

fix(angular-compiler): preserve operator precedence in emitted binary expressions#2275
brandonroberts merged 3 commits intoanalogjs:betafrom
ashley-hunter:fix/angular-compiler-binary-expr-precedence

Conversation

@ashley-hunter
Copy link
Copy Markdown
Contributor

  • The JSEmitter's visitBinaryOperatorExpr flattened parentheses when emitting binary expressions, producing invalid JS when ?? was mixed with ||/&& (a SyntaxError) and wrong semantics when lower-precedence operators were children of higher-precedence ones (e.g. (a ?? 0) + b emitted as a ?? 0 + b)
  • Added a precedence table and childNeedsParens() helper so parens are inserted only when the AST nesting requires them — handles precedence differences, left/right associativity, and the JS spec's nullish coalescing mixing prohibition
  • Broadened assignment wrapping from Assign-only to isAssignment() to cover compound assignments (+=, -=, etc.)

PR Checklist

Closes #

Affected scope

  • Primary scope: angular-compiler
  • Secondary scopes:

Recommended merge strategy for maintainer [optional]

  • Squash merge
  • Rebase merge
  • Other

Commit preservation note [optional]

What is the new behavior?

Test plan

  • nx format:check
  • pnpm build
  • pnpm test
  • Manual verification

Does this PR introduce a breaking change?

  • Yes
  • No

Other information

[optional] What gif best describes this PR or how it makes you feel?

… expressions

The JSEmitter flattened parentheses when emitting binary expressions,
producing invalid JS when ?? was mixed with ||/&& (a SyntaxError) and
wrong semantics when lower-precedence operators were children of
higher-precedence ones (e.g. (a ?? 0) + b emitted as a ?? 0 + b).

Add a precedence table and childNeedsParens() helper to
visitBinaryOperatorExpr so parens are inserted only when the AST
nesting requires them. Also broaden assignment wrapping from Assign-only
to isAssignment() to cover compound assignments.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@netlify
Copy link
Copy Markdown

netlify Bot commented Apr 10, 2026

Deploy Preview for analog-docs ready!

Name Link
🔨 Latest commit 9578302
🔍 Latest deploy log https://app.netlify.com/projects/analog-docs/deploys/69d907e428fbf5000859af10
😎 Deploy Preview https://deploy-preview-2275--analog-docs.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@netlify
Copy link
Copy Markdown

netlify Bot commented Apr 10, 2026

Deploy Preview for analog-blog ready!

Name Link
🔨 Latest commit 9578302
🔍 Latest deploy log https://app.netlify.com/projects/analog-blog/deploys/69d907e5dbd77f0008d72f45
😎 Deploy Preview https://deploy-preview-2275--analog-blog.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@netlify
Copy link
Copy Markdown

netlify Bot commented Apr 10, 2026

Deploy Preview for analog-app ready!

Name Link
🔨 Latest commit 9578302
🔍 Latest deploy log https://app.netlify.com/projects/analog-app/deploys/69d907e452fe57000835a197
😎 Deploy Preview https://deploy-preview-2275--analog-app.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@github-actions github-actions Bot added the scope:angular-compiler Changes in @analogjs/angular-compiler label Apr 10, 2026
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 10, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 8375f83e-d363-411a-97e1-bf1da961cc38

📥 Commits

Reviewing files that changed from the base of the PR and between d57e845 and 9578302.

📒 Files selected for processing (1)
  • packages/angular-compiler/src/lib/component.spec.ts
✅ Files skipped from review due to trivial changes (1)
  • packages/angular-compiler/src/lib/component.spec.ts

📝 Walkthrough

Walkthrough

This pull request introduces operator precedence and associativity handling for JavaScript binary expression emission. The changes add a precedence map and parenthesization helper function to js-emitter.ts to conditionally wrap sub-expressions based on their operator precedence, associativity (left vs. right), and special rules for nullish coalescing combined with logical operators. Comprehensive test coverage validates parenthesization scenarios including precedence boundaries, associativity, and assignment operators. A separate test in component.spec.ts verifies the behavior in template compilation contexts.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

The changes introduce focused logic for operator precedence and associativity in a single module, supported by well-structured test cases. Review complexity stems from understanding precedence rules, associativity direction, and edge cases (particularly nullish coalescing interaction with logical operators), but the scope is contained to one specific problem domain.

🚥 Pre-merge checks | ✅ 2
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The pull request title follows Conventional Commit style with scope and clearly summarizes the main fix: preserving operator precedence in emitted binary expressions.
Description check ✅ Passed The pull request description is directly related to the changeset, detailing the specific problems fixed (flattened parentheses causing invalid JS and wrong semantics) and the solutions implemented (precedence table, childNeedsParens helper, broadened assignment wrapping).

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/angular-compiler/src/lib/integration.spec.ts`:
- Around line 236-256: Add an end-to-end integration test that covers the
regression where a nullish-coalescing expression emitted next to logical
operators causes a SyntaxError: create tests in the same describe block using
compile(...) and expectCompiles(...) that exercise templates like '<div
[attr.x]="(value() ?? 0) || other"></div>' and '<div [attr.x]="(value() ?? 0) &&
other"></div>' (use the same NodeComponent pattern with value = signal<number |
null>(null)), and assert the compiled output contains the parenthesized nullish
expression e.g. '(ctx.value() ?? 0) ||' or '(ctx.value() ?? 0) &&' so the code
generator preserves grouping when mixing ?? with || and &&.

In `@packages/angular-compiler/src/lib/js-emitter.spec.ts`:
- Around line 162-167: Add a second lightweight spec that asserts compound
assignments are wrapped too: create an expression using
o.BinaryOperator.PlusAssign (e.g., const expr = bin(o.BinaryOperator.PlusAssign,
v('x'), lit(1))) and assert expect(emitAngularExpr(expr)).toBe('(x += 1)'); this
pins the broadened behavior in visitBinaryOperatorExpr() which now uses
ast.isAssignment().

In `@packages/angular-compiler/src/lib/js-emitter.ts`:
- Around line 90-124: The childNeedsParens function must also detect unary-like
left-hand operands of the exponentiation operator and force parentheses: when
parentOp === o.BinaryOperator.Exponentiation and isRhs === false, return true if
child is an instance of o.UnaryOperatorExpr, o.NotExpr, o.TypeofExpr, or
o.VoidExpr (in addition to the existing BinaryOperatorExpr handling); add this
check near the top of childNeedsParens so unary expressions on the LHS of ** are
wrapped to produce e.g. (-(a)) ** b.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: ef985fbe-9d77-4c9d-8178-2360483b630d

📥 Commits

Reviewing files that changed from the base of the PR and between 75786d1 and 1388d87.

📒 Files selected for processing (3)
  • packages/angular-compiler/src/lib/integration.spec.ts
  • packages/angular-compiler/src/lib/js-emitter.spec.ts
  • packages/angular-compiler/src/lib/js-emitter.ts

Comment thread packages/angular-compiler/src/lib/integration.spec.ts Outdated
Comment thread packages/angular-compiler/src/lib/js-emitter.spec.ts
Comment thread packages/angular-compiler/src/lib/js-emitter.ts
Copy link
Copy Markdown
Member

@brandonroberts brandonroberts left a comment

Choose a reason for hiding this comment

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

Minor cleanup

Pin the broadened isAssignment() behavior with += and ??= assertions
as requested in PR review.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@ashley-hunter
Copy link
Copy Markdown
Contributor Author

Thank you, added the additional test cases. Awesome project btw 🚀 great work as always

@brandonroberts
Copy link
Copy Markdown
Member

Thanks! This has merge conflicts as I pushed up a refactor of the integration tests

# Conflicts:
#	packages/angular-compiler/src/lib/integration.spec.ts
Copy link
Copy Markdown
Member

@brandonroberts brandonroberts left a comment

Choose a reason for hiding this comment

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

LGTM

@brandonroberts brandonroberts merged commit e2dfb5a into analogjs:beta Apr 10, 2026
27 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

scope:angular-compiler Changes in @analogjs/angular-compiler

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants