Skip to content

Commit 1438f9a

Browse files
author
Paul van Brenk
committed
Codefixes for implement interface and change extends to implements.
1 parent 7c96c28 commit 1438f9a

44 files changed

Lines changed: 934 additions & 1 deletion

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
/* @internal */
2+
namespace ts.codefix {
3+
registerCodeFix({
4+
errorCodes: [Diagnostics.Cannot_extend_an_interface_0_Did_you_mean_implements.code],
5+
getCodeActions: (context: CodeFixContext) => {
6+
const sourceFile = context.sourceFile;
7+
const start = context.span.start;
8+
const token = getTokenAtPosition(sourceFile, start);
9+
const textChanges: TextChange[] = [];
10+
11+
if (token.kind === SyntaxKind.Identifier && token.parent.parent.kind === SyntaxKind.HeritageClause) {
12+
const children = (<HeritageClause>token.parent.parent).getChildren();
13+
ts.forEach(children, child => {
14+
if (child.kind === SyntaxKind.ExtendsKeyword) {
15+
textChanges.push({ newText: " implements", span: { start: child.pos, length: child.end - child.pos } });
16+
}
17+
});
18+
}
19+
20+
if (textChanges.length > 0) {
21+
return [{
22+
description: getLocaleSpecificMessage(Diagnostics.Change_extends_to_implements),
23+
changes: [{
24+
fileName: sourceFile.fileName,
25+
textChanges: textChanges
26+
}]
27+
}];
28+
}
29+
30+
return undefined;
31+
}
32+
});
33+
}

