add quick fix for add missing enum member

This commit is contained in:
王文璐 2018-06-24 23:04:43 +08:00
parent 22d33d2292
commit d5268c81e7
11 changed files with 217 additions and 25 deletions

View file

@ -4418,5 +4418,13 @@
"Remove braces from arrow function": {
"category": "Message",
"code": 95060
},
"Add missing enum member '{0}'": {
"category": "Message",
"code": 95061
},
"Add all missing enum members": {
"category": "Message",
"code": 95062
}
}

View file

@ -11,11 +11,15 @@ namespace ts.codefix {
getCodeActions(context) {
const info = getInfo(context.sourceFile, context.span.start, context.program.getTypeChecker());
if (!info) return undefined;
const { classDeclaration, classDeclarationSourceFile, inJs, makeStatic, token, call } = info;
const methodCodeAction = call && getActionForMethodDeclaration(context, classDeclarationSourceFile, classDeclaration, token, call, makeStatic, inJs, context.preferences);
if (isEnumInfo(info)) {
return singleElementArray(getActionForEnumMemberDeclaration(context, info.enumDeclarationSourceFile, info.declaration, info.token));
}
const { declaration, classDeclarationSourceFile, inJs, makeStatic, token, call } = info;
const methodCodeAction = call && getActionForMethodDeclaration(context, classDeclarationSourceFile, declaration, token, call, makeStatic, inJs, context.preferences);
const addMember = inJs ?
singleElementArray(getActionsForAddMissingMemberInJavaScriptFile(context, classDeclarationSourceFile, classDeclaration, token.text, makeStatic)) :
getActionsForAddMissingMemberInTypeScriptFile(context, classDeclarationSourceFile, classDeclaration, token, makeStatic);
singleElementArray(getActionsForAddMissingMemberInJavaScriptFile(context, classDeclarationSourceFile, declaration, token.text, makeStatic)) :
getActionsForAddMissingMemberInTypeScriptFile(context, classDeclarationSourceFile, declaration, token, makeStatic);
return concatenate(singleElementArray(methodCodeAction), addMember);
},
fixIds: [fixId],
@ -23,31 +27,44 @@ namespace ts.codefix {
const seenNames = createMap<true>();
return codeFixAll(context, errorCodes, (changes, diag) => {
const { program, preferences } = context;
const info = getInfo(diag.file, diag.start, program.getTypeChecker());
if (!info) return;
const { classDeclaration, classDeclarationSourceFile, inJs, makeStatic, token, call } = info;
if (!addToSeen(seenNames, token.text)) {
const checker = program.getTypeChecker();
const info = getInfo(diag.file, diag.start, checker);
if (!info || !addToSeen(seenNames, info.token.text)) {
return;
}
// Always prefer to add a method declaration if possible.
if (call) {
addMethodDeclaration(context, changes, classDeclarationSourceFile, classDeclaration, token, call, makeStatic, inJs, preferences);
if (isEnumInfo(info)) {
const { token, declaration, enumDeclarationSourceFile } = info;
addEnumMemberDeclaration(changes, checker, token, declaration, enumDeclarationSourceFile);
}
else {
if (inJs) {
addMissingMemberInJs(changes, classDeclarationSourceFile, classDeclaration, token.text, makeStatic);
const { declaration, classDeclarationSourceFile, inJs, makeStatic, token, call } = info;
// Always prefer to add a method declaration if possible.
if (call) {
addMethodDeclaration(context, changes, classDeclarationSourceFile, declaration, token, call, makeStatic, inJs, preferences);
}
else {
const typeNode = getTypeNode(program.getTypeChecker(), classDeclaration, token);
addPropertyDeclaration(changes, classDeclarationSourceFile, classDeclaration, token.text, typeNode, makeStatic);
if (inJs) {
addMissingMemberInJs(changes, classDeclarationSourceFile, declaration, token.text, makeStatic);
}
else {
const typeNode = getTypeNode(program.getTypeChecker(), declaration, token);
addPropertyDeclaration(changes, classDeclarationSourceFile, declaration, token.text, typeNode, makeStatic);
}
}
}
});
},
});
interface Info { token: Identifier; classDeclaration: ClassLikeDeclaration; makeStatic: boolean; classDeclarationSourceFile: SourceFile; inJs: boolean; call: CallExpression | undefined; }
interface EnumInfo { token: Identifier; declaration: EnumDeclaration; enumDeclarationSourceFile: SourceFile; }
interface ClassInfo { token: Identifier; declaration: ClassLikeDeclaration; makeStatic: boolean; classDeclarationSourceFile: SourceFile; inJs: boolean; call: CallExpression | undefined; }
type Info = EnumInfo | ClassInfo;
function isEnumInfo (info: Info): info is EnumInfo {
return isEnumDeclaration(info.declaration);
}
function getInfo(tokenSourceFile: SourceFile, tokenPos: number, checker: TypeChecker): Info | undefined {
// The identifier of the missing property. eg:
// this.missing = 1;
@ -62,15 +79,21 @@ namespace ts.codefix {
const leftExpressionType = skipConstraint(checker.getTypeAtLocation(parent.expression)!);
const { symbol } = leftExpressionType;
const classDeclaration = symbol && symbol.declarations && find(symbol.declarations, isClassLike);
if (!classDeclaration) return undefined;
if (!symbol || !symbol.declarations) return undefined;
const makeStatic = (leftExpressionType as TypeReference).target !== checker.getDeclaredTypeOfSymbol(symbol);
const classDeclarationSourceFile = classDeclaration.getSourceFile();
const inJs = isSourceFileJavaScript(classDeclarationSourceFile);
const call = tryCast(parent.parent, isCallExpression);
return { token, classDeclaration, makeStatic, classDeclarationSourceFile, inJs, call };
const classDeclaration = find(symbol.declarations, isClassLike);
if (classDeclaration) {
const makeStatic = (leftExpressionType as TypeReference).target !== checker.getDeclaredTypeOfSymbol(symbol);
const classDeclarationSourceFile = classDeclaration.getSourceFile();
const inJs = isSourceFileJavaScript(classDeclarationSourceFile);
const call = tryCast(parent.parent, isCallExpression);
return { token, declaration: classDeclaration, makeStatic, classDeclarationSourceFile, inJs, call };
}
const enumDeclaration = find(symbol.declarations, isEnumDeclaration);
if (enumDeclaration) {
return { token, declaration: enumDeclaration, enumDeclarationSourceFile: enumDeclaration.getSourceFile() };
}
return undefined;
}
function getActionsForAddMissingMemberInJavaScriptFile(context: CodeFixContext, classDeclarationSourceFile: SourceFile, classDeclaration: ClassLikeDeclaration, tokenName: string, makeStatic: boolean): CodeFixAction | undefined {
@ -188,6 +211,16 @@ namespace ts.codefix {
return createCodeFixAction(fixName, changes, [makeStatic ? Diagnostics.Declare_static_method_0 : Diagnostics.Declare_method_0, token.text], fixId, Diagnostics.Add_all_missing_members);
}
function getActionForEnumMemberDeclaration(
context: CodeFixContext,
enumDeclarationSourceFile: SourceFile,
enumDeclaration: EnumDeclaration,
token: Identifier
): CodeFixAction | undefined {
const changes = textChanges.ChangeTracker.with(context, t => addEnumMemberDeclaration(t, context.program.getTypeChecker(), token, enumDeclaration, enumDeclarationSourceFile));
return createCodeFixAction(fixName, changes, [Diagnostics.Add_missing_enum_member_0, token.text], fixId, Diagnostics.Add_all_missing_enum_members);
}
function addMethodDeclaration(
context: CodeFixContextBase,
changeTracker: textChanges.ChangeTracker,
@ -209,4 +242,32 @@ namespace ts.codefix {
changeTracker.insertNodeAtClassStart(classDeclarationSourceFile, classDeclaration, methodDeclaration);
}
}
function createEnumMemberFromEnumDeclaration(checker: TypeChecker, token: Identifier, enumDeclaration: EnumDeclaration) {
/**
* create initializer only string enum.
* value of initializer is a string literal that equal to name of enum member.
* literal enum or empty enum will not create initializer.
*/
const firstMember = firstOrUndefined(enumDeclaration.members);
let enumMemberInitializer: Expression | undefined;
if (firstMember && firstMember.initializer) {
const memberType = checker.getTypeAtLocation(firstMember.initializer);
if (memberType && memberType.flags & TypeFlags.StringLike) {
enumMemberInitializer = createStringLiteral(token.text);
}
}
return createEnumMember(token, enumMemberInitializer);
}
function addEnumMemberDeclaration(changes: textChanges.ChangeTracker, checker: TypeChecker, token: Identifier, enumDeclaration: EnumDeclaration, file: SourceFile) {
const enumMember = createEnumMemberFromEnumDeclaration(checker, token, enumDeclaration);
changes.replaceNode(file, enumDeclaration, updateEnumDeclaration(
enumDeclaration,
enumDeclaration.decorators,
enumDeclaration.modifiers,
enumDeclaration.name,
concatenate(enumDeclaration.members, singleElementArray(enumMember))
));
}
}

View file

@ -5914,6 +5914,8 @@ declare namespace ts {
Add_or_remove_braces_in_an_arrow_function: DiagnosticMessage;
Add_braces_to_arrow_function: DiagnosticMessage;
Remove_braces_from_arrow_function: DiagnosticMessage;
Add_missing_enum_member_0: DiagnosticMessage;
Add_all_missing_enum_members: DiagnosticMessage;
};
}
declare namespace ts {

View file

@ -0,0 +1,16 @@
/// <reference path='fourslash.ts' />
////enum E {
//// a
////}
////E.b
verify.codeFix({
description: "Add missing enum member 'b'",
newFileContent: `enum E {
a,
b
}
E.b`
});

View file

@ -0,0 +1,16 @@
/// <reference path='fourslash.ts' />
////enum E {
//// a = 1
////}
////E.b
verify.codeFix({
description: "Add missing enum member 'b'",
newFileContent: `enum E {
a = 1,
b
}
E.b`
});

View file

@ -0,0 +1,21 @@
/// <reference path='fourslash.ts' />
////enum E {
//// a,
//// b = 1,
//// c
////}
////E.d
verify.codeFix({
description: "Add missing enum member 'd'",
newFileContent: `enum E {
a,
b = 1,
c,
d
}
E.d`
});

View file

@ -0,0 +1,17 @@
/// <reference path='fourslash.ts' />
////enum E {
//// a = "a",
////}
////E.b
verify.codeFix({
description: "Add missing enum member 'b'",
newFileContent: `enum E {
a = "a",
b = "b"
}
E.b`
});

View file

@ -0,0 +1,17 @@
/// <reference path='fourslash.ts' />
////enum E {
//// a = "a" + "-",
////}
////E.b
verify.codeFix({
description: "Add missing enum member 'b'",
newFileContent: `enum E {
a = "a" + "-",
b = "b"
}
E.b`
});

View file

@ -0,0 +1,17 @@
/// <reference path='fourslash.ts' />
////enum E {
//// a = "b"
////}
////E.b
verify.codeFix({
description: "Add missing enum member 'b'",
newFileContent: `enum E {
a = "b",
b = "b"
}
E.b`
});

View file

@ -0,0 +1,15 @@
/// <reference path='fourslash.ts' />
////enum E {
////}
////E.a
verify.codeFix({
description: "Add missing enum member 'a'",
newFileContent: `enum E {
a
}
E.a`
});

View file

@ -14,4 +14,6 @@
//// let t: T<number>;
//// t.x;
verify.not.codeFixAvailable();
verify.codeFixAvailable([{
description: "Add missing enum member 'c'"
}]);