|
| 1 | +/* @internal */ |
| 2 | +namespace ts.codefix { |
| 3 | + const fixIdAddDefiniteAssignmentAssertions = "addMissingPropertyDefiniteAssignmentAssertions"; |
| 4 | + const fixIdAddUndefinedType = "addMissingPropertyUndefinedType"; |
| 5 | + const fixIdAddInitializer = "addMissingPropertyInitializer"; |
| 6 | + const errorCodes = [Diagnostics.Property_0_has_no_initializer_and_is_not_definitely_assigned_in_the_constructor.code]; |
| 7 | + registerCodeFix({ |
| 8 | + errorCodes, |
| 9 | + getCodeActions: (context) => { |
| 10 | + const propertyDeclaration = getPropertyDeclaration(context.sourceFile, context.span.start); |
| 11 | + if (!propertyDeclaration) return; |
| 12 | + |
| 13 | + const newLineCharacter = getNewLineOrDefaultFromHost(context.host, context.formatContext.options); |
| 14 | + const result = [ |
| 15 | + getActionForAddMissingUndefinedType(context, propertyDeclaration), |
| 16 | + getActionForAddMissingDefiniteAssignmentAssertion(context, propertyDeclaration, newLineCharacter) |
| 17 | + ]; |
| 18 | + |
| 19 | + append(result, getActionForAddMissingInitializer(context, propertyDeclaration, newLineCharacter)); |
| 20 | + |
| 21 | + return result; |
| 22 | + }, |
| 23 | + fixIds: [fixIdAddDefiniteAssignmentAssertions, fixIdAddUndefinedType, fixIdAddInitializer], |
| 24 | + getAllCodeActions: context => { |
| 25 | + const newLineCharacter = getNewLineOrDefaultFromHost(context.host, context.formatContext.options); |
| 26 | + |
| 27 | + return codeFixAll(context, errorCodes, (changes, diag) => { |
| 28 | + const propertyDeclaration = getPropertyDeclaration(diag.file, diag.start); |
| 29 | + if (!propertyDeclaration) return; |
| 30 | + |
| 31 | + switch (context.fixId) { |
| 32 | + case fixIdAddDefiniteAssignmentAssertions: |
| 33 | + addDefiniteAssignmentAssertion(changes, diag.file, propertyDeclaration, newLineCharacter); |
| 34 | + break; |
| 35 | + case fixIdAddUndefinedType: |
| 36 | + addUndefinedType(changes, diag.file, propertyDeclaration); |
| 37 | + break; |
| 38 | + case fixIdAddInitializer: |
| 39 | + const checker = context.program.getTypeChecker(); |
| 40 | + const initializer = getInitializer(checker, propertyDeclaration); |
| 41 | + if (!initializer) return; |
| 42 | + |
| 43 | + addInitializer(changes, diag.file, propertyDeclaration, initializer, newLineCharacter); |
| 44 | + break; |
| 45 | + default: |
| 46 | + Debug.fail(JSON.stringify(context.fixId)); |
| 47 | + } |
| 48 | + }); |
| 49 | + }, |
| 50 | + }); |
| 51 | + |
| 52 | + function getPropertyDeclaration (sourceFile: SourceFile, pos: number): PropertyDeclaration | undefined { |
| 53 | + const token = getTokenAtPosition(sourceFile, pos, /*includeJsDocComment*/ false); |
| 54 | + return isIdentifier(token) ? cast(token.parent, isPropertyDeclaration) : undefined; |
| 55 | + } |
| 56 | + |
| 57 | + function getActionForAddMissingDefiniteAssignmentAssertion (context: CodeFixContext, propertyDeclaration: PropertyDeclaration, newLineCharacter: string): CodeFixAction { |
| 58 | + const description = formatStringFromArgs(getLocaleSpecificMessage(Diagnostics.Add_definite_assignment_assertion_to_property_0), [propertyDeclaration.getText()]); |
| 59 | + const changes = textChanges.ChangeTracker.with(context, t => addDefiniteAssignmentAssertion(t, context.sourceFile, propertyDeclaration, newLineCharacter)); |
| 60 | + return { description, changes, fixId: fixIdAddDefiniteAssignmentAssertions }; |
| 61 | + } |
| 62 | + |
| 63 | + function addDefiniteAssignmentAssertion(changeTracker: textChanges.ChangeTracker, propertyDeclarationSourceFile: SourceFile, propertyDeclaration: PropertyDeclaration, newLineCharacter: string): void { |
| 64 | + const property = updateProperty( |
| 65 | + propertyDeclaration, |
| 66 | + propertyDeclaration.decorators, |
| 67 | + propertyDeclaration.modifiers, |
| 68 | + propertyDeclaration.name, |
| 69 | + createToken(SyntaxKind.ExclamationToken), |
| 70 | + propertyDeclaration.type, |
| 71 | + propertyDeclaration.initializer |
| 72 | + ); |
| 73 | + changeTracker.replaceNode(propertyDeclarationSourceFile, propertyDeclaration, property, { suffix: newLineCharacter }); |
| 74 | + } |
| 75 | + |
| 76 | + function getActionForAddMissingUndefinedType (context: CodeFixContext, propertyDeclaration: PropertyDeclaration): CodeFixAction { |
| 77 | + const description = formatStringFromArgs(getLocaleSpecificMessage(Diagnostics.Add_undefined_type_to_property_0), [propertyDeclaration.name.getText()]); |
| 78 | + const changes = textChanges.ChangeTracker.with(context, t => addUndefinedType(t, context.sourceFile, propertyDeclaration)); |
| 79 | + return { description, changes, fixId: fixIdAddUndefinedType }; |
| 80 | + } |
| 81 | + |
| 82 | + function addUndefinedType(changeTracker: textChanges.ChangeTracker, propertyDeclarationSourceFile: SourceFile, propertyDeclaration: PropertyDeclaration): void { |
| 83 | + const undefinedTypeNode = createKeywordTypeNode(SyntaxKind.UndefinedKeyword); |
| 84 | + const types = isUnionTypeNode(propertyDeclaration.type) ? propertyDeclaration.type.types.concat(undefinedTypeNode) : [propertyDeclaration.type, undefinedTypeNode]; |
| 85 | + changeTracker.replaceNode(propertyDeclarationSourceFile, propertyDeclaration.type, createUnionTypeNode(types)); |
| 86 | + } |
| 87 | + |
| 88 | + function getActionForAddMissingInitializer (context: CodeFixContext, propertyDeclaration: PropertyDeclaration, newLineCharacter: string): CodeFixAction | undefined { |
| 89 | + const checker = context.program.getTypeChecker(); |
| 90 | + const initializer = getInitializer(checker, propertyDeclaration); |
| 91 | + if (!initializer) return undefined; |
| 92 | + |
| 93 | + const description = formatStringFromArgs(getLocaleSpecificMessage(Diagnostics.Add_initializer_to_property_0), [propertyDeclaration.name.getText()]); |
| 94 | + const changes = textChanges.ChangeTracker.with(context, t => addInitializer(t, context.sourceFile, propertyDeclaration, initializer, newLineCharacter)); |
| 95 | + return { description, changes, fixId: fixIdAddInitializer }; |
| 96 | + } |
| 97 | + |
| 98 | + function addInitializer (changeTracker: textChanges.ChangeTracker, propertyDeclarationSourceFile: SourceFile, propertyDeclaration: PropertyDeclaration, initializer: Expression, newLineCharacter: string): void { |
| 99 | + const property = updateProperty( |
| 100 | + propertyDeclaration, |
| 101 | + propertyDeclaration.decorators, |
| 102 | + propertyDeclaration.modifiers, |
| 103 | + propertyDeclaration.name, |
| 104 | + propertyDeclaration.questionToken, |
| 105 | + propertyDeclaration.type, |
| 106 | + initializer |
| 107 | + ); |
| 108 | + changeTracker.replaceNode(propertyDeclarationSourceFile, propertyDeclaration, property, { suffix: newLineCharacter }); |
| 109 | + } |
| 110 | + |
| 111 | + function getInitializer(checker: TypeChecker, propertyDeclaration: PropertyDeclaration): Expression | undefined { |
| 112 | + return getDefaultValueFromType(checker, checker.getTypeFromTypeNode(propertyDeclaration.type)); |
| 113 | + } |
| 114 | + |
| 115 | + function getDefaultValueFromType (checker: TypeChecker, type: Type): Expression | undefined { |
| 116 | + if (type.flags & TypeFlags.String) { |
| 117 | + return createLiteral(""); |
| 118 | + } |
| 119 | + else if (type.flags & TypeFlags.Number) { |
| 120 | + return createNumericLiteral("0"); |
| 121 | + } |
| 122 | + else if (type.flags & TypeFlags.Boolean) { |
| 123 | + return createFalse(); |
| 124 | + } |
| 125 | + else if (type.flags & TypeFlags.Literal) { |
| 126 | + return createLiteral((<LiteralType>type).value); |
| 127 | + } |
| 128 | + else if (type.flags & TypeFlags.Union) { |
| 129 | + return firstDefined((<UnionType>type).types, t => getDefaultValueFromType(checker, t)); |
| 130 | + } |
| 131 | + else if (getObjectFlags(type) & ObjectFlags.Class) { |
| 132 | + const classDeclaration = getClassLikeDeclarationOfSymbol(type.symbol); |
| 133 | + if (!classDeclaration || hasModifier(classDeclaration, ModifierFlags.Abstract)) return undefined; |
| 134 | + |
| 135 | + const constructorDeclaration = find<ClassElement, ConstructorDeclaration>(classDeclaration.members, (m): m is ConstructorDeclaration => isConstructorDeclaration(m) && !!m.body)!; |
| 136 | + if (constructorDeclaration && constructorDeclaration.parameters.length) return undefined; |
| 137 | + |
| 138 | + return createNew(createIdentifier(type.symbol.name), /*typeArguments*/ undefined, /*argumentsArray*/ undefined); |
| 139 | + } |
| 140 | + return undefined; |
| 141 | + } |
| 142 | +} |
0 commit comments