Skip to content

fix(import-elision): keep imports the output still references - #622

Open
Brooooooklyn wants to merge 1 commit into
mainfrom
fix/elision-output-references
Open

Brooooooklyn wants to merge 1 commit into
mainfrom
fix/elision-output-references

Conversation

@Brooooooklyn

@Brooooooklyn Brooooooklyn commented Oct 23, 2026 •

Copy link
Copy Markdown
Member

Follow-up to #606 and #621.

Problem

Without class metadata (advancedOptimizations / emitClassMetadata: false), import elision drops an import used only in decorators that compilation removes. A walk over local names guessed which decorators those were. Some imports were therefore dropped while the output still named them, and the module threw a ReferenceError on load:

source output still has was
@Inject(forwardRef(() => TOKEN)), @Inject(TOKEN.sub) the argument as written, in the factory TOKEN elided
@ViewChild(Foo) declare q / { read: Bar } ɵɵviewQuery(Foo, …) Foo elided
@Input({ transform: toNum }) declare v inputs: { v: [.., toNum] } toNum elided
@Dec(TOKEN) declare p (not Angular's) __decorate([Dec(TOKEN)], …) TOKEN elided
@Inject(TOKEN) on a class with no Angular decorator (plain, abstract base) the decorator Inject, TOKEN elided
constructor overloads (Angular's ctor_overload.ts compliance case) the implementation's @Optional() Optional elided
a component whose template is missing the decorator Inject, LOCALE_ID elided

Fix

The name-based walk is gone (about 350 lines). A reference counts as removed only when the transform strips it and compiles nothing that names it. That happens only on a class with an Angular class decorator:

  • Constructor parameters: the first constructor's Angular param decorators (aliases resolved), plus the argument of @Inject(X) when it is a bare identifier, since the factory names it iN.X.
  • declare fields: the callee of an Angular decorator, but not its arguments.

An import is elided when every value reference to it is removed. After compiling, the transform also keeps whatever an Angular decorator references when that decorator was left in place after all.

A few rare shapes now keep an import that could have gone, which is safe: @Inject(TOKEN.sub) on a component, @ViewChild(forwardRef(…)) declare, @Input({ alias: CONST }) declare.

Checks

  • New tests/import_elision_output_references_test.rs: 12 tests compile through transform_angular_file with advanced_optimizations. Each "kept" test also checks that the output names the import, so nothing is kept for no reason. 11 fail on the base. The control (@Optional() @Inject(TOKEN) on an @Injectable still drops all three; the output has i1.TOKEN) passes on both.
  • test_declare_property_viewchild_arg_elided asserted the wrong elision (the query names the predicate). It is now …_kept.
  • cargo test --workspace: 3427 passed. --all-features: 3528 passed. vitest: 376 passed. Strict compare: 1162 fixtures, 0 mismatches.
  • Real sources: 2294 local Angular .ts files (14,625 imports) compiled with advancedOptimizations, checked against their own output. Elided imports the output still uses went from 3 on the base to 0. Imports kept but unused: 0. Only those 3 imports changed.

Not changed: with constructor overloads, the factory still reads the first constructor, not the implementation as ngtsc does. The import is kept now, so the module loads, but the factory's deps still differ from ngtsc's.

🤖 Generated with Claude Code

@Brooooooklyn

Copy link
Copy Markdown
Member Author

@codex review

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 23, 2026 •

Copy link
Copy Markdown

Codex Review Summary

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

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-23T12:20:59.322540Z e0b5811 New commits
ℹ️ 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.

@Brooooooklyn
Brooooooklyn changed the base branch from fix/inject-alias-elision to main October 23, 2026 11:17
@Brooooooklyn Brooooooklyn reopened this Oct 23, 2026
@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. What shall we delve into next?

Reviewed commit: c282ad524e

ℹ️ 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".

Without class metadata, an import used only in decorators compilation
removes is elided. A name-based walk guessed which decorators those were,
so some imports were dropped while the output still named them, and the
module threw on load:

- `@Inject(forwardRef(() => T))` / `@Inject(NS.x)`: the factory copies
  the argument as written
- `@ViewChild(Foo)` / `{ read: Bar }` / `@Input({ transform: fn })` on
  a `declare` field: compiled into the definition by local name
- a non-Angular decorator on a `declare` field, Angular param decorators
  on a class that isn't compiled, a constructor after the first: they stay

The walk is gone. A reference counts as removed only when the transform
strips it and compiles nothing that names it: on a class with an Angular
class decorator, the first constructor's Angular param decorators (with a
bare `@Inject(X)` argument, which the factory names as `iN.X`) and the
callee of an Angular decorator on a `declare` field. The transform keeps
what an Angular decorator it didn't strip after all references.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@Brooooooklyn
Brooooooklyn force-pushed the fix/elision-output-references branch from c282ad5 to e0b5811 Compare October 23, 2026 12:18
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.

1 participant