99import assert from 'assert' ;
1010import ts from 'typescript' ;
1111
12- import { MigrationHost } from '../migration_host' ;
1312import { ConvertInputPreparation } from './prepare_and_check' ;
1413import { DecoratorInputTransform } from '../../../../../../compiler-cli/src/ngtsc/metadata' ;
1514import { ImportManager } from '../../../../../../compiler-cli/src/ngtsc/translator' ;
15+ import { removeFromUnionIfPossible } from '../utils/remove_from_union' ;
1616
1717const printer = ts . createPrinter ( { newLine : ts . NewLineKind . LineFeed } ) ;
1818
19+ // TODO: Consider initializations inside the constructor. Those are not migrated right now
20+ // though, as they are writes.
21+
1922/**
2023 *
2124 * Converts an `@Input()` property declaration to a signal input.
2225 *
2326 * @returns The transformed property declaration, printed as a string.
2427 */
2528export function convertToSignalInput (
26- host : MigrationHost ,
2729 node : ts . PropertyDeclaration ,
28- { resolvedMetadata : metadata , resolvedType, isResolvedTypeCheckable} : ConvertInputPreparation ,
30+ {
31+ resolvedMetadata : metadata ,
32+ resolvedType,
33+ preferShorthandIfPossible,
34+ isUndefinedInitialValue,
35+ } : ConvertInputPreparation ,
2936 checker : ts . TypeChecker ,
3037 importManager : ImportManager ,
3138) : string {
@@ -46,16 +53,33 @@ export function convertToSignalInput(
4653 ) ;
4754 }
4855 if ( metadata . transform !== null ) {
49- properties . push (
50- extractTransformOfInput ( metadata . transform , resolvedType , isResolvedTypeCheckable , checker ) ,
51- ) ;
56+ properties . push ( extractTransformOfInput ( metadata . transform , resolvedType , checker ) ) ;
5257 }
5358
5459 optionsLiteral = ts . factory . createObjectLiteralExpression ( properties ) ;
5560 }
5661
57- const strictPropertyInitialization =
58- ! ! host . options . strict || ! ! host . options . strictPropertyInitialization ;
62+ // The initial value is `undefined` or none is present:
63+ // - We may be able to use the `input()` shorthand
64+ // - or we use an explicit `undefined` initial value.
65+ if ( isUndefinedInitialValue ) {
66+ // Shorthand not possible, so explicitly add `undefined`.
67+ if ( preferShorthandIfPossible === null ) {
68+ initialValue = ts . factory . createIdentifier ( 'undefined' ) ;
69+ } else {
70+ resolvedType = preferShorthandIfPossible . originalType ;
71+
72+ // When using the `input()` shorthand, try cutting of `undefined` from potential
73+ // union types. `undefined` will be automatically included in the type.
74+ if ( ts . isUnionTypeNode ( resolvedType ) ) {
75+ resolvedType = removeFromUnionIfPossible (
76+ resolvedType ,
77+ ( t ) => t . kind !== ts . SyntaxKind . UndefinedKeyword ,
78+ ) ;
79+ }
80+ }
81+ }
82+
5983 const inputArgs : ts . Expression [ ] = [ ] ;
6084 const typeArguments : ts . TypeNode [ ] = [ ] ;
6185
@@ -67,19 +91,6 @@ export function convertToSignalInput(
6791 }
6892 }
6993
70- // If we have no initial value but strict property initialization is enabled, we
71- // need to add an explicit value. Alternatively, if we have an explicit type, we
72- // need to add an explicit initial value as per the API signature of `input()`.
73- if ( initialValue === undefined && ( strictPropertyInitialization || resolvedType !== undefined ) ) {
74- // TODO: Consider initializations inside the constructor. Those are not migrated right now
75- // though, as they are writes.
76-
77- // TODO: We can use the `input()` shorthand if there is a question mark?
78- // We can assume `undefined` is part of the type already, either already was included, or
79- // we added synthetically as part of the preparation.
80- initialValue = ts . factory . createIdentifier ( 'undefined' ) ;
81- }
82-
8394 // Always add an initial value when the input is optional, and we have one, or we need one
8495 // to be able to pass options as the second argument.
8596 if ( ! metadata . required && ( initialValue !== undefined || optionsLiteral !== null ) ) {
@@ -128,7 +139,6 @@ export function convertToSignalInput(
128139function extractTransformOfInput (
129140 transform : DecoratorInputTransform ,
130141 resolvedType : ts . TypeNode | undefined ,
131- isResolvedTypeCheckable : boolean ,
132142 checker : ts . TypeChecker ,
133143) : ts . PropertyAssignment {
134144 assert ( ts . isExpression ( transform . node ) , `Expected transform to be an expression.` ) ;
@@ -138,7 +148,10 @@ function extractTransformOfInput(
138148 // In some cases, the transform function is not compatible because with decorator inputs,
139149 // those were not checked. We cast the transform to `any` and add a TODO.
140150 // TODO: Insert a TODO and capture this in the design doc.
141- if ( resolvedType !== undefined && isResolvedTypeCheckable ) {
151+ if ( resolvedType !== undefined && ! ts . isSyntheticExpression ( resolvedType ) ) {
152+ // Note: If the type is synthetic, we cannot check, and we accept that in the worst case
153+ // we will create code that is not necessarily compiling. This is unlikely, but notably
154+ // the errors would be correct and valuable.
142155 const transformType = checker . getTypeAtLocation ( transform . node ) ;
143156 const transformSignature = transformType . getCallSignatures ( ) [ 0 ] ;
144157 assert ( transformSignature !== undefined , 'Expected transform to be an invoke-able.' ) ;
0 commit comments