Skip to content

feat(directive): validate input transforms like ngtsc - #494

Open
ashley-hunter wants to merge 1 commit into
fix/decorator-metadata-inputs-outputsfrom
feat/validate-input-transforms
Open

ashley-hunter wants to merge 1 commit into
fix/decorator-metadata-inputs-outputsfrom
feat/validate-input-transforms

Conversation

@ashley-hunter

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

Copy link
Copy Markdown
Collaborator

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

Checks input transforms the way ngtsc does, for both @Input({ transform }) and inputs: [{ transform }].

The bug

ngtsc rejects a transform it can't prove is a usable function (parseDecoratorInputTransformFunction). oxc accepted anything, so code that fails to build with ngc compiled here, e.g.

const toNumber = (v: string) => Number(v);

@Directive({ selector: '[d]' })
export class D {
  @Input({ transform: toNumber }) value!: number;   // ngtsc: Input transform must be a function
}

The fix

Each rule matches ngtsc, with its diagnostic word for word and in its order (inputs: entries, then members):

  • not a function: literals, classes, calls, and function expressions reached through a variable, property access or parentheses (only one written directly as the property value is analyzable)
  • generic, or overloaded: more than one call signature
  • first parameter: untyped, a spread, or typed with a same-file type that isn't exported
  • name clash: a static ngAcceptInputType_<name> member already on the class

The evaluator learns functions to do this: same-file function declarations and static methods resolve to their definitions. A transform reached through such a reference (T.f where const T = { f }) is emitted as the function's own name, as ngtsc does.

One deliberate difference: for a static method (transform: Utils.coerce) ngtsc emits the bare method name. That name isn't in scope, so it either throws or binds an unrelated same-named function, and oxc keeps the written expression instead.

@Input members are also recognised through a namespace import (@core.Input), like @core.Component already was. Imported transforms can't be inspected from one file and are assumed to be functions.

Tests

Added to the ngtsc snapshot: Angular's input transform specs from ngtsc_spec (non-function values, generic, overloaded, spread, untyped, non-exported type, ngAcceptInputType clash), plus probes of each rule and of how ngtsc describes values in these errors. The three ngtsc_spec cases that need types from another file are listed as skipped with that reason, as is an aliased @Input import (member decorators are matched by name throughout oxc). A separate test covers the static-method case.

ngtsc rejects an input `transform` it can't prove is a usable function
(`parseDecoratorInputTransformFunction`); oxc accepted anything, so code that
fails to build with ngc compiled here. Both `@Input({ transform })` and
`inputs: [{ transform }]` are now checked, with ngtsc's diagnostics word for
word and in its order (`inputs:` entries, then members):

- not a function: literals, classes, calls, and function expressions reached
  through a variable, property access or parentheses (only one written directly
  as the property value is analyzable)
- generic, or overloaded (more than one call signature)
- first parameter untyped or a spread
- first parameter typed with a same-file type that isn't exported
- a `static ngAcceptInputType_<name>` member on the class

The evaluator learns functions to do this: same-file function declarations and
static methods resolve to their definitions. A transform reached through such a
reference (`T.f` where `const T = { f }`) is emitted as the function's own name,
as ngtsc does; a static method keeps the written expression, since ngtsc's bare
method name isn't in scope and would bind an unrelated same-named function.

`@Input` members are also found through a namespace import (`@core.Input`),
like `@core.Component` already was. Imported transforms can't be inspected from
one file and are assumed to be functions.
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