• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/* @internal */
2namespace ts.codefix {
3    const fixName = "strictClassInitialization";
4    const fixIdAddDefiniteAssignmentAssertions = "addMissingPropertyDefiniteAssignmentAssertions";
5    const fixIdAddUndefinedType = "addMissingPropertyUndefinedType";
6    const fixIdAddInitializer = "addMissingPropertyInitializer";
7    const errorCodes = [Diagnostics.Property_0_has_no_initializer_and_is_not_definitely_assigned_in_the_constructor.code];
8    registerCodeFix({
9        errorCodes,
10        getCodeActions: (context) => {
11            const propertyDeclaration = getPropertyDeclaration(context.sourceFile, context.span.start);
12            if (!propertyDeclaration) return;
13
14            const result = [
15                getActionForAddMissingUndefinedType(context, propertyDeclaration),
16                getActionForAddMissingDefiniteAssignmentAssertion(context, propertyDeclaration)
17            ];
18
19            append(result, getActionForAddMissingInitializer(context, propertyDeclaration));
20
21            return result;
22        },
23        fixIds: [fixIdAddDefiniteAssignmentAssertions, fixIdAddUndefinedType, fixIdAddInitializer],
24        getAllCodeActions: context => {
25            return codeFixAll(context, errorCodes, (changes, diag) => {
26                const propertyDeclaration = getPropertyDeclaration(diag.file, diag.start);
27                if (!propertyDeclaration) return;
28
29                switch (context.fixId) {
30                    case fixIdAddDefiniteAssignmentAssertions:
31                        addDefiniteAssignmentAssertion(changes, diag.file, propertyDeclaration);
32                        break;
33                    case fixIdAddUndefinedType:
34                        addUndefinedType(changes, diag.file, propertyDeclaration);
35                        break;
36                    case fixIdAddInitializer:
37                        const checker = context.program.getTypeChecker();
38                        const initializer = getInitializer(checker, propertyDeclaration);
39                        if (!initializer) return;
40
41                        addInitializer(changes, diag.file, propertyDeclaration, initializer);
42                        break;
43                    default:
44                        Debug.fail(JSON.stringify(context.fixId));
45                }
46            });
47        },
48    });
49
50    function getPropertyDeclaration(sourceFile: SourceFile, pos: number): PropertyDeclaration | undefined {
51        const token = getTokenAtPosition(sourceFile, pos);
52        return isIdentifier(token) ? cast(token.parent, isPropertyDeclaration) : undefined;
53    }
54
55    function getActionForAddMissingDefiniteAssignmentAssertion(context: CodeFixContext, propertyDeclaration: PropertyDeclaration): CodeFixAction {
56        const changes = textChanges.ChangeTracker.with(context, t => addDefiniteAssignmentAssertion(t, context.sourceFile, propertyDeclaration));
57        return createCodeFixAction(fixName, changes, [Diagnostics.Add_definite_assignment_assertion_to_property_0, propertyDeclaration.getText()], fixIdAddDefiniteAssignmentAssertions, Diagnostics.Add_definite_assignment_assertions_to_all_uninitialized_properties);
58    }
59
60    function addDefiniteAssignmentAssertion(changeTracker: textChanges.ChangeTracker, propertyDeclarationSourceFile: SourceFile, propertyDeclaration: PropertyDeclaration): void {
61        const property = factory.updatePropertyDeclaration(
62            propertyDeclaration,
63            propertyDeclaration.decorators,
64            propertyDeclaration.modifiers,
65            propertyDeclaration.name,
66            factory.createToken(SyntaxKind.ExclamationToken),
67            propertyDeclaration.type,
68            propertyDeclaration.initializer
69        );
70        changeTracker.replaceNode(propertyDeclarationSourceFile, propertyDeclaration, property);
71    }
72
73    function getActionForAddMissingUndefinedType(context: CodeFixContext, propertyDeclaration: PropertyDeclaration): CodeFixAction {
74        const changes = textChanges.ChangeTracker.with(context, t => addUndefinedType(t, context.sourceFile, propertyDeclaration));
75        return createCodeFixAction(fixName, changes, [Diagnostics.Add_undefined_type_to_property_0, propertyDeclaration.name.getText()], fixIdAddUndefinedType, Diagnostics.Add_undefined_type_to_all_uninitialized_properties);
76    }
77
78    function addUndefinedType(changeTracker: textChanges.ChangeTracker, propertyDeclarationSourceFile: SourceFile, propertyDeclaration: PropertyDeclaration): void {
79        const undefinedTypeNode = factory.createKeywordTypeNode(SyntaxKind.UndefinedKeyword);
80        const type = propertyDeclaration.type!; // TODO: GH#18217
81        const types = isUnionTypeNode(type) ? type.types.concat(undefinedTypeNode) : [type, undefinedTypeNode];
82        changeTracker.replaceNode(propertyDeclarationSourceFile, type, factory.createUnionTypeNode(types));
83    }
84
85    function getActionForAddMissingInitializer(context: CodeFixContext, propertyDeclaration: PropertyDeclaration): CodeFixAction | undefined {
86        const checker = context.program.getTypeChecker();
87        const initializer = getInitializer(checker, propertyDeclaration);
88        if (!initializer) return undefined;
89
90        const changes = textChanges.ChangeTracker.with(context, t => addInitializer(t, context.sourceFile, propertyDeclaration, initializer));
91        return createCodeFixAction(fixName, changes, [Diagnostics.Add_initializer_to_property_0, propertyDeclaration.name.getText()], fixIdAddInitializer, Diagnostics.Add_initializers_to_all_uninitialized_properties);
92    }
93
94    function addInitializer(changeTracker: textChanges.ChangeTracker, propertyDeclarationSourceFile: SourceFile, propertyDeclaration: PropertyDeclaration, initializer: Expression): void {
95        const property = factory.updatePropertyDeclaration(
96            propertyDeclaration,
97            propertyDeclaration.decorators,
98            propertyDeclaration.modifiers,
99            propertyDeclaration.name,
100            propertyDeclaration.questionToken,
101            propertyDeclaration.type,
102            initializer
103        );
104        changeTracker.replaceNode(propertyDeclarationSourceFile, propertyDeclaration, property);
105    }
106
107    function getInitializer(checker: TypeChecker, propertyDeclaration: PropertyDeclaration): Expression | undefined {
108        return getDefaultValueFromType(checker, checker.getTypeFromTypeNode(propertyDeclaration.type!)); // TODO: GH#18217
109    }
110
111    function getDefaultValueFromType(checker: TypeChecker, type: Type): Expression | undefined {
112        if (type.flags & TypeFlags.BooleanLiteral) {
113            return (type === checker.getFalseType() || type === checker.getFalseType(/*fresh*/ true)) ? factory.createFalse() : factory.createTrue();
114        }
115        else if (type.isStringLiteral()) {
116            return factory.createStringLiteral(type.value);
117        }
118        else if (type.isNumberLiteral()) {
119            return factory.createNumericLiteral(type.value);
120        }
121        else if (type.flags & TypeFlags.BigIntLiteral) {
122            return factory.createBigIntLiteral((type as BigIntLiteralType).value);
123        }
124        else if (type.isUnion()) {
125            return firstDefined(type.types, t => getDefaultValueFromType(checker, t));
126        }
127        else if (type.isClass()) {
128            const classDeclaration = getClassLikeDeclarationOfSymbol(type.symbol);
129            if (!classDeclaration || hasSyntacticModifier(classDeclaration, ModifierFlags.Abstract)) return undefined;
130
131            const constructorDeclaration = getFirstConstructorWithBody(classDeclaration);
132            if (constructorDeclaration && constructorDeclaration.parameters.length) return undefined;
133
134            return factory.createNewExpression(factory.createIdentifier(type.symbol.name), /*typeArguments*/ undefined, /*argumentsArray*/ undefined);
135        }
136        else if (checker.isArrayLikeType(type)) {
137            return factory.createArrayLiteralExpression();
138        }
139        return undefined;
140    }
141}
142