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