Skip to content

fix(directive): compile inputs:/outputs: declared in decorator metadata - #493

Open
ashley-hunter wants to merge 1 commit into
mainfrom
fix/decorator-metadata-inputs-outputs
Open

ashley-hunter wants to merge 1 commit into
mainfrom
fix/decorator-metadata-inputs-outputs

Conversation

@ashley-hunter

@ashley-hunter ashley-hunter commented Sep 24, 2026 •

Copy link
Copy Markdown
Collaborator

Stack (1/4): #493 inputs/outputs → #494 transform validation → #495 queries: → #496 .d.ts transform types. This is #493.

Fixes inputs: and outputs: declared in @Directive / @Component metadata being silently dropped from the compiled definition.

The bug

@Directive({
  selector: 'a',
  inputs: ['x', { name: 'y', alias: 'why', transform: bool }],
})
class A { x?: string; y?: boolean; }

compiled to ɵɵdefineDirective({ type: A, selectors: [["a"]] }): no inputs at all. The decorator parsers never read the inputs/outputs keys, 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, for outputs:, 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? }) and outputs: ('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.

Values are read through directive/evaluator.rs, a single-file model of ngtsc's partial evaluator: literals, template strings, arrays and objects with spreads, and same-file const/let/var. So inputs: [...SHARED, 'x'] with a same-file SHARED resolves like it does in ngtsc. Each file's top-level declarations are only collected when a decorator actually needs one, and each const is evaluated once.

Forms ngtsc rejects now report its diagnostic word for word instead of being dropped: a non-array inputs:/outputs:, entries that aren't strings or object literals, objects without a string name, and a signal input()/output()/model() also listed in the metadata. Like ngtsc, only the first error per class is reported.

Values imported from another file can't be evaluated from one file, so they're reported as unresolvable, where ngtsc would follow the import. Operators and calls in metadata (inputs: cond ? a : b) are also reported rather than evaluated.

Tests

  • decorator_inputs_outputs_test.rs: the original report, which failed before the fix.
  • decorator_metadata_ngtsc_test.rs compares diagnostics, the inputs/outputs maps and the .d.ts declarations with a snapshot of @angular/compiler-cli 22.1.7's output (tests/fixtures/decorator_metadata_ngtsc.json). The cases are Angular's own specs for decorator inputs/outputs (ngtsc_spec, authoring_{inputs,outputs,models}_spec, the order_bindings compliance case, and the directive_spec acceptance cases), plus probes of the evaluator and its diagnostics. Maps are compared ignoring whitespace and parentheses, which the two emitters place differently.
  • The Angular specs are also added to the compare harness (decorator-metadata-angular-spec.fixture.ts), which diffs the full file against a live ngtsc.

…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.
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