Repository navigation
fix: close older ngtsc parity gaps found while reviewing #660 - #662
Conversation
A useClass with deps is instantiated as `new useClass(...deps)`, but the emitter printed the class expression bare. For `useClass: makeClass()` that gave `new makeClass()(dep)`, which constructs `makeClass` itself and throws "Class constructors cannot be invoked without 'new'"; for `useClass: Maybe?.cls` it gave `new Maybe?.cls(dep)`, a SyntaxError. ngtsc's `ts.factory.createNewExpression` runs the callee through `parenthesizeExpressionOfNew`, which wraps a callee whose left spine holds a call, an optional chain or a non-left-hand-side expression: `new (makeClass())(i0.ɵɵinject(DEP))`. Port that rule for Instantiate. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…g it
A factory written as an optional chain (`useFactory: Maybe?.make`, or
`@Service({ factory: Maybe?.make })`, with or without deps) was called
as `Maybe?.make()`, which joins the chain and silently returns
undefined when `Maybe` is nullish. A `useClass: Maybe?.cls` without
deps had the same problem: `Maybe?.cls.ɵfac(__ngFactoryType__)`.
ngtsc calls the user factory through `ts.factory.createCallExpression`,
whose `parenthesizeLeftSideOfAccess` wraps an optional chain:
`(Maybe?.make)()`, which throws instead. Property access does the same,
`(Maybe?.cls).ɵfac(__ngFactoryType__)`. Wrap such expressions the same
way in the useFactory wrapper, the delegated factory, the service
factory and the useClass delegation.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`useClass: X, deps: []` was read as having no deps, so full output delegated to `X.ɵfac` and partial output dropped `deps: []`. A plain class (`useClass: Holder.cls()`) has no `ɵfac`, so it threw "Holder.cls(...).ɵfac is not a function" in both modes. ngtsc only checks whether the `deps` key is present: full output builds `new (Holder.cls())()` in the conditional factory, and partial output keeps `useClass: Holder.cls(), deps: []`. Track a missing `deps` key as `None` for useClass and build the delegated factory for an empty list. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A string predicate like `@ViewChild('x, ')` is split on `,` into its refs.
The file transform dropped the empty parts (`["x"]`), and the linker did not
split at all: it passed `["y, z"]` through as one ref. Both trimmed with
Rust's white space set, which strips U+0085 and keeps U+FEFF.
Angular's `getQueryPredicate` does `selector.split(',').map(t => t.trim())`
for every string of the predicate, keeping empty parts: ngtsc 22.2.3 gives
`["x", ""]`, and Angular's linker splits a partial `["y, z"]` into
`["y", "z"]`. `String.prototype.trim` strips U+FEFF and keeps U+0085
(`["x"]` for `'x'`, `["y\u0085"]` for `'y\u0085'`). Do the same in
both paths, for decorator and signal queries.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The linker emitted string query predicates inline, as `ɵɵviewQuery(["x"], 5)`, and started the host bindings' pool at `_c0`. Angular's linker compiles a declaration with `compileComponentFromMetadata` / `compileDirectiveFromMetadata` and one `ConstantPool`: `getQueryPredicate` pools each string predicate with `getConstLiteral(..., true)`, content queries first, then view queries, before the host bindings' constants and the template's, which reuse equal literals (`["*"]` for `ngContentSelectors`). A predicate with a lone surrogate (`["\uD800"]`) is split and pooled like any other. Pool them the same way, in that order. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The linker never emitted `attrs`, whatever `angularVersion` was. The file transform does since 1a577e6, for versions below 22.0.0-next.4. Angular 21's linker runs `compileComponentFromMetadata`, which pools the first selector's attributes after `baseDirectiveFields` (the query predicates and the host bindings' constants) and before the template, and sets `attrs` after `features`: a `span[bitBadge]` component linked with 21.2.25 gives `const _c0 = ["bitBadge", ""]; ... attrs: _c0, ngContentSelectors: _c1`. Do the same when `angularVersion` asks for it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Each component numbered its i18n_N and MSG_ names from its own index, so the second component of a file started again at i18n_0 and MSG_..._0, and the Closure names had an extra underscore after the file suffix (MSG_TEST_TS__0). ngtsc claims both names with pool.uniqueName, so they run on across the file: i18n_ counts on its own, the external Closure name counts the file suffix (MSG_EXTERNAL_<id>$$TEST_TS_0), and the other one counts MSG_<suffix> (MSG_TEST_TS_0). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A template declared all its pooled values first and then the pool's statements, so a DepsFn pooled before a pure function was declared after it: _c0, A_Defer_1_DepsFn, _forTrack0. ngtsc's ConstantPool.statements is one list in insertion order, which gives A_Defer_1_DepsFn, _c0, _forTrack0. The pool now records how many statements came before each value, and emit interleaves them. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
compileTemplate (compile_template_to_js_with_options) compiled the template first and the host bindings after, so the template's constants took the first _c names and a host constant equal to a template one reused the template's. The file transform and the linker already do host first. ngtsc's compileComponentFromMetadata compiles the host bindings (baseDirectiveFields) before the template with one pool: the host bindings' constants come first, and the template reuses equal ones. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Partial compilation returned before the `@defer` step, so an `imports` entry used only inside `@defer` kept its static import and an eager `dependencies` entry, and the declaration had no `deferBlockDependencies`. ngtsc's `compilePartial` resolves the blocks as its full compilation does: the entry leaves `dependencies`, its static import is removed, `ɵɵngDeclareComponent` lists one dependency function per block in `deferBlockDependencies` (null for a block without dependencies), and `ɵɵngDeclareClassMetadataAsync` takes the lazy class as a `resolveMetadata` parameter. The `@defer` step now runs for both modes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
An `imports` entry that doesn't name a class, a spread (`imports: [ChildA, ...SHARED]`), a nested array (`[ChildA, [OtherB]]`) or a namespace member (`[ChildA, ns.ChildB]`), made the `@defer` step give up on the whole component, so ChildA, used only inside `@defer`, stayed an eager dependency and kept its static import, in that component and in any other of the file that defers it. ngtsc's `registerDeferrableCandidate` only skips such an entry, which is not an identifier: ChildA is still lazy-loaded, and the classes the entry brings in stay eager, even when one of them is the deferred class. These entries are now skipped the same way. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The transform only walked top-level statements, so a @component, @directive, @pipe, @Injectable or @NgModule class declared in a function body, block, arrow or IIFE kept its `__decorate` call and needed the JIT compiler at runtime. ngtsc compiles these classes like top-level ones: the definitions go on the class, `setClassMetadata` / `setClassDebugInfo` / the HMR initializer right after it in the same scope, and the constant pool and child template functions at the file top level, after the imports, shared with the file's other classes (the function may run before its own statement). A class declared in another class's members compiles before that class. Now the transform does the same, keys class definitions by span (two functions may declare classes of one name), keeps nested classes out of the .d.ts, and finds them for import elision, resource URLs and HMR updates. A nested class whose metadata or constructor parameter types read a binding of the enclosing function keeps its decorators: the evaluator and import map only see top-level bindings, and would otherwise miss a local const or use the binding a local one shadows. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 85da8a6256
ℹ️ 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".
… class A nested class can reuse a constant an earlier top-level class pooled, which was declared before that earlier class. A function declared later in the file can run before that statement, so the nested class read the constant in its TDZ (ReferenceError). Declare the constants of every class compiled before the last nested one after the imports too, where ngtsc declares them all. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The count of other components' deferrable `imports` entries walked only top-level classes. A nested component that defers an import counted the top-level ones and removed the static import, while the top-level component missed the nested mark and kept an eager reference: ReferenceError when the module loads. Two nested components deferring one import both stayed eager. Walk every class the transform compiles, as ngc's DeferredSymbolTracker sees every component of the file. A nested class that keeps its decorators (it reads an enclosing binding) keeps its `imports` entry and is skipped. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 06b1fb0003
ℹ️ 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".
HMR finds a component by {path}@{name}. Two classes named Tp in different
functions shared that id, so both listeners took one update, and the update
compiler always picked the first Tp: changing the second component's template
could install the first's on both. ngtsc builds the same id for both.
Compile a class whose name another class of the file declares without HMR
(no initializer, no template update), and give it no HMR update.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 713bf03a71
ℹ️ 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".
The guard that keeps a nested class's decorators when its metadata reads an
enclosing binding visited only the arguments of a field initializer call. With
`function make(input) { class C { value = input(0); } }` the callee resolved
to the imported `input` by name, so C got a signal input ngtsc doesn't see.
Visit the callee too.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
@codex review |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: f4d0a67980
ℹ️ 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".
The linker built the useClass factory as the text `{useClass}.ɵfac(...)`.
A conditional useClass then read `flag ? A : B.ɵfac(...)`, and an optional
chain read `Maybe?.cls.ɵfac(...)`, pulling .ɵfac into the chain.
Angular's linker builds this as an AST, so TS's parenthesizeLeftSideOfAccess
wraps any receiver that is not a plain left-hand side or is an optional
chain: `(flag ? A : B).ɵfac(...)`, `(Maybe?.cls).ɵfac(...)`. The other
string-built templates already wrap their operands (`new (X)(...)`,
`(f)(...)`).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`useFactory: make, deps: []` was read as having no deps, so full output wrapped it as `() => make()` and partial output dropped `deps: []`. ngtsc only checks whether the `deps` key is present, as for useClass: full output builds the delegated conditional factory, and partial output keeps `useFactory: make, deps: []`, which the linkers turn into the same conditional factory. Track a missing `deps` key as `None` for useFactory. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
In partial mode a @service class got a full ɵɵdefineService and a plain ɵfac function, so a library build shipped already-linked code. ngtsc's ServiceDecoratorHandler.compilePartial emits ɵɵngDeclareFactory({..., deps: [], target: ɵɵFactoryTarget.Service}) and ɵɵngDeclareService({minVersion: "22.0.0", ..., type, autoProvided?: false, factory?}), keeping the user factory as written. Emit the same; the linker already links ɵɵngDeclareService. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The HMR fingerprint only looked at top-level classes, so the inline template and styles of a component declared in a function stayed in it. An edit to them changed the fingerprint and the vite plugin reloaded the page, though the transform compiles that component with HMR (as ngtsc does) and an update would do. Walk every class the transform compiles, nested ones too, and leave out a name two classes declare, which compiles without HMR. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
setClassDebugInfo's lineNumber came from the class span start. A class with no `export` (and every nested class) has its decorators in that span, so the line was the decorator's: 3 for `@Component(...)\nclass Foo` on lines 3-4. ngtsc takes the line of `clazz.name.pos`, the end of the `class` keyword. Find that keyword after the last decorator, past modifiers and comments, and take its line. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A decorated class in a `namespace` body was left as is: the class walk skipped module blocks. ngtsc compiles it like a class nested in a function (definitions on the class, constants and child templates at the file top), but only adds statements after a class in a block or the file, so a namespace member gets no class metadata, debug info, NgModule scope or HMR code. It is in the .d.ts, inside its namespace: its declaration is named `NS.App`, so the injection finds that class and not a same-named one elsewhere. As it gets no HMR code, it does not make a top-level class of its name ambiguous for HMR, and its template stays in the HMR fingerprint. A `declare` namespace or module is still left alone. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The linker emitted one statement per query creation call (ɵɵviewQuery(_c0, 5); ɵɵviewQuery(_c1, 5);) and one ɵɵqueryAdvance() per signal query. Angular's linker compiles the queries with createViewQueriesFunction / createContentQueriesFunction, which chain the calls of one instruction (ɵɵviewQuery(_c0, 5)(_c1, 5)) and collapse consecutive advances into one (ɵɵqueryAdvance(3)). Do the same, with the file transform's version gate for the chaining: runtimes before v21.0.4 return nothing from the query instructions. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…omOnly mode The linker always compiled templates in Full mode, so a standalone component with no directive dependencies got ɵɵelementStart and ɵɵproperty where Angular 22's linker emits ɵɵdomElementStart and ɵɵdomProperty. The version was not the cause: an explicit 22.2.2 gave the same output. Angular's linker sets hasDirectiveDependencies to !isStandalone, a directive, component or NgModule dependency, or a @defer block with a dependency function, and compileComponentFromMetadata picks DomOnly without them. Do the same, for Angular 20.1 and later (and an unset version), like the file transform. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The linker compiled templates without a context file path and with external ids, so its Closure message names were MSG_APP_0 (or MSG_EXTERNAL_<id>$$APP_1 for a custom id). Angular's linker sets relativeContextFilePath to the linked file's path and i18nUseExternalIds to false, which gives MSG__X_LIB_A_COMPONENT_MJS_0 for /x/lib/a.component.mjs. Pass the same. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The linker compiled each partial declaration with its own constant pool and kept the constants in an IIFE per declaration, so the _cN, i18n_N and MSG_ numbers restarted for each declaration of a file and equal constants (query predicates, pure functions, track functions, long styles) were declared again. Angular's FileLinker gives the declarations whose ngImport is bound in the same module, function declaration or function expression one EmitScope with one ConstantPool, whose statements the babel plugin puts once at the top of that scope: before the first statement that is not an import, or at the start of the function body. Any other binding (an arrow function, a method, a global) gets a LocalEmitScope, which keeps the constants in an IIFE. The main template function goes inline in the definition. A long style is inline on its first use and shared from its second; a first use in an earlier declaration stays inline, since that declaration has been emitted already. Do the same: find the binding of ngImport with oxc_semantic, carry the pool of its scope from one declaration to the next, insert the scope's statements once, inline the template function, pool the styles after the template, and name the host binding function with the pool, as Angular's addNamesToView does (A_HostBindings1 for a second class A). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Since the linker shares one constant pool per scope, a @defer dependency function went to the top of the scope binding ngImport. When it used a binding of a scope inside it, such as a class declared in the function that declares the component, it threw a ReferenceError there. Angular 22.2.3's linker moves it to the top all the same, so its output breaks too. Keep such a declaration in a local IIFE with its own pool, as Angular's LocalEmitScope does and as the linker did before. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Without `resolveImportedValues`, a `@defer` block that uses a component declared in the same file got no dependency function, and `ɵɵdefer` had no resolver argument. ngtsc's full compilation emits one per block (`const A_Defer_1_DepsFn = () => [Dep]`) and drops a class used only inside `@defer` from `dependencies`. The selector of a class of the file needs no other file, so it is now read without cross-file analysis too. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With `imports: IMPORTS` and `const IMPORTS = [Dep, Dep2]` in the same file, no `@defer` block got its own dependency function. ngtsc resolves the `const` and lists the classes each block uses in a per-block `DepsFn`, in full and partial mode. It reads each class through the identifier in the `const`'s array, so components naming the same array share one function. Like its partial evaluator, the array is read through `let`, `as const` and a variable naming another one. The classes stay eager, since only entries of an `imports` array literal can be lazy-loaded. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A `@defer` block's dependency function left out the classes that a nested array (`[OtherB]`), a spread of a same-file `const` array or a namespace member (`ns.ChildB`) brings in. ngtsc flattens and resolves `imports` into its scope, so the block's `DepsFn` lists them, eagerly (`[ChildA, OtherB]`), and lists a class two entries name once: for `[ChildA, ns.ChildA, ns.ChildB]` it gives `[ChildA, i1.ChildB]`. The namespace member is emitted as written (`ns.ChildB`) rather than through a new import. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The partial declaration's `dependencies` was built only from the plain identifiers of an `imports` array literal, so a nested array (`[OtherB]`), a namespace member (`ns.ChildB`), a spread of a same-file `const` array, a `forwardRef(() => X)` entry and a non-literal `imports: IMPORTS` were dropped, with or without `@defer`. ngtsc flattens and resolves `imports` before it lists `dependencies`. They are now listed once per class: `Cmp` and `ns.Cmp` are one class when they read one export of one module, or when cross-file analysis finds one declaration, and a class `@defer` lazy-loads is left out however else `imports` names it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A standalone pipe in `imports` used only inside `@defer` stayed eager,
in full and partial mode: `@defer` matched the template against
selectors only. ngtsc's binder records the pipes each block uses, so the
pipe is listed in the block's `DepsFn` as `import("./p").then(m => m.P)`,
its static import is removed and the class metadata is set
asynchronously. Pipes are now matched by their name, listed after the
directives, and a `when` condition counts toward the enclosing template.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Template i18n messages only had a custom @@id, so with i18nUseExternalIds a message without one was named MSG_<suffix>N. ngtsc gives every message an id in its first I18nMetaVisitor pass (_setMessageId: the custom id, else decimalDigest of the message before whitespace removal; a sole ICU takes its element's id) and names it MSG_EXTERNAL_<id>$$<suffix>N, ICU sub-messages and i18n-* attributes included. The $localize meta block keeps only custom ids, as in ngtsc. The linker now compiles with i18nUseExternalIds false, as Angular's linker does, so its names keep the MSG_<suffix>N form. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
All arrow functions of a view were hoisted before any create operation was reified, so an arrowFn of a binding was pooled before the _forTrack of a @for in the same view. ngtsc's reify hoists each create operation's expressions just before reifying it (pooling _forTrack with its repeaterCreate), and the update operations' arrow functions after all create operations, which declares _forTrack0 before arrowFn0. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Each class's constants and child view functions were declared just before that class (only files with a nested class put them after the imports), so a file's pool was split among its classes, and a long style that a later class shared was declared with the earlier class's part. ngtsc declares all of its constant pool's statements after the imports, before the first class, in pool order, and pools a shared style while the later class compiles. Every class's declarations now go there, joined into one edit in compile order, with a shared style declared with the class that shared it. When a statement comes before the last import (ngtsc moves the imports up), the declarations go before that statement. The TDZ hoisting of top-level bindings (#287) is unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
@codex review |
|
Codex Review: Didn't find any major issues. Hooray! Reviewed commit: ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
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". |
When a statement comes before the last import, the pool goes before that
statement. For `@Other() export class Plain {}` the statement's span starts
at `export`, so the pool landed between the kept decorator and its class:
`@Other() const _forTrack0 = ...`, a syntax error for esbuild and oxc.
Start at the class's first decorator when it comes first.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…path `A.C` dropped the `A` of `namespace A.B` and then looked for `C` like a bare name, which also searches nested namespaces, so it matched `A.B.C`: that class got both sets of members (duplicate identifiers) and `A.C` none. Look a dotted name up segment by segment, the last naming a class declared directly in the innermost namespace, and try each namespace of a name. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
@codex review |
|
Codex Review: Something went wrong. Try again later by commenting “@codex review”. ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
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". |
|
@codex review |
|
Codex Review: Didn't find any major issues. Nice work! Reviewed commit: ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
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". |
Summary
Stacked on #660. Fixes the older ngtsc parity bugs found while reviewing #660. Each one was reproduced against ngtsc / compiler-cli 22.2.3 (or the Angular 21.2 source for the pre-v22 cases) before it was fixed. Each fix has its own commit and a test that fails without it.
Fixes
fix(emitter)useClass: makeClass(), deps: [DEP]new makeClass()(dep)throwsnew (makeClass())(dep)fix(injectable)Maybe?.make(),Maybe?.cls.ɵfac(t)return undefined(Maybe?.make)(),(Maybe?.cls).ɵfac(t)fix(injectable)useClass: X, deps: []X.ɵfac(t)new (X)(); partial keepsdeps: []fix(transform)__decorate(...), needs JITimportscount with the file's other componentsfix(queries)@ViewChild('x, ')["x"]["x",""](JStrimrules)fix(linker)_cNconstsfix(linker)attrsattrs: _cN, as Angular 21's linkerfix(i18n)i18n_N/MSG_..._Nnamesfix(emit)_c0, A_Defer_1_DepsFn, _forTrack0A_Defer_1_DepsFn, _c0, _forTrack0(pooled order)fix(compile-template)compileTemplatewith host bindingsfix(partial)dependenciesdeferBlockDependencies+ɵɵngDeclareClassMetadataAsync, import removed; our linker links itfix(defer)imports: [ChildA, ...SHARED](or a nested array /ns.Xentry)fix(hmr)fix(debug-info)setClassDebugInfoof a class withoutexportclasskeyword's linefix(transform)namespace__decorate(...).d.tsmember underNS.Xfix(linker)useClass: Maybe?.cls/a ? B : Cin the linkerMaybe?.cls.ɵfac(t)(Maybe?.cls).ɵfac(t)fix(injectable)useFactory: f, deps: []() => f(), partial dropsdepsdeps: []fix(service)@Servicein partial modeɵɵdefineServiceɵɵngDeclareService+ɵɵngDeclareFactory; our linker links itfix(defer)const A_Defer_1_DepsFn = () => [Dep]fix(defer)imports: IMPORTS(same-file const,let, alias,as const)fix(defer)[OtherB]/ns.Xfix(partial)dependencieswith[OtherB],ns.X,...SHAREDfix(defer)fix(linker)ɵɵqueryAdvance(n)fix(linker)ɵɵpropertyɵɵdomProperty(DomOnly), as Angular 22's linkerfix(linker)MSG_APP_...fix(linker)fix(i18n)MSG_<suffix>NMSG_EXTERNAL_<digest>$$<suffix>Nfix(reify)_forTrackvsarrowFnin one template_forTrackfirstfix(transform)Not changed
_c0shadowed by an enclosingfunction make(_c0): ngtsc emits the same. The PR thread is left open for a decision.getComponentDepsFactory(T, [...])for an eagerimportslist we can't resolve without type info: by design, as in single-file builds.@deferblock uses a sibling local class: ngtsc hoists that DepsFn outside the sibling's scope, which breaks at runtime; ours keepsgetComponentDepsFactory.Checks
cargo test --all-features: 3750 passed, 0 failed (3608 without features)build-test+ vitest: 401/401🤖 Generated with Claude Code