refactor(migrations): always add readonly to migrated signal inputs (#57368)

Signal inputs are no longer updated by assignment, unlike `@Input()`, so
a good practice is adding `readonly` for the `InputSignal`— which should
never be swapped out.

This is a safe operation because the migration skips all inputs that are
being written anyway.

PR Close #57368
This commit is contained in:
Paul Gschwendtner 2024-08-13 11:26:00 +00:00 committed by Andrew Kushnir
parent 87d00d26ff
commit bbc970bb0b
6 changed files with 180 additions and 111 deletions

View file

@ -32,6 +32,7 @@ export function convertToSignalInput(
resolvedType,
preferShorthandIfPossible,
isUndefinedInitialValue,
originalInputDecorator,
}: ConvertInputPreparation,
checker: ts.TypeChecker,
importManager: ImportManager,
@ -117,12 +118,16 @@ export function convertToSignalInput(
inputArgs,
);
// TODO:
// - modifiers (but private does not work)
// - preserve custom decorators etc.
let modifiersWithoutInputDecorator =
node.modifiers?.filter((m) => m !== originalInputDecorator.node) ?? [];
// Add `readonly` to all new signal input declarations.
if (!modifiersWithoutInputDecorator?.some((s) => s.kind === ts.SyntaxKind.ReadonlyKeyword)) {
modifiersWithoutInputDecorator.push(ts.factory.createModifier(ts.SyntaxKind.ReadonlyKeyword));
}
const result = ts.factory.createPropertyDeclaration(
undefined,
modifiersWithoutInputDecorator,
node.name,
undefined,
undefined,

View file

@ -13,6 +13,8 @@ import {
InputMemberIncompatibility,
} from '../input_detection/incompatibility';
import {InputNode} from '../input_detection/input_node';
import {Decorator} from '../../../../../../compiler-cli/src/ngtsc/reflection';
import assert from 'assert';
/**
* Interface describing analysis performed when the input
@ -23,6 +25,7 @@ export interface ConvertInputPreparation {
preferShorthandIfPossible: {originalType: ts.TypeNode} | null;
isUndefinedInitialValue: boolean;
resolvedMetadata: ExtractedInput;
originalInputDecorator: Decorator;
}
/**
@ -46,6 +49,12 @@ export function prepareAndCheckForConversion(
reason: InputIncompatibilityReason.Accessor,
};
}
assert(
metadata.inputDecorator !== null,
'Expected an input decorator for inputs that are being migrated.',
);
const initialValue = node.initializer;
// If an input can be required, due to the non-null assertion on the property,
@ -118,5 +127,6 @@ export function prepareAndCheckForConversion(
resolvedType: typeToAdd,
preferShorthandIfPossible,
isUndefinedInitialValue,
originalInputDecorator: metadata.inputDecorator,
};
}

View file

@ -24,7 +24,7 @@ import {
} from '../../../../../../compiler-cli/src/ngtsc/partial_evaluator';
import {
ClassDeclaration,
DecoratorIdentifier,
Decorator,
ReflectionHost,
} from '../../../../../../compiler-cli/src/ngtsc/reflection';
import {CompilationMode} from '../../../../../../compiler-cli/src/ngtsc/transform';
@ -34,6 +34,7 @@ import {InputNode, isInputContainerNode} from '../input_detection/input_node';
/** Metadata extracted of an input declaration (in `.ts` or `.d.ts` files). */
export interface ExtractedInput extends InputMapping {
inSourceFile: boolean;
inputDecorator: Decorator | null;
}
/** Attempts to extract metadata of a potential TypeScript `@Input()` declaration. */
@ -86,6 +87,7 @@ function extractDtsInput(node: ts.Node, metadataReader: DtsMetadataReader): Extr
? null
: {
...inputMapping,
inputDecorator: null,
inSourceFile: false,
};
}
@ -150,6 +152,7 @@ function extractSourceCodeInput(
isSignal: false,
inSourceFile: true,
transform: transformResult,
inputDecorator,
};
}

