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