fix(directive): compile inputs:/outputs: declared in decorator metadata - #493
Merged
Merged
Conversation
…tadata
`@Directive({ inputs: [...] })` and `@Component({ inputs: [...], outputs: [...] })`
were ignored by the decorator parsers, so metadata-declared inputs and outputs
were silently dropped from the compiled definition and template bindings went
to the element instead of the directive.
Both parsers now read `inputs:` (`'name'`, `'name: alias'`,
`{ name, alias?, required?, transform? }`) and `outputs:` (`'name'`,
`'name: alias'`) and merge them with `@Input`/`@Output`/signal members the way
ngtsc does: keyed by class property, a member declaration replacing a metadata
entry in place, a repeated metadata entry keeping its first position.
Values are read through a single-file model of ngtsc's partial evaluator
(`directive/evaluator.rs`): literals, template strings, arrays and objects with
spreads, and same-file `const`/`let`/`var`, so `inputs: [...SHARED, 'x']`
resolves like it does in ngtsc. Forms ngtsc rejects now report its diagnostics
word for word instead of being dropped (non-array values, invalid entries,
entries without a `name`, signal members also listed in metadata). Values
imported from another file can't be evaluated from one file and are reported
as unresolvable.
`decorator_metadata_ngtsc_test.rs` compares diagnostics, inputs/outputs maps
and `.d.ts` declarations against a snapshot of `@angular/compiler-cli` 22.1.7's
output for Angular's own specs covering decorator `inputs`/`outputs`, plus
probes of the evaluator.
This was referenced Sep 24, 2026
Brooooooklyn
added this pull request to stack #503
September 30, 2026 04:46
…iagnostics
Checked against @angular/compiler-cli 22.1.7 (probe results are in the
fixture):
- Evaluate what ngtsc's partial evaluator resolves in one file: binary,
logical (`&&`, `||`), unary and conditional operators, template literals
with non-string parts, enums, `const {a} = ...` / `const [a] = ...`,
calls to same-file functions and static methods whose body is one
`return`, `array.concat/slice/length`, `string.concat`, and
`declare const X: ['a']`. Top-level consts are evaluated
dependencies-first, so long chains don't nest; depth and a work budget
bound cycles, recursion and exponential growth.
- Reject what ngtsc rejects: `satisfies`, `<T>x`, `??`, computed numeric
keys, `array['0']`, `string.length`, computed decorator keys
(`[K]: [...]`). The first `var X` declaration wins.
- Messages word for word: a bad spread marks only its element, enum
members are `of type 'E'`, `let x;` is `undefined`, unknown names are
`could not be determined statically`.
- A value imported from another module is reported as something OXC can't
read (it compiles one file), including an imported `alias`/`required`,
which was silently ignored.
- Diagnostics point where ngtsc's do: the `inputs:`/`outputs:` value, or
the colliding member.
- Maps follow JS object key rules: `__proto__` entries are dropped and
integer-like keys come first.
- The ngtsc snapshot test only ignores whitespace outside string
literals, and compares diagnostic spans.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…g Angular's signal APIs
Checked against @angular/compiler-cli 22.1.7 (probes are in the fixture):
- A transform returned by a same-file function the metadata calls was
emitted as written inside that function, so
`make('x', booleanAttribute)` with `return [{name, transform}]`
compiled to `transform`, a name that only exists there (ReferenceError
at class init). A parameter now becomes the argument it was passed
(`booleanAttribute`), through nested calls and default values, as
ngtsc emits it. A transform that uses the function's parameters, `this`
or `arguments` in any other way is reported instead: ngtsc copies it
as written, where it means something else or nothing.
- The "also declared as non-signal" / "unexpectedly declared" errors
fired for any call named `input()`, `model()`, `output()` or
`outputFromObservable()`. Like ngtsc, they now fire only for Angular's
own: imported by name from `@angular/core` (or
`@angular/core/rxjs-interop`), under any alias, or through a namespace
import. A local `output()` or `local.input()` no longer errors, and an
aliased `input as inp` now does. Signal members are still compiled by
name, as before.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…read and rest arguments
A transform returned by a helper was rejected when it merely looked like it
used the helper's parameters:
- A name the transform declares itself (its own parameter, a `const`, a
nested function or class, a loop or `catch` binding, a hoisted `var`)
hides the helper's parameter of the same name, and a nested non-arrow
function has its own `this` and `arguments`. Only a name that really
resolves to the helper's parameter (or the helper's `this` /
`arguments`) is still an error.
- Arguments passed through a spread (`make(...args)`, `make('x', ...[f])`)
and read through a rest parameter (`function make(...a)` with `a[1]`)
keep where each one was written, so the transform is emitted as that
expression, like ngtsc.
Adds 20 ngtsc 22.1.7 probes (`scope-shadowed*`, `scope-spread*`,
`scope-rest*`) to the snapshot, more cases to the scoped-transform test, and
bounds for cyclic and exponential spread arguments.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…e result `inputs: [LOCAL || NAME]` with `NAME` imported reported the "imported from another module" error, although `LOCAL` is truthy and picks the result. ngtsc emits `LOCAL`'s value there. An import now only matters as the left operand (it picks the result) or as the operand picked; a dynamic operand still makes the whole expression dynamic, as in ngtsc. `c ? a : b` already evaluated only the branch it picks. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A transform returned from a helper was rejected when it mentioned one of the helper's parameters only in a type: `(v: string): typeof name => v`, `v as typeof name`, `id<typeof name>(v)`, a parameter's or a nested declaration's type, or a local type alias or interface. Types are erased from the emitted code, so ngtsc 22.1.7 accepts these. The scope check now skips type annotations, type arguments and parameters, and type-only declarations, and still checks the runtime parts next to them (a parameter's default, the expression inside `x!`, `as`, `satisfies` and `<T>x`). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…ike ngtsc ngtsc resolves the last part of `typeof NS.X` (in a `declare const`) to its declaration and evaluates it, so a value exported from a namespace in the same file compiles. oxc reported it as a reference to `X`. The evaluator now collects namespace bodies (merged like TypeScript: by name at the top level, by parent for exported nested ones, `namespace A.B`, and every member exported in `declare namespace`) and resolves `typeof A.B.X` through them. Code written in a namespace sees its declarations first, then its merged bodies' exports, then the enclosing ones; functions and static members declared there are evaluated in that scope. Import-equals aliases (`import I = NS.X`, `import A = NS`, also in namespaces) resolve to what they name, as TypeScript resolves them. A namespace that declares a value is a reference to it, like ngtsc (`inputs: NS` is "a reference to 'NS'"); one with only types has no value. `NS.X` in a value position stays dynamic, as in ngtsc. A transform written in a namespace that uses the namespace's declarations is reported: ngtsc emits it as written, where it isn't in scope. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Brooooooklyn
approved these changes
Oct 1, 2026
Brooooooklyn
removed this pull request from stack #503
October 1, 2026 08:45
Brooooooklyn
added a commit
that referenced
this pull request
Oct 1, 2026
…type names
- An enum member reached through a namespace (`NS.E.A`, `C.E.A` for a
class merged with a namespace, `NS.M.E.A`, `namespace A.B`) is kept as
written, like `E.A`. It was shortened to `A`, which doesn't resolve;
ngtsc 22.1.7 throws on these. `NS.E` and `A.B.T` are still shortened
to `E` / `T`, as ngtsc writes them.
- A computed property name in a transform type (`{ [token]: string }`,
`{ [ns.token]: string }`) follows the type-name rules: an
`@angular/core` value becomes `i0.token`, another module's makes the
type `unknown`. ngtsc copies the expression, which names an import the
`.d.ts` doesn't have. Local and global names are unchanged.
Also compares the `ngAcceptInputType_*` types of the `scope-*` probes added
under #493, and adds a `dts-qualifiedNamespaceEnum` probe.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Brooooooklyn
added a commit
that referenced
this pull request
Oct 1, 2026
…type names
- An enum member reached through a namespace (`NS.E.A`, `C.E.A` for a
class merged with a namespace, `NS.M.E.A`, `namespace A.B`) is kept as
written, like `E.A`. It was shortened to `A`, which doesn't resolve;
ngtsc 22.1.7 throws on these. `NS.E` and `A.B.T` are still shortened
to `E` / `T`, as ngtsc writes them.
- A computed property name in a transform type (`{ [token]: string }`,
`{ [ns.token]: string }`) follows the type-name rules: an
`@angular/core` value becomes `i0.token`, another module's makes the
type `unknown`. ngtsc copies the expression, which names an import the
`.d.ts` doesn't have. Local and global names are unchanged.
Also compares the `ngAcceptInputType_*` types of the `scope-*` probes added
under #493, and adds a `dts-qualifiedNamespaceEnum` probe.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Brooooooklyn
added a commit
that referenced
this pull request
Oct 1, 2026
…type names
- An enum member reached through a namespace (`NS.E.A`, `C.E.A` for a
class merged with a namespace, `NS.M.E.A`, `namespace A.B`) is kept as
written, like `E.A`. It was shortened to `A`, which doesn't resolve;
ngtsc 22.1.7 throws on these. `NS.E` and `A.B.T` are still shortened
to `E` / `T`, as ngtsc writes them.
- A computed property name in a transform type (`{ [token]: string }`,
`{ [ns.token]: string }`) follows the type-name rules: an
`@angular/core` value becomes `i0.token`, another module's makes the
type `unknown`. ngtsc copies the expression, which names an import the
`.d.ts` doesn't have. Local and global names are unchanged.
Also compares the `ngAcceptInputType_*` types of the `scope-*` probes added
under #493, and adds a `dts-qualifiedNamespaceEnum` probe.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Brooooooklyn
added a commit
that referenced
this pull request
Oct 1, 2026
…type (#496) * feat(dts): type `ngAcceptInputType_*` with the transform's parameter type Library `.d.ts` declarations wrote `static ngAcceptInputType_<input>: unknown` for every input with a transform, so consumers' template type-checking accepted any value. ngtsc writes the type of the transform's first parameter, so e.g. `[count]="true"` against a `(value: string | number) => number` transform is a type error. That type is now printed the way ngtsc prints it: `@angular/core` names become `i0.Name`, string literals are re-quoted, spacing is normalised, local and global names are kept, `unknown` for a transform with no parameters. Two deliberate differences from ngtsc, both falling back to the previous `unknown`: - a type referencing another module: ngtsc adds `import * as iN` for it, but aliases numbered per source file can't be merged safely into bundled declaration files - an imported transform, whose signature can't be read from one file A member `@Input` overriding an `inputs:` entry decides the type, like the compiled inputs map. Also emits `"ng-component"` as the `.d.ts` selector of a component without one, as ngtsc does (it was `never`). * fix(dts): rewrite every type position of transform types like ngtsc The `ngAcceptInputType_*` printer copied type parameters, mapped types, predicates, `this` parameters and non-property members verbatim, so `@angular/core` names there missed the `i0.` rewrite and other-module names escaped the `unknown` fallback. Print every node from the AST instead, and fall back to `unknown` for forms that can't be printed. Also match TypeScript's printer: property keys and destructuring keys double-quoted, strings escaped to ASCII (`\0`, `😀`), `{ w }` patterns, bigint/number normalisation, and the same-line comments it keeps in front of list elements. `/** */` comments after a type or on their own line are still dropped. The new test compares 239 forms against ngtsc 22.1.7's `.d.ts` output. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * fix(dts): type overloads from the first declaration and print qualified local names like ngtsc Checked against @angular/compiler-cli 22.1.7: - An overloaded function or static method transform is typed from its first declaration (the one ngtsc reads), not its implementation. - A qualified type name whose head the file declares (`NS.T`, `C.T` for a class merged with a namespace, `A.B.T`, `declare namespace`) is written as its last part, `T`, as ngtsc writes it. An enum member (`E.A`), which ngtsc can't emit, stays as written; `ng.Signal` through an `@angular/core` namespace is still `i0.Signal`, and names from other modules are still `unknown`. - A TypeScript library function used as a transform (`parseInt`, `isNaN`, ...) is typed with its first parameter's type from `lib.es5.d.ts`. - The ngtsc snapshot gets back the typed `ngAcceptInputType_*` of the 26 cases added while every one was `unknown`, regenerated from ngtsc, plus probes of qualified names and library functions. A static `ngAcceptInputType_*` the class declares itself is TypeScript's own declaration output, so the test doesn't expect oxc to write it. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * fix(dts): quote and escape `ngAcceptInputType_*` names like ngtsc An input whose class property is a string key was written quoted but not escaped whenever it wasn't an identifier, so `'a-"b'` gave `static "ngAcceptInputType_a-"b": ...`, which doesn't parse, and `'c.\\d'` named a different property. ngtsc (22.1.7) quotes the name only when Angular's `isUnsafeObjectKey` (`/[-.]/`) matches, and prints it as a TypeScript string literal (`"ngAcceptInputType_a-\"b"`, `"ngAcceptInputType_k-é"`). Every other name is written as is, like `ngAcceptInputType_a"b`. oxc now does the same, reusing the string printer of the transform types. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * test(dts): compare the `.d.ts` types of transforms passed through helper functions The snapshot's `scope-*` probes (added earlier in the stack, before `.d.ts` transform types) left out ngtsc's `ngAcceptInputType_*` types. With them back, a function expression passed to a helper, as a default value, through `@Input(opts(...))` or written in a helper is typed from its own parameter, like ngtsc 22.1.7 (`string`). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * fix(dts): keep nested enum member types and type computed names like type names - An enum member reached through a namespace (`NS.E.A`, `C.E.A` for a class merged with a namespace, `NS.M.E.A`, `namespace A.B`) is kept as written, like `E.A`. It was shortened to `A`, which doesn't resolve; ngtsc 22.1.7 throws on these. `NS.E` and `A.B.T` are still shortened to `E` / `T`, as ngtsc writes them. - A computed property name in a transform type (`{ [token]: string }`, `{ [ns.token]: string }`) follows the type-name rules: an `@angular/core` value becomes `i0.token`, another module's makes the type `unknown`. ngtsc copies the expression, which names an import the `.d.ts` doesn't have. Local and global names are unchanged. Also compares the `ngAcceptInputType_*` types of the `scope-*` probes added under #493, and adds a `dts-qualifiedNamespaceEnum` probe. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * fix(dts): type import-equals aliases, `typeof this`, enum-merged namespaces and lone surrogates like ngtsc - `import A = NS` / `import C = NS.T` (and `export import`) stand for their target: `A.T` and `C` are written `T`, as ngtsc writes them. An alias of another module (`import R = require('./other')`, or of an import) makes the type `unknown`, like any other module's type (ngtsc writes the bare name, which doesn't resolve). An alias of `@angular/core` becomes `i0.X`, and one of a global is written as its target (`Intl.NumberFormat`). - `typeof this` is kept, like ngtsc (it was `unknown`). - A qualified name through an enum merged with a namespace (`E.T`) is shortened to `T` like ngtsc; only an actual enum member (`E.A`, from any declaration of the enum) is kept as written. - Lone surrogates in string literal types and quoted keys (`'\uD800'`) are printed as `"\uD800"`, like ngtsc, instead of OXC's internal encoding. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * fix(dts): type import-equals aliases declared in namespaces like their target `namespace NS { export import Alias = Local; }` with a parameter typed `NS.Alias` printed `Alias`, where ngtsc resolves the alias and writes `Local`. The same went for nested namespaces (`NS.Inner.Alias2`), aliases of aliases, aliases reached through a top-level alias (`import X = NS.Alias`), and targets named from the namespace's own scope (`export import Rel = In2`). Namespace member aliases now resolve to their target, whose head is looked up in the alias's namespace first, then the enclosing ones, then the file. The result follows the existing rules: local names are shortened, `@angular/core` names become `i0.X`, other modules make the type `unknown`, and globals keep their target (`Intl.NumberFormat`). Also tests that a transform that's a global declared outside the file (`atob`, now assumed to be a function) is typed `unknown`, like an imported one. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * fix(dts): type `import Core = require('@angular/core')` members as `i0.X` Every `require` alias was treated as another module's, so `Core.Signal<string>` (also through `import S = Core.Signal`, `import C = Core` or a namespace's alias of it) typed the input as `unknown`. An alias of `@angular/core` now gives `i0.Signal<string>`, like a namespace import of it. ngtsc 22.1.7 writes the bare `Signal<string>`, which doesn't resolve in its `.d.ts`; other modules stay `unknown`. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * fix(dts): resolve import-equals aliases in computed property names A computed name in a transform's parameter type (`{ [Core.ɵSIGNAL]: string }`) skipped the alias resolution type names get, so an alias of `@angular/core` (`import Core = require('@angular/core')`, `import C = ng`, `import S = Core.ɵSIGNAL`, or one declared in a namespace) was written as is, another module's alias didn't make the type `unknown`, and a global's alias wasn't written as its target. Computed names now resolve aliases the way type names do: `i0.ɵSIGNAL`, `unknown`, and `G.k`. ngtsc 22.1.7 copies all of these as written, which doesn't resolve in its `.d.ts`. An alias of a name the file declares stays as written, like ngtsc. The snapshot's `scope-typeOnly-*` probes (added earlier in the stack, before `.d.ts` transform types) now also compare ngtsc's `ngAcceptInputType_*` types, including `typeof name` for a helper's parameter used as a type. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * fix(dts): keep enum members named by a string or template literal as written `enum E { [`A`] = 'a' }` names its member with a template literal, which the enum member check ignored: `E.A` was taken for a qualified local name and shortened to `A`, which doesn't resolve (`static ngAcceptInputType_x: A;`), also for an enum in a namespace. Members named `'A'`, `['A']` or `` [`A`] `` (escapes included) are now recognised like `A`, so the type is kept as written. ngtsc 22.1.7 throws on all of these. Transforms declared in a namespace (through `typeof NS.fn`, a static method of a class there, or `import H = NS.fn`) are typed from their declaration, like ngtsc; add them to the tests and snapshot. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * fix(dts): follow alias chains of any length and keep U+2028 / U+2029 line breaks whole An import-equals alias chain is followed until it ends, stopping only on a cycle, instead of giving up after 16 aliases (`A17.T` through 18 aliases is `T`, like ngtsc). Looking back for a `//` before a union now steps over a U+2028 / U+2029 line break by its full UTF-8 length, where it used to slice inside the character and panic. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: LongYinan <lynweklm@gmail.com> Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Stack (1/5): #493 inputs/outputs → #494 transform validation → #495
queries:→ #496.d.tstransform types → #504 signal API import identity. This is #493.Fixes
inputs:andoutputs:declared in@Directive/@Componentmetadata being silently dropped from the compiled definition.The bug
compiled to
ɵɵdefineDirective({ type: A, selectors: [["a"]] }): noinputsat all. The decorator parsers never read theinputs/outputskeys, so there was no error either, and a binding like[x]="..."went to the element as a property instead of reaching the directive. The same happened for@Component, foroutputs:, and on abstract@Directive()bases (whose subclasses then inherited nothing).The fix
Both decorator parsers now read
inputs:('name','name: alias',{ name, alias?, required?, transform? }) andoutputs:('name','name: alias') and merge them with@Input/@Output/signal members the way ngtsc does ({...inputsFromMeta, ...inputsFromFields}): keyed by class property, a member replacing a metadata entry in place, a repeated metadata entry keeping its first position, and anything after a second:ignored. Keys follow JS object rules, as in ngtsc's object: a__proto__entry is dropped and integer-like keys come first.Values are read through
directive/evaluator.rs, a single-file model of ngtsc's partial evaluator. It resolves what ngtsc resolves within one file:const/let/var(the firstvarwins), destructuring (const {a} = ...,const [a] = ...), enums, anddeclare const X: ['a']&&/||operatorsreturn. A transform returned from such a helper (inputs: make('x', booleanAttribute)) is emitted as the expression passed at the call site, so it stays in scopearray.concat/slice/lengthandstring.concatSyntax ngtsc doesn't evaluate is rejected too:
satisfies,<T>x,??, numeric computed keys,array['0'],string.length, computed decorator keys ([K]: [...]). Each file's top-level declarations are only collected when a decorator needs one, eachconstis evaluated once and dependencies-first (so long chains don't nest), and a depth and work limit stop cycles, recursion and exponential growth.Forms ngtsc rejects now report its diagnostic word for word, at the node ngtsc points to (the
inputs:/outputs:value, or the colliding member), instead of being dropped: a non-arrayinputs:/outputs:, entries that aren't strings or object literals, objects without a stringname, and a signalinput()/output()/model()also listed in the metadata. That check, like ngtsc's, only counts Angular's own functions: a named import (aliases too) or a namespace import from@angular/core, andoutputFromObservablefrom@angular/core/rxjs-interop. A same-named local or foreign function doesn't collide. The value descriptions match too (a bad spread marks only its element, enum members areof type 'E',let x;isundefined, unknown namescould not be determined statically). Like ngtsc, only the first error per class is reported.One deliberate difference: ngtsc follows imports, but OXC compiles one file at a time. A value imported from another module (including an imported
aliasorrequired, which would otherwise change the binding) reports:Two more differences, both where ngtsc emits code that doesn't work:
(v) => v + name, withnamea helper parameter) is an error. ngtsc copies it, andnamethen refers to a global.make({t: booleanAttribute}), returningo.t) is an error. ngtsc emitsbooleanAttribute.(A small output-only difference is recorded in the snapshot: oxc quotes integer-like keys in the JS inputs map, where ngtsc leaves them bare. The order is the same.)
A transform returned by a helper is checked with JavaScript scoping: names the transform declares itself (parameters,
const, functions, classes, loop andcatchbindings, hoistedvars) hide the helper's parameters, and a nested normal function has its ownthis/arguments. Arguments passed through spreads (make(...args)) or read through rest parameters (a[1]) keep where they were written, so they emit like ngtsc.&&/||need an imported operand only when it is the left one or the one picked (LOCAL || NAMEisLOCAL), andc ? a : bevaluates only the branch it picks, like ngtsc. When the import decides the result, the "imported from another module" error is kept.The scope check for a helper's transform ignores types (annotations, return types, type arguments and parameters, the type part of
as/satisfies/<T>, local type aliases and interfaces), since they're erased; ngtsc accepts these. A helper parameter used at runtime next to a type (name!,name as T, a default value) is still reported.Values exported from a same-file namespace are evaluated through
typeof NS.Xand aliases likeimport I = NS.X, scoped and merged like TypeScript. As in ngtsc,NS.Xin a value position stays dynamic, and a transform written in a namespace that uses the namespace's own declarations is reported.Tests
decorator_inputs_outputs_test.rs: the original report, which failed before the fix.decorator_metadata_ngtsc_test.rscompares diagnostics, diagnostic spans, the inputs/outputs maps and the.d.tsdeclarations with a snapshot of@angular/compiler-cli22.1.7's output (tests/fixtures/decorator_metadata_ngtsc.json, 229 cases). 14 are Angular's own specs for decoratorinputs/outputs(ngtsc_spec,authoring_{inputs,outputs,models}_spec, theorder_bindingscompliance case), the rest probe the evaluator and its diagnostics. Maps are compared ignoring whitespace and parentheses (which the two emitters place differently) outside string literals, so a stray space inside an emitted name still fails.directive_spec/inherit_definition_feature_specacceptance cases, are also added to the compare harness (decorator-metadata-angular-spec.fixture.ts), which diffs the full file against a live ngtsc.