Skip to content

feat(dts): type ngAcceptInputType_* with the transform's parameter type - #496

Open
ashley-hunter wants to merge 1 commit into
feat/decorator-metadata-queriesfrom
feat/dts-input-transform-types
Open

ashley-hunter wants to merge 1 commit into
feat/decorator-metadata-queriesfrom
feat/dts-input-transform-types

Conversation

@ashley-hunter

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

Copy link
Copy Markdown
Collaborator

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

Types static ngAcceptInputType_<input> in library .d.ts output with the transform's parameter type, as ngtsc does.

The bug

Every input with a transform was declared as

static ngAcceptInputType_count: unknown;

so a consumer's template type-checking accepted any value for it. ngtsc writes the type of the transform's first parameter, so [count]="true" against (value: string | number) => number is a type error.

The fix

The type is printed the way ngtsc prints it (directive/dts_type.rs):

  • @angular/core names become i0.Name
  • string literals are re-quoted and spacing is normalised
  • local and global names are kept
  • a transform with no parameters gets unknown

A member @Input overriding an inputs: entry decides the type, like the compiled inputs map.

Two deliberate differences from ngtsc, both falling back to the previous unknown:

  • Another module's type: ngtsc adds an import * as iN for it. These aliases are numbered per source file, which can't be merged safely into bundled declaration files, since the Vite .d.ts injector combines every module's declarations.
  • An imported transform: its signature can't be read from one file.

This PR also emits "ng-component" as the .d.ts selector of a component without one, as ngtsc does (it was never).

Tests

  • Snapshot: Angular's input transforms specs from ngtsc_spec and a probe covering most type forms are added to the ngtsc snapshot. The test maps ngtsc's other-module types to unknown (the documented difference), and the cases that need declarations from another file are skipped with that reason.
  • Checked by hand: oxc's generated library .d.ts, injected by the Vite plugin's helper, type-checks with skipLibCheck: false. ngtsc with strictTemplates accepts a consumer with valid bindings against it, and rejects wrong ones (boolean for a string | number transform, a value outside a literal union, a missing required input).

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