-
Notifications
You must be signed in to change notification settings - Fork 12.5k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
add support of codefix for Strict Class Initialization (#21528)
* add support of add undefined type to propertyDeclaration * add support of add Definite Assignment Assertions to propertyDeclaration * add support of add Initializer to propertyDeclaration * remove useless parameter * fix PropertyDeclaration emit missing exclamationToken * merge fixes and fix * fix unnecessary type assert
- Loading branch information
Showing
23 changed files
with
640 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,142 @@ | ||
/* @internal */ | ||
namespace ts.codefix { | ||
const fixIdAddDefiniteAssignmentAssertions = "addMissingPropertyDefiniteAssignmentAssertions"; | ||
const fixIdAddUndefinedType = "addMissingPropertyUndefinedType"; | ||
const fixIdAddInitializer = "addMissingPropertyInitializer"; | ||
const errorCodes = [Diagnostics.Property_0_has_no_initializer_and_is_not_definitely_assigned_in_the_constructor.code]; | ||
registerCodeFix({ | ||
errorCodes, | ||
getCodeActions: (context) => { | ||
const propertyDeclaration = getPropertyDeclaration(context.sourceFile, context.span.start); | ||
if (!propertyDeclaration) return; | ||
|
||
const newLineCharacter = getNewLineOrDefaultFromHost(context.host, context.formatContext.options); | ||
const result = [ | ||
getActionForAddMissingUndefinedType(context, propertyDeclaration), | ||
getActionForAddMissingDefiniteAssignmentAssertion(context, propertyDeclaration, newLineCharacter) | ||
]; | ||
|
||
append(result, getActionForAddMissingInitializer(context, propertyDeclaration, newLineCharacter)); | ||
|
||
return result; | ||
}, | ||
fixIds: [fixIdAddDefiniteAssignmentAssertions, fixIdAddUndefinedType, fixIdAddInitializer], | ||
getAllCodeActions: context => { | ||
const newLineCharacter = getNewLineOrDefaultFromHost(context.host, context.formatContext.options); | ||
|
||
return codeFixAll(context, errorCodes, (changes, diag) => { | ||
const propertyDeclaration = getPropertyDeclaration(diag.file, diag.start); | ||
if (!propertyDeclaration) return; | ||
|
||
switch (context.fixId) { | ||
case fixIdAddDefiniteAssignmentAssertions: | ||
addDefiniteAssignmentAssertion(changes, diag.file, propertyDeclaration, newLineCharacter); | ||
break; | ||
case fixIdAddUndefinedType: | ||
addUndefinedType(changes, diag.file, propertyDeclaration); | ||
break; | ||
case fixIdAddInitializer: | ||
const checker = context.program.getTypeChecker(); | ||
const initializer = getInitializer(checker, propertyDeclaration); | ||
if (!initializer) return; | ||
|
||
addInitializer(changes, diag.file, propertyDeclaration, initializer, newLineCharacter); | ||
break; | ||
default: | ||
Debug.fail(JSON.stringify(context.fixId)); | ||
} | ||
}); | ||
}, | ||
}); | ||
|
||
function getPropertyDeclaration (sourceFile: SourceFile, pos: number): PropertyDeclaration | undefined { | ||
const token = getTokenAtPosition(sourceFile, pos, /*includeJsDocComment*/ false); | ||
return isIdentifier(token) ? cast(token.parent, isPropertyDeclaration) : undefined; | ||
} | ||
|
||
function getActionForAddMissingDefiniteAssignmentAssertion (context: CodeFixContext, propertyDeclaration: PropertyDeclaration, newLineCharacter: string): CodeFixAction { | ||
const description = formatStringFromArgs(getLocaleSpecificMessage(Diagnostics.Add_definite_assignment_assertion_to_property_0), [propertyDeclaration.getText()]); | ||
const changes = textChanges.ChangeTracker.with(context, t => addDefiniteAssignmentAssertion(t, context.sourceFile, propertyDeclaration, newLineCharacter)); | ||
return { description, changes, fixId: fixIdAddDefiniteAssignmentAssertions }; | ||
} | ||
|
||
function addDefiniteAssignmentAssertion(changeTracker: textChanges.ChangeTracker, propertyDeclarationSourceFile: SourceFile, propertyDeclaration: PropertyDeclaration, newLineCharacter: string): void { | ||
const property = updateProperty( | ||
propertyDeclaration, | ||
propertyDeclaration.decorators, | ||
propertyDeclaration.modifiers, | ||
propertyDeclaration.name, | ||
createToken(SyntaxKind.ExclamationToken), | ||
propertyDeclaration.type, | ||
propertyDeclaration.initializer | ||
); | ||
changeTracker.replaceNode(propertyDeclarationSourceFile, propertyDeclaration, property, { suffix: newLineCharacter }); | ||
} | ||
|
||
function getActionForAddMissingUndefinedType (context: CodeFixContext, propertyDeclaration: PropertyDeclaration): CodeFixAction { | ||
const description = formatStringFromArgs(getLocaleSpecificMessage(Diagnostics.Add_undefined_type_to_property_0), [propertyDeclaration.name.getText()]); | ||
const changes = textChanges.ChangeTracker.with(context, t => addUndefinedType(t, context.sourceFile, propertyDeclaration)); | ||
return { description, changes, fixId: fixIdAddUndefinedType }; | ||
} | ||
|
||
function addUndefinedType(changeTracker: textChanges.ChangeTracker, propertyDeclarationSourceFile: SourceFile, propertyDeclaration: PropertyDeclaration): void { | ||
const undefinedTypeNode = createKeywordTypeNode(SyntaxKind.UndefinedKeyword); | ||
const types = isUnionTypeNode(propertyDeclaration.type) ? propertyDeclaration.type.types.concat(undefinedTypeNode) : [propertyDeclaration.type, undefinedTypeNode]; | ||
changeTracker.replaceNode(propertyDeclarationSourceFile, propertyDeclaration.type, createUnionTypeNode(types)); | ||
} | ||
|
||
function getActionForAddMissingInitializer (context: CodeFixContext, propertyDeclaration: PropertyDeclaration, newLineCharacter: string): CodeFixAction | undefined { | ||
const checker = context.program.getTypeChecker(); | ||
const initializer = getInitializer(checker, propertyDeclaration); | ||
if (!initializer) return undefined; | ||
|
||
const description = formatStringFromArgs(getLocaleSpecificMessage(Diagnostics.Add_initializer_to_property_0), [propertyDeclaration.name.getText()]); | ||
const changes = textChanges.ChangeTracker.with(context, t => addInitializer(t, context.sourceFile, propertyDeclaration, initializer, newLineCharacter)); | ||
return { description, changes, fixId: fixIdAddInitializer }; | ||
} | ||
|
||
function addInitializer (changeTracker: textChanges.ChangeTracker, propertyDeclarationSourceFile: SourceFile, propertyDeclaration: PropertyDeclaration, initializer: Expression, newLineCharacter: string): void { | ||
const property = updateProperty( | ||
propertyDeclaration, | ||
propertyDeclaration.decorators, | ||
propertyDeclaration.modifiers, | ||
propertyDeclaration.name, | ||
propertyDeclaration.questionToken, | ||
propertyDeclaration.type, | ||
initializer | ||
); | ||
changeTracker.replaceNode(propertyDeclarationSourceFile, propertyDeclaration, property, { suffix: newLineCharacter }); | ||
} | ||
|
||
function getInitializer(checker: TypeChecker, propertyDeclaration: PropertyDeclaration): Expression | undefined { | ||
return getDefaultValueFromType(checker, checker.getTypeFromTypeNode(propertyDeclaration.type)); | ||
} | ||
|
||
function getDefaultValueFromType (checker: TypeChecker, type: Type): Expression | undefined { | ||
if (type.flags & TypeFlags.String) { | ||
return createLiteral(""); | ||
} | ||
else if (type.flags & TypeFlags.Number) { | ||
return createNumericLiteral("0"); | ||
} | ||
else if (type.flags & TypeFlags.Boolean) { | ||
return createFalse(); | ||
} | ||
else if (type.flags & TypeFlags.Literal) { | ||
return createLiteral((<LiteralType>type).value); | ||
} | ||
else if (type.flags & TypeFlags.Union) { | ||
return firstDefined((<UnionType>type).types, t => getDefaultValueFromType(checker, t)); | ||
} | ||
else if (getObjectFlags(type) & ObjectFlags.Class) { | ||
const classDeclaration = getClassLikeDeclarationOfSymbol(type.symbol); | ||
if (!classDeclaration || hasModifier(classDeclaration, ModifierFlags.Abstract)) return undefined; | ||
|
||
const constructorDeclaration = find<ClassElement, ConstructorDeclaration>(classDeclaration.members, (m): m is ConstructorDeclaration => isConstructorDeclaration(m) && !!m.body)!; | ||
if (constructorDeclaration && constructorDeclaration.parameters.length) return undefined; | ||
|
||
return createNew(createIdentifier(type.symbol.name), /*typeArguments*/ undefined, /*argumentsArray*/ undefined); | ||
} | ||
return undefined; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
40 changes: 40 additions & 0 deletions
40
tests/cases/fourslash/codeFixClassPropertyInitialization.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
/// <reference path='fourslash.ts' /> | ||
|
||
// @strict: true | ||
|
||
//// abstract class A { abstract a (); } | ||
//// | ||
//// class TT { constructor () {} } | ||
//// | ||
//// class AT extends A { a () {} } | ||
//// | ||
//// class Foo {} | ||
//// | ||
//// class T { | ||
//// | ||
//// a: string; | ||
//// | ||
//// static b: string; | ||
//// | ||
//// private c: string; | ||
//// | ||
//// d: number | undefined; | ||
//// | ||
//// e: string | number; | ||
//// | ||
//// f: 1; | ||
//// | ||
//// g: "123" | "456"; | ||
//// | ||
//// h: boolean; | ||
//// | ||
//// i: TT; | ||
//// | ||
//// j: A; | ||
//// | ||
//// k: AT; | ||
//// | ||
//// l: Foo; | ||
//// } | ||
|
||
verify.codeFixAvailable() |
15 changes: 15 additions & 0 deletions
15
tests/cases/fourslash/codeFixClassPropertyInitialization1.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
/// <reference path='fourslash.ts' /> | ||
|
||
// @strict: true | ||
|
||
//// class T { | ||
//// a: string; | ||
//// } | ||
|
||
verify.codeFix({ | ||
description: `Add 'undefined' type to property 'a'`, | ||
newFileContent: `class T { | ||
a: string | undefined; | ||
}`, | ||
index: 0 | ||
}) |
15 changes: 15 additions & 0 deletions
15
tests/cases/fourslash/codeFixClassPropertyInitialization10.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
/// <reference path='fourslash.ts' /> | ||
|
||
// @strict: true | ||
|
||
//// class T { | ||
//// a: "a" | 2; | ||
//// } | ||
|
||
verify.codeFix({ | ||
description: `Add initializer to property 'a'`, | ||
newFileContent: `class T { | ||
a: "a" | 2 = "a"; | ||
}`, | ||
index: 2 | ||
}) |
19 changes: 19 additions & 0 deletions
19
tests/cases/fourslash/codeFixClassPropertyInitialization11.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
/// <reference path='fourslash.ts' /> | ||
|
||
// @strict: true | ||
|
||
//// class TT { constructor () {} } | ||
//// | ||
//// class T { | ||
//// a: TT; | ||
//// } | ||
|
||
verify.codeFix({ | ||
description: `Add initializer to property 'a'`, | ||
newFileContent: `class TT { constructor () {} } | ||
class T { | ||
a: TT = new TT; | ||
}`, | ||
index: 2 | ||
}) |
23 changes: 23 additions & 0 deletions
23
tests/cases/fourslash/codeFixClassPropertyInitialization12.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
/// <reference path='fourslash.ts' /> | ||
|
||
// @strict: true | ||
|
||
//// abstract class A { abstract a (); } | ||
//// | ||
//// class AT extends A { a () {} } | ||
//// | ||
//// class T { | ||
//// a: AT; | ||
//// } | ||
|
||
verify.codeFix({ | ||
description: `Add initializer to property 'a'`, | ||
newFileContent: `abstract class A { abstract a (); } | ||
class AT extends A { a () {} } | ||
class T { | ||
a: AT = new AT; | ||
}`, | ||
index: 2 | ||
}) |
19 changes: 19 additions & 0 deletions
19
tests/cases/fourslash/codeFixClassPropertyInitialization13.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
/// <reference path='fourslash.ts' /> | ||
|
||
// @strict: true | ||
|
||
//// class TT { } | ||
//// | ||
//// class T { | ||
//// a: TT; | ||
//// } | ||
|
||
verify.codeFix({ | ||
description: `Add initializer to property 'a'`, | ||
newFileContent: `class TT { } | ||
class T { | ||
a: TT = new TT; | ||
}`, | ||
index: 2 | ||
}) |
15 changes: 15 additions & 0 deletions
15
tests/cases/fourslash/codeFixClassPropertyInitialization2.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
/// <reference path='fourslash.ts' /> | ||
|
||
// @strict: true | ||
|
||
//// class T { | ||
//// a: string; | ||
//// } | ||
|
||
verify.codeFix({ | ||
description: `Add definite assignment assertion to property 'a: string;'`, | ||
newFileContent: `class T { | ||
a!: string; | ||
}`, | ||
index: 1 | ||
}) |
15 changes: 15 additions & 0 deletions
15
tests/cases/fourslash/codeFixClassPropertyInitialization3.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
/// <reference path='fourslash.ts' /> | ||
|
||
// @strict: true | ||
|
||
//// class T { | ||
//// a: string; | ||
//// } | ||
|
||
verify.codeFix({ | ||
description: `Add initializer to property 'a'`, | ||
newFileContent: `class T { | ||
a: string = ""; | ||
}`, | ||
index: 2 | ||
}) |
Oops, something went wrong.