Skip to content

fix(angular-compiler): make hoisting dependency-aware to prevent TDZ#2286

Merged
brandonroberts merged 2 commits into
analogjs:betafrom
ashley-hunter:fix/tdz-hoisting-dependency-aware
Apr 13, 2026
Merged

fix(angular-compiler): make hoisting dependency-aware to prevent TDZ#2286
brandonroberts merged 2 commits into
analogjs:betafrom
ashley-hunter:fix/tdz-hoisting-dependency-aware

Conversation

@ashley-hunter

@ashley-hunter ashley-hunter commented Apr 13, 2026

Copy link
Copy Markdown
Contributor

Currently the compile.ts file hoists non-exported variable/function statements before the first class so Ivy static definitions can reference file-level constants. However, it indiscriminately hoisted all such statements, including user-authored code that references classes, enums, or exported names defined after the first class - moving the reference before its definition and creating a TDZ error.

Replace the single-pass hoist with a two-pass dependency-aware algorithm (two lightweight iterations over the same top-level statements):

  • Pass 1: collect names from statements that won't be hoisted (classes, enums, exported vars/functions after the first class)
  • Pass 2: for each candidate, check if it references any non-hoistable name; if so, skip it and mark its own names as
    non-hoistable (transitivity)

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?

…ent TDZ

Step 2a hoists non-exported variable/function statements before the first
class so Ivy static definitions can reference file-level constants. However,
it indiscriminately hoisted all such statements, including user-authored code
that references classes, enums, or exported names defined after the first
class — moving the reference before its definition and creating a TDZ error.

Replace the single-pass hoist with a two-pass dependency-aware algorithm that
adds negligible overhead (two lightweight iterations over the same top-level
statements, with O(1) Set lookups):
- Pass 1: collect names from statements that won't be hoisted (classes, enums,
  exported vars/functions after the first class)
- Pass 2: for each candidate, check if it references any non-hoistable name;
  if so, skip it and mark its own names as non-hoistable (transitivity)

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

netlify Bot commented Apr 13, 2026

Copy link
Copy Markdown

Deploy Preview for analog-docs ready!

Name Link
🔨 Latest commit 8f34de6
🔍 Latest deploy log https://app.netlify.com/projects/analog-docs/deploys/69dd15e7caedd500085e239e
😎 Deploy Preview https://deploy-preview-2286--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

netlify Bot commented Apr 13, 2026

Copy link
Copy Markdown

Deploy Preview for analog-blog ready!

Name Link
🔨 Latest commit 8f34de6
🔍 Latest deploy log https://app.netlify.com/projects/analog-blog/deploys/69dd15e7255a4d0008f8d305
😎 Deploy Preview https://deploy-preview-2286--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

netlify Bot commented Apr 13, 2026

Copy link
Copy Markdown

Deploy Preview for analog-app ready!

Name Link
🔨 Latest commit 8f34de6
🔍 Latest deploy log https://app.netlify.com/projects/analog-app/deploys/69dd15e7554fd400087b8abb
😎 Deploy Preview https://deploy-preview-2286--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.

@coderabbitai

coderabbitai Bot commented Apr 13, 2026

Copy link
Copy Markdown

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: cc93c92c-15a6-4820-be20-d105f92facf4

📥 Commits

Reviewing files that changed from the base of the PR and between 3dfa543 and 8f34de6.

📒 Files selected for processing (2)
  • packages/angular-compiler/src/lib/compile.ts
  • packages/angular-compiler/src/lib/component.spec.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/angular-compiler/src/lib/compile.ts
  • packages/angular-compiler/src/lib/component.spec.ts

📝 Walkthrough

Walkthrough

This PR augments the compiler hoisting pass with TDZ-safety analysis. New helpers (collectIdentifiers, collectBindingNames, getDefinedNames) analyze referenced and defined names while skipping traversal into function/class bodies. Hoisting is reworked to compute firstClassPos (start of first class) and run a two-pass algorithm using a nonHoistableNames set: Pass 1 marks names from non-hoistable top-level declarations after firstClassPos; Pass 2 skips hoisting candidates that eagerly reference any non-hoistable name and propagates transitive non-hoistability. Tests added cover many positive and negative hoisting scenarios.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

🚥 Pre-merge checks | ✅ 2
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title follows Conventional Commit style with the supported scope 'angular-compiler' and clearly describes the main fix: making hoisting dependency-aware to prevent temporal dead zone (TDZ) errors.
Description check ✅ Passed The description is directly related to the changeset, clearly explaining the problem (indiscriminate hoisting causing TDZ), the solution (two-pass dependency-aware algorithm), and provides implementation details that match the code changes.

✏️ 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.

@github-actions github-actions Bot added the scope:angular-compiler Changes in @analogjs/angular-compiler label Apr 13, 2026
@ashley-hunter ashley-hunter changed the title fix(angular-compiler): make Step 2a hoisting dependency-aware to prevent TDZ fix(angular-compiler): make hoisting dependency-aware to prevent TDZ Apr 13, 2026
@ashley-hunter ashley-hunter marked this pull request as ready for review April 13, 2026 15:59

@coderabbitai coderabbitai 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.

Actionable comments posted: 1

🤖 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/compile.ts`:
- Around line 126-137: collectIdentifiers currently descends into nested
function/arrow/class bodies and thus collects identifiers from lazily-evaluated
code; update the walk() inside collectIdentifiers to stop recursing into nested
function-like and class-like nodes (e.g. where ts.isFunctionLike(n) ||
ts.isClassLike(n)) so that when such a node is encountered you still handle the
node itself (identifiers on the node) but do not call ts.forEachChild on its
body; this ensures only eagerly-executed parts of initializers are scanned and
prevents lazy references (like in ArrowFunction/FunctionExpression/Class bodies)
from being treated as dependencies.
🪄 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: 4162fe34-f2ea-4e7e-94d2-24db3f0d4329

📥 Commits

Reviewing files that changed from the base of the PR and between 5bc376f and 3dfa543.

📒 Files selected for processing (2)
  • packages/angular-compiler/src/lib/compile.ts
  • packages/angular-compiler/src/lib/component.spec.ts

Comment thread packages/angular-compiler/src/lib/compile.ts Outdated
…ent false TDZ blocks

collectIdentifiers walked into arrow/function/class bodies, treating
lazy references as eager dependencies. This caused constants like
`const TOKEN = () => LaterClass` to be incorrectly blocked from
hoisting. Stop recursing into function-like and class-like nodes so
only eagerly-evaluated identifiers are collected.

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

@brandonroberts brandonroberts left a comment

Copy link
Copy Markdown
Member

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 f33f6b5 into analogjs:beta Apr 13, 2026
33 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