Split CodeFixes in Separate Files

This commit is contained in:
Arthur Ozga 2016-10-26 16:57:43 -07:00
parent ecc029fd1c
commit a66b0ae54c
10 changed files with 275 additions and 256 deletions

View File

@ -149,6 +149,7 @@ var servicesSources = [
"signatureHelp.ts",
"symbolDisplay.ts",
"transpile.ts",
// Formatting
"formatting/formatting.ts",
"formatting/formattingContext.ts",
"formatting/formattingRequestKind.ts",
@ -164,7 +165,15 @@ var servicesSources = [
"formatting/rulesMap.ts",
"formatting/rulesProvider.ts",
"formatting/smartIndenter.ts",
"formatting/tokenRange.ts"
"formatting/tokenRange.ts",
// CodeFixes
"codeFixes/codeFixProvider.ts",
"codeFixes/fixes.ts",
"codeFixes/fixExtendsInterfaceBecomesImplements.ts",
"codeFixes/fixClassIncorrectlyImplementsInterface.ts",
"codeFixes/fixClassDoesntImplementInheritedAbstractMember.ts",
"codeFixes/fixClassSuperMustPrecedeThisAccess.ts",
"codeFixes/fixConstructorForDerivedNeedSuperCall.ts"
].map(function (f) {
return path.join(servicesDirectory, f);
}));

View File

@ -0,0 +1,34 @@
/* @internal */
namespace ts.codefix {
registerCodeFix({
errorCodes: [Diagnostics.Non_abstract_class_0_does_not_implement_inherited_abstract_member_1_from_class_2.code],
getCodeActions: (context: CodeFixContext) => {
const sourceFile = context.sourceFile;
const start = context.span.start;
const token = getTokenAtPosition(sourceFile, start);
const checker = context.program.getTypeChecker();
if (token.kind === SyntaxKind.Identifier && isClassLike(token.parent)) {
const classDeclaration = <ClassDeclaration>token.parent;
const startPos = classDeclaration.members.pos;
// TODO: (arozga) actually get abstract members
const abstractClassMembers = ts.map(getNamedAbstractClassMembers(classDeclaration), member => member.name.getText());
const trackingAddedMembers: string[] = [];
const extendsClause = ts.getClassExtendsHeritageClauseElement(classDeclaration);
let textChanges = getCodeFixChanges(extendsClause, abstractClassMembers, startPos, checker, /*reference*/ false, trackingAddedMembers, context.newLineCharacter);
if (textChanges.length > 0) {
return [{
description: getLocaleSpecificMessage(Diagnostics.Implement_inherited_abstract_class),
changes: [{
fileName: sourceFile.fileName,
textChanges: textChanges
}]
}];
}
}
return undefined;
}
});
}

View File

@ -0,0 +1,39 @@
/* @internal */
namespace ts.codefix {
registerCodeFix({
errorCodes: [Diagnostics.Class_0_incorrectly_implements_interface_1.code],
getCodeActions: (context: CodeFixContext) => {
const sourceFile = context.sourceFile;
const start = context.span.start;
const token = getTokenAtPosition(sourceFile, start);
const checker = context.program.getTypeChecker();
if (token.kind === SyntaxKind.Identifier && isClassLike(token.parent)) {
const classDeclaration = <ClassDeclaration>token.parent;
const startPos: number = classDeclaration.members.pos;
const classMembers = ts.map(getNamedClassMembers(classDeclaration), member => member.name.getText());
const trackingAddedMembers: string[] = [];
const interfaceClauses = ts.getClassImplementsHeritageClauseElements(classDeclaration);
let textChanges: TextChange[] = undefined;
for (let i = 0; interfaceClauses && i < interfaceClauses.length; i++) {
let newChanges = getCodeFixChanges(interfaceClauses[i], classMembers, startPos, checker, /*reference*/ false, trackingAddedMembers, context.newLineCharacter);
textChanges = textChanges ? textChanges.concat(newChanges) : newChanges;
}
if (textChanges && textChanges.length > 0) {
return [{
description: getLocaleSpecificMessage(Diagnostics.Implement_interface_on_class),
changes: [{
fileName: sourceFile.fileName,
textChanges: textChanges
}]
}];
}
}
return undefined;
}
});
}

View File