View file

@ -0,0 +1,15 @@
// tslint:disable
import {Component, Input} from '@angular/core';
function CustomDecorator() {
return (a: any, b: any) => {};
}
@Component({template: ''})
class ModifierScenarios {
@Input() readonly alreadyReadonly = true;
@Input() protected ImProtected = true;
@Input() protected readonly ImProtectedAndReadonly = true;
@Input() @CustomDecorator() protected readonly usingCustomDecorator = true;
}

View file

@ -9,11 +9,11 @@ import {By} from '@angular/platform-browser';
function it(msg: string, fn: () => void) {}
class SubDir {
name = input('John');
readonly name = input('John');
}
class MyComp {
hello = input('');
readonly hello = input('');
}
it('should work', () => {
@ -40,12 +40,12 @@ class Sub implements BaseNonAngular {
}
class BaseWithAngular {
disabled = input<string>('');
readonly disabled = input<string>('');
}
@Directive()
class Sub2 extends BaseWithAngular {
disabled = input('');
readonly disabled = input('');
}
interface BaseNonAngularInterface {
@ -67,7 +67,7 @@ class BothInputImported {
@Input() decoratorInput = true;
signalInput = input<boolean>();
thisCanBeMigrated = input(true);
readonly thisCanBeMigrated = input(true);
__makeDecoratorInputNonMigratable() {
this.decoratorInput = false;
@ -87,7 +87,7 @@ function it(msg: string, fn: () => void) {}
function bootstrapTemplate(tmpl: string, inputs: unknown) {}
class MyComp {
hello = input('');
readonly hello = input('');
}
it('should work', () => {
@ -109,7 +109,7 @@ import { Component, input } from '@angular/core';
`,
})
class Group {
label = input.required<string>();
readonly label = input.required<string>();
}
@Component({
@ -182,7 +182,7 @@ let nextUniqueId = 0;
@Directive()
export class MatHint {
align: string = '';
id = input(`mat-hint-${nextUniqueId++}`);
readonly id = input(`mat-hint-${nextUniqueId++}`);
}
@Component({
@ -226,7 +226,7 @@ import { input } from '@angular/core';
class ExistingSignalImport {
signalInput = input<boolean>();
thisCanBeMigrated = input(true);
readonly thisCanBeMigrated = input(true);
}
@@@@@@ getters.ts @@@@@@
@ -266,7 +266,7 @@ import { Component, input } from '@angular/core';
},
})
class HostBindingTestCmp {
id = input('works');
readonly id = input('works');
// for testing nested expressions.
nested = this;
@ -284,7 +284,7 @@ const SHARED = {
host: SHARED,
})
class HostBindingsShared {
id = input(false);
readonly id = input(false);
}
@Component({
@ -292,8 +292,8 @@ class HostBindingsShared {
host: SHARED,
})
class HostBindingsShared2 {
id = input(false);
id2 = input(false);
readonly id = input(false);
readonly id2 = input(false);
}
@@@@@@ identifier_collisions.ts @@@@@@
@ -313,8 +313,8 @@ const complex = 'some global variable';
@Component({template: ''})
class MyComp {
name = input<string | null>(null);
complex = input<string | null>(null);
readonly name = input<string | null>(null);
readonly complex = input<string | null>(null);
valid() {
const name = this.name();
@ -371,10 +371,10 @@ interface Audi extends Car {
templateUrl: './template.html',
})
export class AppComponent {
input = input<string | null>(null);
bla = input.required<boolean, string | boolean>({ transform: disabledTransform });
narrowableMultipleTimes = input<Vehicle | null>(null);
withUndefinedInput = input<string>();
readonly input = input<string | null>(null);
readonly bla = input.required<boolean, string | boolean>({ transform: disabledTransform });
readonly narrowableMultipleTimes = input<Vehicle | null>(null);
readonly withUndefinedInput = input<string>();
@Input() incompatible: string | null = null;
private _bla: any;
@ -496,7 +496,7 @@ import { Component, input } from '@angular/core';
@Component({template: ''})
class IndexAccessInput {
items = input<string[]>([]);
readonly items = input<string[]>([]);
bla() {
const {items} = this;
@ -558,7 +558,7 @@ import { Component, input } from '@angular/core';
`,
})
export class InlineTmpl {
justify = input<'start' | 'end'>('end');
readonly justify = input<'start' | 'end'>('end');
}
@@@@@@ jit_true_component_external_tmpl.html @@@@@@
@ -582,7 +582,7 @@ import { Component, input } from '@angular/core';
template: '{{test()}}',
})
class JitTrueComponent {
test = input(true);
readonly test = input(true);
}
@Component({
@ -590,7 +590,7 @@ class JitTrueComponent {
templateUrl: './jit_true_component_external_tmpl.html',
})
class JitTrueComponentExternalTmpl {
test = input(true);
readonly test = input(true);
}
@@@@@@ loop_labels.ts @@@@@@
@ -599,7 +599,7 @@ class JitTrueComponentExternalTmpl {
import { input } from '@angular/core';
class MyTestCmp {
someInput = input.required<boolean | string>();
readonly someInput = input.required<boolean | string>();
tmpValue = false;
@ -638,6 +638,24 @@ import {Component, Input} from '@angular/core';
export class ManualInstantiation {
@Input() bla: string = '';
}
@@@@@@ modifier_tests.ts @@@@@@
// tslint:disable
import { Component, input } from '@angular/core';
function CustomDecorator() {
return (a: any, b: any) => {};
}
@Component({template: ''})
class ModifierScenarios {
readonly alreadyReadonly = input(true);
protected readonly ImProtected = input(true);
protected readonly ImProtectedAndReadonly = input(true);
@CustomDecorator()
protected readonly usingCustomDecorator = input(true);
}
@@@@@@ mutate.ts @@@@@@
// tslint:disable
@ -645,7 +663,7 @@ export class ManualInstantiation {
import { input } from '@angular/core';
export class TestCmp {
shared = input<{
readonly shared = input<{
x: string;
}>({ x: '' });
@ -678,7 +696,7 @@ interface Config {
`,
})
export class NestedTemplatePropAccess {
config = input<Config>({});
readonly config = input<Config>({});
}
@@@@@@ object_expansion.ts @@@@@@
@ -688,7 +706,7 @@ import { Component, input } from '@angular/core';
@Component({})
export class ObjectExpansion {
bla = input<string>('');
readonly bla = input<string>('');
expansion() {
const {bla} = this;
@ -720,7 +738,7 @@ import { Directive, input } from '@angular/core';
@Directive()
class OptionalInput {
bla = input<string>();
readonly bla = input<string>();
}
@@@@@@ problematic_type_reference.ts @@@@@@
@ -767,12 +785,12 @@ import {COMPLEX_VAR} from './required-no-explicit-type-extra';
export const CONST = {field: true};
export class RequiredNoExplicitType {
someInputNumber = input.required<number>();
someInput = input.required<boolean>();
withConstInitialVal = input.required<typeof CONST>();
readonly someInputNumber = input.required<number>();
readonly someInput = input.required<boolean>();
readonly withConstInitialVal = input.required<typeof CONST>();
// typing this explicitly now would require same imports as from the `-extra` file.
complexVal = input.required<typeof COMPLEX_VAR>();
readonly complexVal = input.required<typeof COMPLEX_VAR>();
}
@@@@@@ required.ts @@@@@@
@ -781,7 +799,7 @@ export class RequiredNoExplicitType {
import { input } from '@angular/core';
class Required {
simpleInput = input.required<string>();
readonly simpleInput = input.required<string>();
}
@@@@@@ safe_property_reads.ts @@@@@@
@ -803,7 +821,7 @@ import { Component, input } from '@angular/core';
`,
})
class WithSafePropertyReads {
myInput = input(0);
readonly myInput = input(0);
bla: this | undefined = this;
}
@ -814,7 +832,7 @@ class WithSafePropertyReads {
import { input } from '@angular/core';
export class TestCmp {
shared = input(false);
readonly shared = input(false);
bla() {
const shared = this.shared();
@ -837,7 +855,7 @@ import { Directive, Component, input } from '@angular/core';
@Directive()
class SomeDir {
bla = input.required<RegExp>();
readonly bla = input.required<RegExp>();
}
@Component({
@ -931,8 +949,8 @@ export class MyComp {
@Input() first = true;
@Input() second = false;
@Input() third = true;
fourth = input(true);
fifth = input(true);
readonly fourth = input(true);
readonly fifth = input(true);
}
@@@@@@ template_object_shorthand.ts @@@@@@
@ -950,7 +968,7 @@ import { Component, input } from '@angular/core';
},
})
export class TemplateObjectShorthand {
myInput = input(true);
readonly myInput = input(true);
}
@@@@@@ template_writes.ts @@@@@@
@ -972,7 +990,7 @@ class TwoWayBinding {
@Input() inputA = '';
@Input() inputB = true;
@Input() inputC = false;
inputD = input(false);
readonly inputD = input(false);
}
@@@@@@ transform_functions.ts @@@@@@
@ -987,12 +1005,12 @@ function x(v: string | undefined): string | undefined {
export class TransformFunctions {
// We can check this, and expect `as any` due to transform incompatibility.
withExplicitTypeWorks = input.required<{
readonly withExplicitTypeWorks = input.required<{
ok: true;
}, string | undefined>({ transform: ((v: string | undefined) => '') as any });
// This will be a synthetic type because we add `undefined` to `boolean`.
synthetic1 = input.required<boolean | undefined, string | undefined>({ transform: x });
readonly synthetic1 = input.required<boolean | undefined, string | undefined>({ transform: x });
// Synthetic as we infer a full type from the initial value. Cannot be checked.
@Input({required: true, transform: (v: string | undefined) => ''}) synthetic2 = {
infer: COMPLEX_VAR,
@ -1009,7 +1027,7 @@ import { Directive, input } from '@angular/core';
@Directive()
class TransformIncompatibleTypes {
// @ts-ignore Simulate `--strictPropertyInitialization=false`.
disabled = input<boolean, unknown>(undefined, { transform: ((v: unknown) => (v === null ? undefined : !!v)) as any });
readonly disabled = input<boolean, unknown>(undefined, { transform: ((v: unknown) => (v === null ? undefined : !!v)) as any });
}
@@@@@@ with_getters.ts @@@@@@
@ -1036,7 +1054,7 @@ export class WithSettersAndGetters {
}
private _accessor: string = '';
simpleInput = input.required<string>();
readonly simpleInput = input.required<string>();
}
@@@@@@ with_getters_reference.ts @@@@@@
@ -1063,7 +1081,7 @@ class WithJsdoc {
/**
* Works
*/
simpleInput = input.required<string>();
readonly simpleInput = input.required<string>();
withCommentInside = input</* intended */ boolean>();
readonly withCommentInside = input</* intended */ boolean>();
}

View file

@ -9,11 +9,11 @@ import {By} from '@angular/platform-browser';
function it(msg: string, fn: () => void) {}
class SubDir {
name = input('John');
readonly name = input('John');
}
class MyComp {
hello = input('');
readonly hello = input('');
}
it('should work', () => {
@ -36,16 +36,16 @@ class BaseNonAngular {
@Directive()
class Sub implements BaseNonAngular {
// should not be migrated because of the interface.
disabled = input('');
readonly disabled = input('');
}
class BaseWithAngular {
disabled = input<string>('');
readonly disabled = input<string>('');
}
@Directive()
class Sub2 extends BaseWithAngular {
disabled = input('');
readonly disabled = input('');
}
interface BaseNonAngularInterface {
@ -55,7 +55,7 @@ interface BaseNonAngularInterface {
@Directive()
class Sub3 implements BaseNonAngularInterface {
// should not be migrated because of the interface.
disabled = input('');
readonly disabled = input('');
}
@@@@@@ both_input_imports.ts @@@@@@
@ -64,10 +64,10 @@ class Sub3 implements BaseNonAngularInterface {
import { input } from '@angular/core';
class BothInputImported {
decoratorInput = input(true);
readonly decoratorInput = input(true);
signalInput = input<boolean>();
thisCanBeMigrated = input(true);
readonly thisCanBeMigrated = input(true);
__makeDecoratorInputNonMigratable() {
this.decoratorInput = false;
@ -87,7 +87,7 @@ function it(msg: string, fn: () => void) {}
function bootstrapTemplate(tmpl: string, inputs: unknown) {}
class MyComp {
hello = input('');
readonly hello = input('');
}
it('should work', () => {
@ -109,7 +109,7 @@ import { Component, input } from '@angular/core';
`,
})
class Group {
label = input.required<string>();
readonly label = input.required<string>();
}
@Component({
@ -132,7 +132,7 @@ import { Directive, input } from '@angular/core';
@Directive()
class Base {
bla = input(true);
readonly bla = input(true);
}
class Derived extends Base {
@ -142,7 +142,7 @@ class Derived extends Base {
// overridden in separate file
@Directive()
export class Base2 {
bla = input(true);
readonly bla = input(true);
}
@@@@@@ derived_class_meta_input_alias.ts @@@@@@
@ -153,7 +153,7 @@ import { Directive, input } from '@angular/core';
@Directive()
class Base {
// should not be migrated.
bla = input(true);
readonly bla = input(true);
}
@Directive({
@ -182,7 +182,7 @@ let nextUniqueId = 0;
@Directive()
export class MatHint {
align: string = '';
id = input(`mat-hint-${nextUniqueId++}`);
readonly id = input(`mat-hint-${nextUniqueId++}`);
}
@Component({
@ -226,7 +226,7 @@ import { input } from '@angular/core';
class ExistingSignalImport {
signalInput = input<boolean>();
thisCanBeMigrated = input(true);
readonly thisCanBeMigrated = input(true);
}
@@@@@@ getters.ts @@@@@@
@ -266,7 +266,7 @@ import { Component, input } from '@angular/core';
},
})
class HostBindingTestCmp {
id = input('works');
readonly id = input('works');
// for testing nested expressions.
nested = this;
@ -284,7 +284,7 @@ const SHARED = {
host: SHARED,
})
class HostBindingsShared {
id = input(false);
readonly id = input(false);
}
@Component({
@ -292,8 +292,8 @@ class HostBindingsShared {
host: SHARED,
})
class HostBindingsShared2 {
id = input(false);
id2 = input(false);
readonly id = input(false);
readonly id2 = input(false);
}
@@@@@@ identifier_collisions.ts @@@@@@
@ -313,8 +313,8 @@ const complex = 'some global variable';
@Component({template: ''})
class MyComp {
name = input<string | null>(null);
complex = input<string | null>(null);
readonly name = input<string | null>(null);
readonly complex = input<string | null>(null);
valid() {
const name = this.name();
@ -371,11 +371,11 @@ interface Audi extends Car {
templateUrl: './template.html',
})
export class AppComponent {
input = input<string | null>(null);
bla = input.required<boolean, string | boolean>({ transform: disabledTransform });
narrowableMultipleTimes = input<Vehicle | null>(null);
withUndefinedInput = input<string>();
incompatible = input<string | null>(null);
readonly input = input<string | null>(null);
readonly bla = input.required<boolean, string | boolean>({ transform: disabledTransform });
readonly narrowableMultipleTimes = input<Vehicle | null>(null);
readonly withUndefinedInput = input<string>();
readonly incompatible = input<string | null>(null);
private _bla: any;
@Input()
@ -496,7 +496,7 @@ import { Component, input } from '@angular/core';
@Component({template: ''})
class IndexAccessInput {
items = input<string[]>([]);
readonly items = input<string[]>([]);
bla() {
const {items} = this;
@ -558,7 +558,7 @@ import { Component, input } from '@angular/core';
`,
})
export class InlineTmpl {
justify = input<'start' | 'end'>('end');
readonly justify = input<'start' | 'end'>('end');
}
@@@@@@ jit_true_component_external_tmpl.html @@@@@@
@ -582,7 +582,7 @@ import { Component, input } from '@angular/core';
template: '{{test()}}',
})
class JitTrueComponent {
test = input(true);
readonly test = input(true);
}
@Component({
@ -590,7 +590,7 @@ class JitTrueComponent {
templateUrl: './jit_true_component_external_tmpl.html',
})
class JitTrueComponentExternalTmpl {
test = input(true);
readonly test = input(true);
}
@@@@@@ loop_labels.ts @@@@@@
@ -599,7 +599,7 @@ class JitTrueComponentExternalTmpl {
import { input } from '@angular/core';
class MyTestCmp {
someInput = input.required<boolean | string>();
readonly someInput = input.required<boolean | string>();
tmpValue = false;
@ -638,6 +638,24 @@ import {Component, Input} from '@angular/core';
export class ManualInstantiation {
@Input() bla: string = '';
}
@@@@@@ modifier_tests.ts @@@@@@
// tslint:disable
import { Component, input } from '@angular/core';
function CustomDecorator() {
return (a: any, b: any) => {};
}
@Component({template: ''})
class ModifierScenarios {
readonly alreadyReadonly = input(true);
protected readonly ImProtected = input(true);
protected readonly ImProtectedAndReadonly = input(true);
@CustomDecorator()
protected readonly usingCustomDecorator = input(true);
}
@@@@@@ mutate.ts @@@@@@
// tslint:disable
@ -645,7 +663,7 @@ export class ManualInstantiation {
import { input } from '@angular/core';
export class TestCmp {
shared = input<{
readonly shared = input<{
x: string;
}>({ x: '' });
@ -678,7 +696,7 @@ interface Config {
`,
})
export class NestedTemplatePropAccess {
config = input<Config>({});
readonly config = input<Config>({});
}
@@@@@@ object_expansion.ts @@@@@@
@ -688,7 +706,7 @@ import { Component, input } from '@angular/core';
@Component({})
export class ObjectExpansion {
bla = input<string>('');
readonly bla = input<string>('');
expansion() {
const {bla} = this;
@ -720,7 +738,7 @@ import { Directive, input } from '@angular/core';
@Directive()
class OptionalInput {
bla = input<string>();
readonly bla = input<string>();
}
@@@@@@ problematic_type_reference.ts @@@@@@
@ -734,7 +752,7 @@ import { Component, Directive, QueryList, input } from '@angular/core';
`,
})
class Group {
label = input.required<string>();
readonly label = input.required<string>();
}
@Directive()
@ -767,12 +785,12 @@ import {COMPLEX_VAR} from './required-no-explicit-type-extra';
export const CONST = {field: true};
export class RequiredNoExplicitType {
someInputNumber = input.required<number>();
someInput = input.required<boolean>();
withConstInitialVal = input.required<typeof CONST>();
readonly someInputNumber = input.required<number>();
readonly someInput = input.required<boolean>();
readonly withConstInitialVal = input.required<typeof CONST>();
// typing this explicitly now would require same imports as from the `-extra` file.
complexVal = input.required<typeof COMPLEX_VAR>();
readonly complexVal = input.required<typeof COMPLEX_VAR>();
}
@@@@@@ required.ts @@@@@@
@ -781,7 +799,7 @@ export class RequiredNoExplicitType {
import { input } from '@angular/core';
class Required {
simpleInput = input.required<string>();
readonly simpleInput = input.required<string>();
}
@@@@@@ safe_property_reads.ts @@@@@@
@ -803,7 +821,7 @@ import { Component, input } from '@angular/core';
`,
})
class WithSafePropertyReads {
myInput = input(0);
readonly myInput = input(0);
bla: this | undefined = this;
}
@ -814,7 +832,7 @@ class WithSafePropertyReads {
import { input } from '@angular/core';
export class TestCmp {
shared = input(false);
readonly shared = input(false);
bla() {
const shared = this.shared();
@ -837,7 +855,7 @@ import { Directive, Component, input } from '@angular/core';
@Directive()
class SomeDir {
bla = input.required<RegExp>();
readonly bla = input.required<RegExp>();
}
@Component({
@ -928,11 +946,11 @@ import { Component, input } from '@angular/core';
`,
})
export class MyComp {
first = input(true);
second = input(false);
third = input(true);
fourth = input(true);
fifth = input(true);
readonly first = input(true);
readonly second = input(false);
readonly third = input(true);
readonly fourth = input(true);
readonly fifth = input(true);
}
@@@@@@ template_object_shorthand.ts @@@@@@
@ -950,7 +968,7 @@ import { Component, input } from '@angular/core';
},
})
export class TemplateObjectShorthand {
myInput = input(true);
readonly myInput = input(true);
}
@@@@@@ template_writes.ts @@@@@@
@ -969,10 +987,10 @@ import { Component, input } from '@angular/core';
},
})
class TwoWayBinding {
inputA = input('');
inputB = input(true);
inputC = input(false);
inputD = input(false);
readonly inputA = input('');
readonly inputB = input(true);
readonly inputC = input(false);
readonly inputD = input(false);
}
@@@@@@ transform_functions.ts @@@@@@
@ -987,12 +1005,12 @@ function x(v: string | undefined): string | undefined {
export class TransformFunctions {
// We can check this, and expect `as any` due to transform incompatibility.
withExplicitTypeWorks = input.required<{
readonly withExplicitTypeWorks = input.required<{
ok: true;
}, string | undefined>({ transform: ((v: string | undefined) => '') as any });
// This will be a synthetic type because we add `undefined` to `boolean`.
synthetic1 = input.required<boolean | undefined, string | undefined>({ transform: x });
readonly synthetic1 = input.required<boolean | undefined, string | undefined>({ transform: x });
// Synthetic as we infer a full type from the initial value. Cannot be checked.
@Input({required: true, transform: (v: string | undefined) => ''}) synthetic2 = {
infer: COMPLEX_VAR,
@ -1009,7 +1027,7 @@ import { Directive, input } from '@angular/core';
@Directive()
class TransformIncompatibleTypes {
// @ts-ignore Simulate `--strictPropertyInitialization=false`.
disabled = input<boolean, unknown>(undefined, { transform: ((v: unknown) => (v === null ? undefined : !!v)) as any });
readonly disabled = input<boolean, unknown>(undefined, { transform: ((v: unknown) => (v === null ? undefined : !!v)) as any });
}
@@@@@@ with_getters.ts @@@@@@
@ -1036,7 +1054,7 @@ export class WithSettersAndGetters {
}
private _accessor: string = '';
simpleInput = input.required<string>();
readonly simpleInput = input.required<string>();
}
@@@@@@ with_getters_reference.ts @@@@@@
@ -1063,7 +1081,7 @@ class WithJsdoc {
/**
* Works
*/
simpleInput = input.required<string>();
readonly simpleInput = input.required<string>();
withCommentInside = input</* intended */ boolean>();
readonly withCommentInside = input</* intended */ boolean>();
}