src/services/codefixes/fixes.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,3 @@
1-
///<reference path='superFixes.ts' />
1+
///<reference path='superFixes.ts' />
2+
///<reference path='interfaceFixes.ts' />
3+
///<reference path='changeExtendsToImplementsFix.ts' />
Lines changed: 268 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,268 @@
1+
/* @internal */
2+
namespace ts.codefix {
3+
registerCodeFix({
4+
errorCodes: [Diagnostics.Type_0_is_not_assignable_to_type_1.code],
5+
getCodeActions: (context: CodeFixContext) => {
6+
const sourceFile = context.sourceFile;
7+
const start = context.span.start;
8+
const token = getTokenAtPosition(sourceFile, start);
9+
const checker = context.program.getTypeChecker();
10+
let textChanges: TextChange[] = [];
11+
12+
if (token.kind === SyntaxKind.Identifier && token.parent.kind === SyntaxKind.VariableDeclaration) {
13+
const variableDeclaration = <VariableDeclaration>token.parent;
14+
const membersAndStartPosObject = getMembersAndStartPosFromReference(variableDeclaration);
15+
const variableMembers = membersAndStartPosObject.members;
16+
const trackingAddedMembers: string[] = [];
17+
const startPos: number = membersAndStartPosObject.startPos;
18+
19+
if (variableDeclaration.type.kind === SyntaxKind.TypeReference) {
20+
textChanges = textChanges.concat(getChanges(variableDeclaration.type, variableMembers, startPos, checker, /*reference*/ true, trackingAddedMembers, context.newLineCharacter));
21+
}
22+
else if (variableDeclaration.type.kind === SyntaxKind.UnionType) {
23+
const types = (<UnionTypeNode>variableDeclaration.type).types;
24+
ts.forEach(types, t => {
25+
textChanges = textChanges.concat(getChanges(t, variableMembers, startPos, checker, /*reference*/ true, trackingAddedMembers, context.newLineCharacter));
26+
});
27+
}
28+
}
29+
30+
if (textChanges.length > 0) {
31+
return [{
32+
description: getLocaleSpecificMessage(Diagnostics.Implement_interface_on_reference),
33+
changes: [{
34+
fileName: sourceFile.fileName,
35+
textChanges: textChanges
36+
}]
37+
}];
38+
}
39+
40+
return undefined;
41+
}
42+
});
43+
44+
registerCodeFix({
45+
errorCodes: [Diagnostics.Class_0_incorrectly_implements_interface_1.code],
46+
getCodeActions: (context: CodeFixContext) => {
47+
const sourceFile = context.sourceFile;
48+
const start = context.span.start;
49+
const token = getTokenAtPosition(sourceFile, start);
50+
const checker = context.program.getTypeChecker();
51+
52+
let textChanges: TextChange[] = [];
53+
54+
if (token.kind === SyntaxKind.Identifier && token.parent.kind === SyntaxKind.ClassDeclaration) {
55+
const classDeclaration = <ClassDeclaration>token.parent;
56+
const startPos: number = classDeclaration.members.pos;
57+
const classMembers = getClassMembers(classDeclaration);
58+
const trackingAddedMembers: string[] = [];
59+
const interfaceClauses = ts.getClassImplementsHeritageClauseElements(classDeclaration);
60+
61+
for (let i = 0; interfaceClauses && i < interfaceClauses.length; i++) {
62+
textChanges = textChanges.concat(getChanges(interfaceClauses[i], classMembers, startPos, checker, /*reference*/ false, trackingAddedMembers, context.newLineCharacter));
63+
}
64+
}
65+
66+
if (textChanges.length > 0) {
67+
return [{
68+
description: getLocaleSpecificMessage(Diagnostics.Implement_interface_on_class),
69+
changes: [{
70+
fileName: sourceFile.fileName,
71+
textChanges: textChanges
72+
}]
73+
}];
74+
}
75+
76+
return undefined;
77+
}
78+
});
79+
80+
registerCodeFix({
81+
errorCodes: [Diagnostics.Non_abstract_class_0_does_not_implement_inherited_abstract_member_1_from_class_2.code],
82+
getCodeActions: (context: CodeFixContext) => {
83+
const sourceFile = context.sourceFile;
84+
const start = context.span.start;
85+
const token = getTokenAtPosition(sourceFile, start);
86+
const checker = context.program.getTypeChecker();
87+
88+
let textChanges: TextChange[] = [];
89+
90+
if (token.kind === SyntaxKind.Identifier && token.parent.kind === SyntaxKind.ClassDeclaration) {
91+
const classDeclaration = <ClassDeclaration>token.parent;
92+
const startPos = classDeclaration.members.pos;
93+
const classMembers = getClassMembers(classDeclaration);
94+
const trackingAddedMembers: string[] = [];
95+
const extendsClause = ts.getClassExtendsHeritageClauseElement(classDeclaration);
96+
textChanges = textChanges.concat(getChanges(extendsClause, classMembers, startPos, checker, /*reference*/ false, trackingAddedMembers, context.newLineCharacter));
97+
}
98+
99+
if (textChanges.length > 0) {
100+
return [{
101+
description: getLocaleSpecificMessage(Diagnostics.Implement_inherited_abstract_class),
102+
changes: [{
103+
fileName: sourceFile.fileName,
104+
textChanges: textChanges
105+
}]
106+
}];
107+
}
108+
109+
return undefined;
110+
}
111+
});
112+
113+
function getChanges(interfaceClause: Node, existingMembers: string[], startPos: number, checker: TypeChecker, reference: boolean, trackingAddedMembers: string[], newLineCharacter: string): TextChange[] {
114+
const type = checker.getTypeAtLocation(interfaceClause);
115+
const changesArray: TextChange[] = [];
116+
117+
if (type && type.symbol && type.symbol.declarations) {
118+
const interfaceMembers = getMembers(<InterfaceDeclaration>type.symbol.declarations[0], checker);
119+
for (let j = 0; interfaceMembers && j < interfaceMembers.length; j++) {
120+
if (interfaceMembers[j].name && existingMembers.indexOf(interfaceMembers[j].name.getText()) === -1) {
121+
if (interfaceMembers[j].kind === SyntaxKind.PropertySignature) {
122+
const interfaceProperty = <PropertySignature>interfaceMembers[j];
123+
if (trackingAddedMembers.indexOf(interfaceProperty.name.getText()) === -1) {
124+
let propertyText = "";
125+
if (reference) {
126+
propertyText = `${interfaceProperty.name.getText()} : ${getDefaultValue(interfaceProperty.type.kind)},${newLineCharacter}`;
127+
}
128+
else {
129+
propertyText = interfaceProperty.getText();
130+
const stringToAdd = !(propertyText.match(/;$/)) ? `;${newLineCharacter}` : newLineCharacter;
131+
propertyText += stringToAdd;
132+
}
133+
changesArray.push({ newText: propertyText, span: { start: startPos, length: 0 } });
134+
trackingAddedMembers.push(interfaceProperty.name.getText());
135+
}
136+
}
137+
else if (interfaceMembers[j].kind === SyntaxKind.MethodSignature || interfaceMembers[j].kind === SyntaxKind.MethodDeclaration) {
138+
const interfaceMethod = <MethodSignature>interfaceMembers[j];
139+
handleMethods(interfaceMethod, startPos, reference, trackingAddedMembers, changesArray, newLineCharacter);
140+
}
141+
}
142+
}
143+
}
144+
145+
if (reference && existingMembers.length === 0 && changesArray.length > 0) {
146+
let lastValue = changesArray[changesArray.length - 1].newText;
147+
lastValue = `${lastValue.substr(0, lastValue.length - (newLineCharacter.length + 1))} ${newLineCharacter}`;
148+
changesArray[changesArray.length - 1].newText = lastValue;
149+
}
150+
151+
return changesArray;
152+
}
153+
154+
function getMembers(declaration: InterfaceDeclaration, checker: TypeChecker): TypeElement[] {
155+
const clauses = getInterfaceBaseTypeNodes(declaration);
156+
let result: TypeElement[] = [];
157+
for (let i = 0; clauses && i < clauses.length; i++) {
158+
const type = checker.getTypeAtLocation(clauses[i]);
159+
if (type && type.symbol && type.symbol.declarations) {
160+
result = result.concat(getMembers(<InterfaceDeclaration>type.symbol.declarations[0], checker));
161+
}
162+
}
163+
164+
if (declaration.members) {
165+
result = result.concat(declaration.members);
166+
}
167+
168+
return result;
169+
}
170+
171+
function getClassMembers(classDeclaration: ClassDeclaration): string[] {
172+
const classMembers: string[] = [];
173+
for (let i = 0; classDeclaration.members && i < classDeclaration.members.length; i++) {
174+
if (classDeclaration.members[i].name) {
175+
classMembers.push(classDeclaration.members[i].name.getText());
176+
}
177+
}
178+
return classMembers;
179+
}
180+
181+
function getMembersAndStartPosFromReference(variableDeclaration: VariableDeclaration): { startPos: number, members: string[] } {
182+
const children = variableDeclaration.getChildren();
183+
const variableMembers: string[] = [];
184+
let startPos = 0;
185+
186+
ts.forEach(children, child => {
187+
if (child.kind === SyntaxKind.ObjectLiteralExpression) {
188+
const properties = (<ObjectLiteralExpression>child).properties;
189+
if (properties) {
190+
startPos = properties.pos;
191+
}
192+
for (let j = 0; properties && j < properties.length; j++) {
193+
if (properties[j].name) {
194+
variableMembers.push(properties[j].name.getText());
195+
}
196+
}
197+
}
198+
});
199+
200+
return { startPos: startPos, members: variableMembers };
201+
}
202+
203+
function getDefaultValue(kind: SyntaxKind): string {
204+
switch (kind) {
205+
case SyntaxKind.StringKeyword:
206+
return '""';
207+
case SyntaxKind.BooleanKeyword:
208+
return "false";
209+
case SyntaxKind.NumberKeyword:
210+
return "0";
211+
default:
212+
return "null";
213+
}
214+
}
215+
216+
function handleMethods(interfaceMethod: MethodSignature, startPos: number, isReference: boolean, trackingAddedMembers: string[], textChanges: TextChange[], newLineCharacter: string) {
217+
const methodBody = "throw new Error('Method not Implemented');";
218+
219+
if (trackingAddedMembers.indexOf(interfaceMethod.name.getText())) {
220+
const methodName = interfaceMethod.name.getText();
221+
const typeParameterArray: string[] = [];
222+
223+
for (let i = 0; interfaceMethod.typeParameters && i < interfaceMethod.typeParameters.length; i++) {
224+
typeParameterArray.push(interfaceMethod.typeParameters[i].getText());
225+
}
226+
227+
const parameterArray: string[] = [];
228+
for (let j = 0; interfaceMethod.parameters && j < interfaceMethod.parameters.length; j++) {
229+
parameterArray.push(interfaceMethod.parameters[j].getText());
230+
}
231+
232+
let methodText = methodName;
233+
if (typeParameterArray.length > 0) {
234+
methodText += "<";
235+
}
236+
237+
for (let k = 0; k < typeParameterArray.length; k++) {
238+
methodText += typeParameterArray[k];
239+
if (k !== typeParameterArray.length - 1) {
240+
methodText += ",";
241+
}
242+
}
243+
244+
if (typeParameterArray.length > 0) {
245+
methodText += ">";
246+
}
247+
248+
methodText += "(";
249+
for (let k = 0; k < parameterArray.length; k++) {
250+
methodText += parameterArray[k];
251+
if (k !== parameterArray.length - 1) {
252+
methodText += ",";
253+
}
254+
}
255+
256+
methodText += `)`;
257+
if (interfaceMethod.type) {
258+
methodText += ":" + interfaceMethod.type.getText();
259+
}
260+
261+
methodText += `{${newLineCharacter}${methodBody}${newLineCharacter}`;
262+
methodText = isReference ? methodText.concat(`},${newLineCharacter}`) : methodText.concat(`}${newLineCharacter}`);
263+
264+
textChanges.push({ newText: methodText, span: { start: startPos, length: 0 } });
265+
trackingAddedMembers.push(interfaceMethod.name.getText());
266+
}
267+
}
268+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
/// <reference path='fourslash.ts' />
2+
3+
//// interface I1 {}
4+
//// [|class c1 extends I1|]{}
5+
6+
verify.codeFixAtPosition("class c1 implements I1");
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
/// <reference path='fourslash.ts' />
2+
3+
////interface I1 {}
4+
////[|class c1<T extends string , U> extends I1|]{}
5+
6+
verify.codeFixAtPosition("class c1<T extends string , U> implements I1");
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
/// <reference path='fourslash.ts' />
2+
3+
//// namespace N1 {
4+
//// export interface I1 {
5+
//// f1();
6+
//// }
7+
//// }
8+
//// interface I1 {
9+
//// f1();
10+
//// }
11+
////
12+
//// class C1 implements N1.I1 {[|
13+
//// |]}
14+
15+
verify.codeFixAtPosition(`f1(){
16+
throw new Error('Method not Implemented');
17+
}
18+
`);
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
/// <reference path='fourslash.ts' />
2+
3+
//// interface I1 {
4+
//// f1()
5+
//// }
6+
////
7+
//// interface I2 extends I1 {
8+
////
9+
//// }
10+
////
11+
////
12+
//// class C1 implements I2 {[|
13+
//// |]}
14+
15+
verify.codeFixAtPosition(`f1(){
16+
throw new Error('Method not Implemented');
17+
}
18+
`);
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
/// <reference path='fourslash.ts' />
2+
3+
//// interface I1 {
4+
////
5+
//// }
6+
////
7+
//// interface I2 extends I1 {
8+
//// f1();
9+
//// }
10+
////
11+
//// interface I3 extends I2 {}
12+
////
13+
//// class C1 implements I3 {[|
14+
//// |]}
15+
16+
verify.codeFixAtPosition(`f1(){
17+
throw new Error('Method not Implemented');
18+
}
19+
`);

0 commit comments

Comments
 (0)