@ -1,28 +1,5 @@
/* @internal */
namespace ts.codefix {
function getOpenBraceEnd(constructor: ConstructorDeclaration, sourceFile: SourceFile) {
// First token is the open curly, this is where we want to put the 'super' call.
return constructor.body.getFirstToken(sourceFile).getEnd();
}
registerCodeFix({
errorCodes: [Diagnostics.Constructors_for_derived_classes_must_contain_a_super_call.code],
getCodeActions: (context: CodeFixContext) => {
const sourceFile = context.sourceFile;
const token = getTokenAtPosition(sourceFile, context.span.start);
if (token.kind !== SyntaxKind.ConstructorKeyword) {
return undefined;
}
const newPosition = getOpenBraceEnd(<ConstructorDeclaration>token.parent, sourceFile);
return [{
description: getLocaleSpecificMessage(Diagnostics.Add_missing_super_call),
changes: [{ fileName: sourceFile.fileName, textChanges: [{ newText: "super();", span: { start: newPosition, length: 0 } }] }]
}];
}
});
registerCodeFix({
errorCodes: [Diagnostics.super_must_be_called_before_accessing_this_in_the_constructor_of_a_derived_class.code],
getCodeActions: (context: CodeFixContext) => {

View File

@ -0,0 +1,20 @@
/* @internal */
namespace ts.codefix {
registerCodeFix({
errorCodes: [Diagnostics.Constructors_for_derived_classes_must_contain_a_super_call.code],
getCodeActions: (context: CodeFixContext) => {
const sourceFile = context.sourceFile;
const token = getTokenAtPosition(sourceFile, context.span.start);
if (token.kind !== SyntaxKind.ConstructorKeyword) {
return undefined;
}
const newPosition = getOpenBraceEnd(<ConstructorDeclaration>token.parent, sourceFile);
return [{
description: getLocaleSpecificMessage(Diagnostics.Add_missing_super_call),
changes: [{ fileName: sourceFile.fileName, textChanges: [{ newText: "super();", span: { start: newPosition, length: 0 } }] }]
}];
}
});
}

View File

@ -1,3 +1,5 @@
///<reference path='superFixes.ts' />
///<reference path='interfaceFixes.ts' />
///<reference path='changeExtendsToImplementsFix.ts' />
/// <reference path="fixClassIncorrectlyImplementsInterface.ts" />
/// <reference path="fixClassDoesntImplementInheritedAbstractMember.ts" />
/// <reference path="fixClassSuperMustPrecedeThisAccess.ts" />
/// <reference path="fixConstructorForDerivedNeedSuperCall.ts" />
/// <reference path="fixExtendsInterfaceBecomesImplements.ts" />

View File

@ -1,228 +0,0 @@
/* @internal */
namespace ts.codefix {
registerCodeFix({
errorCodes: [Diagnostics.Class_0_incorrectly_implements_interface_1.code],
getCodeActions: (context: CodeFixContext) => {
const sourceFile = context.sourceFile;
const start = context.span.start;
const token = getTokenAtPosition(sourceFile, start);
const checker = context.program.getTypeChecker();
if (token.kind === SyntaxKind.Identifier && isClassLike(token.parent)) {
const classDeclaration = <ClassDeclaration>token.parent;
const startPos: number = classDeclaration.members.pos;
const classMembers = ts.map(getNamedClassMemberDeclarations(classDeclaration), member => member.name.getText());
const trackingAddedMembers: string[] = [];
const interfaceClauses = ts.getClassImplementsHeritageClauseElements(classDeclaration);
let textChanges: TextChange[] = undefined;
for (let i = 0; interfaceClauses && i < interfaceClauses.length; i++) {
let newChanges = getChanges(interfaceClauses[i], classMembers, startPos, checker, /*reference*/ false, trackingAddedMembers, context.newLineCharacter);
textChanges = textChanges ? textChanges.concat(newChanges) : newChanges;
}
if (textChanges && textChanges.length > 0) {
return [{
description: getLocaleSpecificMessage(Diagnostics.Implement_interface_on_class),
changes: [{
fileName: sourceFile.fileName,
textChanges: textChanges
}]
}];
}
}
return undefined;
}
});
registerCodeFix({
errorCodes: [Diagnostics.Non_abstract_class_0_does_not_implement_inherited_abstract_member_1_from_class_2.code],
getCodeActions: (context: CodeFixContext) => {
const sourceFile = context.sourceFile;
const start = context.span.start;
const token = getTokenAtPosition(sourceFile, start);
const checker = context.program.getTypeChecker();
let textChanges: TextChange[] = [];
if (token.kind === SyntaxKind.Identifier && isClassLike(token.parent)) {
const classDeclaration = <ClassDeclaration>token.parent;
const startPos = classDeclaration.members.pos;
const abstractClassMembers = ts.map(getNamedClassAbstractMemberDeclarations(classDeclaration), member => member.name.getText());
const trackingAddedMembers: string[] = [];
const extendsClause = ts.getClassExtendsHeritageClauseElement(classDeclaration);
textChanges = textChanges.concat(getChanges(extendsClause, abstractClassMembers, startPos, checker, /*reference*/ false, trackingAddedMembers, context.newLineCharacter));
}
if (textChanges.length > 0) {
return [{
description: getLocaleSpecificMessage(Diagnostics.Implement_inherited_abstract_class),
changes: [{
fileName: sourceFile.fileName,
textChanges: textChanges
}]
}];
}
return undefined;
}
});
function getChanges(interfaceClause: Node, existingMembers: string[], startPos: number, checker: TypeChecker, reference: boolean, trackingAddedMembers: string[], newLineCharacter: string): TextChange[] {
const type = checker.getTypeAtLocation(interfaceClause);
const changesArray: TextChange[] = [];
if (type && type.symbol && type.symbol.declarations) {
const interfaceMembers = getMembers(<InterfaceDeclaration>type.symbol.declarations[0], checker);
for(let interfaceMember of interfaceMembers){
if (interfaceMember.name && existingMembers.indexOf(interfaceMember.name.getText()) === -1) {
if (interfaceMember.kind === SyntaxKind.PropertySignature) {
const interfaceProperty = <PropertySignature>interfaceMember;
if (trackingAddedMembers.indexOf(interfaceProperty.name.getText()) === -1) {
let propertyText = "";
if (reference) {
propertyText = `${interfaceProperty.name.getText()} : ${getDefaultValue(interfaceProperty.type.kind)},${newLineCharacter}`;
}
else {
propertyText = interfaceProperty.getText();
const stringToAdd = !(propertyText.match(/;$/)) ? `;${newLineCharacter}` : newLineCharacter;
propertyText += stringToAdd;
}
changesArray.push({ newText: propertyText, span: { start: startPos, length: 0 } });
trackingAddedMembers.push(interfaceProperty.name.getText());
}
}
else if (interfaceMember.kind === SyntaxKind.MethodSignature || interfaceMember.kind === SyntaxKind.MethodDeclaration) {
const interfaceMethod = <MethodSignature>interfaceMember;
handleMethods(interfaceMethod, startPos, reference, trackingAddedMembers, changesArray, newLineCharacter);
}
}
}
}
if (reference && existingMembers.length === 0 && changesArray.length > 0) {
let lastValue = changesArray[changesArray.length - 1].newText;
lastValue = `${lastValue.substr(0, lastValue.length - (newLineCharacter.length + 1))} ${newLineCharacter}`;
changesArray[changesArray.length - 1].newText = lastValue;
}
return changesArray;
}
function getMembers(declaration: InterfaceDeclaration, checker: TypeChecker): TypeElement[] {
const clauses = getInterfaceBaseTypeNodes(declaration);
let result: TypeElement[] = [];
for (let i = 0; clauses && i < clauses.length; i++) {
const type = checker.getTypeAtLocation(clauses[i]);
if (type && type.symbol && type.symbol.declarations) {
result = result.concat(getMembers(<InterfaceDeclaration>type.symbol.declarations[0], checker));
}
}
if (declaration.members) {
result = result.concat(declaration.members);
}
return result;
}
function getNamedClassMemberDeclarations(classDeclaration: ClassDeclaration): ClassElement[] {
return classDeclaration.members.filter(member => member.name);
}
function getNamedClassAbstractMemberDeclarations(classDeclaration: ClassDeclaration): ClassElement[] {
return getNamedClassMemberDeclarations(classDeclaration).filter(member => getModifierFlags(member) & ModifierFlags.Abstract);
}
/*
function getMembersAndStartPosFromReference(variableDeclaration: VariableDeclaration): { startPos: number, members: string[] } {
const children = variableDeclaration.getChildren();
const variableMembers: string[] = [];
let startPos = 0;
ts.forEach(children, child => {
if (child.kind === SyntaxKind.ObjectLiteralExpression) {
const properties = (<ObjectLiteralExpression>child).properties;
if (properties) {
startPos = properties.pos;
}
for (let j = 0; properties && j < properties.length; j++) {
if (properties[j].name) {
variableMembers.push(properties[j].name.getText());
}
}
}
});
return { startPos: startPos, members: variableMembers };
}
*/
function getDefaultValue(kind: SyntaxKind): string {
switch (kind) {
case SyntaxKind.StringKeyword:
return '""';
case SyntaxKind.BooleanKeyword:
return "false";
case SyntaxKind.NumberKeyword:
return "0";
default:
return "null";
}
}
function handleMethods(interfaceMethod: MethodSignature, startPos: number, isReference: boolean, trackingAddedMembers: string[], textChanges: TextChange[], newLineCharacter: string) {
const methodBody = "throw new Error('Method not Implemented');";
if (trackingAddedMembers.indexOf(interfaceMethod.name.getText())) {
const methodName = interfaceMethod.name.getText();
const typeParameterArray: string[] = [];
for (let i = 0; interfaceMethod.typeParameters && i < interfaceMethod.typeParameters.length; i++) {
typeParameterArray.push(interfaceMethod.typeParameters[i].getText());
}
const parameterArray: string[] = [];
for (let j = 0; interfaceMethod.parameters && j < interfaceMethod.parameters.length; j++) {
parameterArray.push(interfaceMethod.parameters[j].getText());
}
let methodText = methodName;
if (typeParameterArray.length > 0) {
methodText += "<";
}
for (let k = 0; k < typeParameterArray.length; k++) {
methodText += typeParameterArray[k];
if (k !== typeParameterArray.length - 1) {
methodText += ",";
}
}
if (typeParameterArray.length > 0) {
methodText += ">";
}
methodText += "(";
for (let k = 0; k < parameterArray.length; k++) {
methodText += parameterArray[k];
if (k !== parameterArray.length - 1) {
methodText += ",";
}
}
methodText += `)`;
if (interfaceMethod.type) {
methodText += ":" + interfaceMethod.type.getText();
}
methodText += `{${newLineCharacter}${methodBody}${newLineCharacter}`;
methodText = isReference ? methodText.concat(`},${newLineCharacter}`) : methodText.concat(`}${newLineCharacter}`);
textChanges.push({ newText: methodText, span: { start: startPos, length: 0 } });
trackingAddedMembers.push(interfaceMethod.name.getText());
}
}
}

View File

@ -85,6 +85,11 @@
"formatting/smartIndenter.ts",
"formatting/tokenRange.ts",
"codeFixes/codeFixProvider.ts",
"codeFixes/fixes.ts"
"codeFixes/fixes.ts",
"codeFixes/fixExtendsInterfaceBecomesImplements.ts",
"codeFixes/fixClassIncorrectlyImplementsInterface.ts",
"codeFixes/fixClassDoesntImplementInheritedAbstractMember.ts",
"codeFixes/fixClassSuperMustPrecedeThisAccess.ts",
"codeFixes/fixConstructorForDerivedNeedSuperCall.ts"
]
}

View File

@ -1358,4 +1358,165 @@ namespace ts {
diagnostics: error ? concatenate(diagnostics, [error]) : diagnostics
};
}
export function getCodeFixChanges(interfaceClause: Node, existingMembers: string[], startPos: number, checker: TypeChecker, reference: boolean, trackingAddedMembers: string[], newLineCharacter: string): TextChange[] {
const type = checker.getTypeAtLocation(interfaceClause);
const changesArray: TextChange[] = [];
if (type && type.symbol && type.symbol.declarations) {
const interfaceMembers = getInterfaceMembers(<InterfaceDeclaration>type.symbol.declarations[0], checker);
for(let interfaceMember of interfaceMembers){
if (interfaceMember.name && existingMembers.indexOf(interfaceMember.name.getText()) === -1) {
if (interfaceMember.kind === SyntaxKind.PropertySignature) {
const interfaceProperty = <PropertySignature>interfaceMember;
if (trackingAddedMembers.indexOf(interfaceProperty.name.getText()) === -1) {
let propertyText = "";
if (reference) {
propertyText = `${interfaceProperty.name.getText()} : ${getDefaultValue(interfaceProperty.type.kind)},${newLineCharacter}`;
}
else {
propertyText = interfaceProperty.getText();
const stringToAdd = !(propertyText.match(/;$/)) ? `;${newLineCharacter}` : newLineCharacter;
propertyText += stringToAdd;
}
changesArray.push({ newText: propertyText, span: { start: startPos, length: 0 } });
trackingAddedMembers.push(interfaceProperty.name.getText());
}
}
else if (interfaceMember.kind === SyntaxKind.MethodSignature || interfaceMember.kind === SyntaxKind.MethodDeclaration) {
const interfaceMethod = <MethodSignature>interfaceMember;
handleMethods(interfaceMethod, startPos, reference, trackingAddedMembers, changesArray, newLineCharacter);
}
}
}
}
if (reference && existingMembers.length === 0 && changesArray.length > 0) {
let lastValue = changesArray[changesArray.length - 1].newText;
lastValue = `${lastValue.substr(0, lastValue.length - (newLineCharacter.length + 1))} ${newLineCharacter}`;
changesArray[changesArray.length - 1].newText = lastValue;
}
return changesArray;
}
function getInterfaceMembers(declaration: InterfaceDeclaration, checker: TypeChecker): TypeElement[] {
const clauses = getInterfaceBaseTypeNodes(declaration);
let result: TypeElement[] = [];
for (let i = 0; clauses && i < clauses.length; i++) {
const type = checker.getTypeAtLocation(clauses[i]);
if (type && type.symbol && type.symbol.declarations) {
result = result.concat(getInterfaceMembers(<InterfaceDeclaration>type.symbol.declarations[0], checker));
}
}
if (declaration.members) {
result = result.concat(declaration.members);
}
return result;
}
export function getNamedClassMembers(classDeclaration: ClassDeclaration): ClassElement[] {
return classDeclaration.members.filter(member => member.name);
}
export function getNamedAbstractClassMembers(classDeclaration: ClassDeclaration): ClassElement[] {
return getNamedClassMembers(classDeclaration).filter(member => getModifierFlags(member) & ModifierFlags.Abstract);
}
/*
function getMembersAndStartPosFromReference(variableDeclaration: VariableDeclaration): { startPos: number, members: string[] } {
const children = variableDeclaration.getChildren();
const variableMembers: string[] = [];
let startPos = 0;
ts.forEach(children, child => {
if (child.kind === SyntaxKind.ObjectLiteralExpression) {
const properties = (<ObjectLiteralExpression>child).properties;
if (properties) {
startPos = properties.pos;
}
for (let j = 0; properties && j < properties.length; j++) {
if (properties[j].name) {
variableMembers.push(properties[j].name.getText());
}
}
}
});
return { startPos: startPos, members: variableMembers };
}
*/
function getDefaultValue(kind: SyntaxKind): string {
switch (kind) {
case SyntaxKind.StringKeyword:
return '""';
case SyntaxKind.BooleanKeyword:
return "false";
case SyntaxKind.NumberKeyword:
return "0";
default:
return "null";
}
}
function handleMethods(interfaceMethod: MethodSignature, startPos: number, isReference: boolean, trackingAddedMembers: string[], textChanges: TextChange[], newLineCharacter: string) {
const methodBody = "throw new Error('Method not Implemented');";
if (trackingAddedMembers.indexOf(interfaceMethod.name.getText())) {
const methodName = interfaceMethod.name.getText();
const typeParameterArray: string[] = [];
for (let i = 0; interfaceMethod.typeParameters && i < interfaceMethod.typeParameters.length; i++) {
typeParameterArray.push(interfaceMethod.typeParameters[i].getText());
}
const parameterArray: string[] = [];
for (let j = 0; interfaceMethod.parameters && j < interfaceMethod.parameters.length; j++) {
parameterArray.push(interfaceMethod.parameters[j].getText());
}
let methodText = methodName;
if (typeParameterArray.length > 0) {
methodText += "<";
}
for (let k = 0; k < typeParameterArray.length; k++) {
methodText += typeParameterArray[k];
if (k !== typeParameterArray.length - 1) {
methodText += ",";
}
}
if (typeParameterArray.length > 0) {
methodText += ">";
}
methodText += "(";
for (let k = 0; k < parameterArray.length; k++) {
methodText += parameterArray[k];
if (k !== parameterArray.length - 1) {
methodText += ",";
}
}
methodText += `)`;
if (interfaceMethod.type) {
methodText += ":" + interfaceMethod.type.getText();
}
methodText += `{${newLineCharacter}${methodBody}${newLineCharacter}`;
methodText = isReference ? methodText.concat(`},${newLineCharacter}`) : methodText.concat(`}${newLineCharacter}`);
textChanges.push({ newText: methodText, span: { start: startPos, length: 0 } });
trackingAddedMembers.push(interfaceMethod.name.getText());
}
}
export function getOpenBraceEnd(constructor: ConstructorDeclaration, sourceFile: SourceFile) {
// First token is the open curly, this is where we want to put the 'super' call.
return constructor.body.getFirstToken(sourceFile).getEnd();
}
}