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