diff --git a/src/services/codefixes/convertFunctionToEs6Class.ts b/src/services/codefixes/convertFunctionToEs6Class.ts index 0ca2135307f95..e20d9eb68120d 100644 --- a/src/services/codefixes/convertFunctionToEs6Class.ts +++ b/src/services/codefixes/convertFunctionToEs6Class.ts @@ -14,48 +14,31 @@ namespace ts.codefix { function doChange(changes: textChanges.ChangeTracker, sourceFile: SourceFile, position: number, checker: TypeChecker): void { const ctorSymbol = checker.getSymbolAtLocation(getTokenAtPosition(sourceFile, position))!; - if (!ctorSymbol || !(ctorSymbol.flags & (SymbolFlags.Function | SymbolFlags.Variable))) { // Bad input return undefined; } const ctorDeclaration = ctorSymbol.valueDeclaration; - - let precedingNode: Node | undefined; - let newClassDeclaration: ClassDeclaration | undefined; - switch (ctorDeclaration.kind) { - case SyntaxKind.FunctionDeclaration: - precedingNode = ctorDeclaration; - changes.delete(sourceFile, ctorDeclaration); - newClassDeclaration = createClassFromFunctionDeclaration(ctorDeclaration as FunctionDeclaration); - break; - - case SyntaxKind.VariableDeclaration: - precedingNode = ctorDeclaration.parent.parent; - newClassDeclaration = createClassFromVariableDeclaration(ctorDeclaration as VariableDeclaration); - if ((ctorDeclaration.parent).declarations.length === 1) { - copyLeadingComments(precedingNode, newClassDeclaration!, sourceFile); // TODO: GH#18217 - changes.delete(sourceFile, precedingNode); - } - else { - changes.delete(sourceFile, ctorDeclaration); - } - break; - } - - if (!newClassDeclaration) { - return undefined; + if (isFunctionDeclaration(ctorDeclaration)) { + changes.replaceNode(sourceFile, ctorDeclaration, createClassFromFunctionDeclaration(ctorDeclaration)); } + else if (isVariableDeclaration(ctorDeclaration)) { + const classDeclaration = createClassFromVariableDeclaration(ctorDeclaration); + if (!classDeclaration) { + return undefined; + } - // Deleting a declaration only deletes JSDoc style comments, so only copy those to the new node. - if (hasJSDocNodes(ctorDeclaration)) { - copyLeadingComments(ctorDeclaration, newClassDeclaration, sourceFile); + const ancestor = ctorDeclaration.parent.parent; + if (isVariableDeclarationList(ctorDeclaration.parent) && ctorDeclaration.parent.declarations.length > 1) { + changes.delete(sourceFile, ctorDeclaration); + changes.insertNodeAfter(sourceFile, ancestor, classDeclaration); + } + else { + changes.replaceNode(sourceFile, ancestor, classDeclaration); + } } - // Because the preceding node could be touched, we need to insert nodes before delete nodes. - changes.insertNodeAfter(sourceFile, precedingNode!, newClassDeclaration); - function createClassElementsFromSymbol(symbol: Symbol) { const memberElements: ClassElement[] = []; // all instance members are stored in the "member" array of symbol @@ -220,12 +203,8 @@ namespace ts.codefix { } function createClassFromVariableDeclaration(node: VariableDeclaration): ClassDeclaration | undefined { - const initializer = node.initializer as FunctionExpression; - if (!initializer || initializer.kind !== SyntaxKind.FunctionExpression) { - return undefined; - } - - if (node.name.kind !== SyntaxKind.Identifier) { + const initializer = node.initializer; + if (!initializer || !isFunctionExpression(initializer) || !isIdentifier(node.name)) { return undefined; } @@ -234,7 +213,7 @@ namespace ts.codefix { memberElements.unshift(factory.createConstructorDeclaration(/*decorators*/ undefined, /*modifiers*/ undefined, initializer.parameters, initializer.body)); } - const modifiers = getModifierKindFromSource(precedingNode!, SyntaxKind.ExportKeyword); + const modifiers = getModifierKindFromSource(node.parent.parent, SyntaxKind.ExportKeyword); const cls = factory.createClassDeclaration(/*decorators*/ undefined, modifiers, node.name, /*typeParameters*/ undefined, /*heritageClauses*/ undefined, memberElements); // Don't call copyComments here because we'll already leave them in place diff --git a/tests/cases/fourslash/convertFunctionToEs6Class_commentOnVariableDeclaration.ts b/tests/cases/fourslash/convertFunctionToEs6Class_commentOnVariableDeclaration.ts index 07ca6ea70d65b..6de9ff55ada5a 100644 --- a/tests/cases/fourslash/convertFunctionToEs6Class_commentOnVariableDeclaration.ts +++ b/tests/cases/fourslash/convertFunctionToEs6Class_commentOnVariableDeclaration.ts @@ -8,10 +8,8 @@ verify.codeFix({ description: "Convert function to an ES2015 class", newFileContent: -` -/** Doc */ +`/** Doc */ class C { constructor() { this.x = 0; } -} -`, +}`, }); diff --git a/tests/cases/fourslash/server/convertFunctionToEs6Class-server1.ts b/tests/cases/fourslash/server/convertFunctionToEs6Class-server1.ts index 316572063a5ea..7e34b109f5ccc 100644 --- a/tests/cases/fourslash/server/convertFunctionToEs6Class-server1.ts +++ b/tests/cases/fourslash/server/convertFunctionToEs6Class-server1.ts @@ -22,6 +22,6 @@ class fn {\r bar() {\r console.log('hello world');\r }\r -}\r +} `, }); diff --git a/tests/cases/fourslash/server/convertFunctionToEs6Class-server2.ts b/tests/cases/fourslash/server/convertFunctionToEs6Class-server2.ts index 2f9c1a88bdf7c..470325c5ae7c7 100644 --- a/tests/cases/fourslash/server/convertFunctionToEs6Class-server2.ts +++ b/tests/cases/fourslash/server/convertFunctionToEs6Class-server2.ts @@ -16,9 +16,9 @@ verify.codeFix({ description: "Convert function to an ES2015 class", newFileContent: -`/**\r - * JSDoc Comment\r - */\r +`/** + * JSDoc Comment + */ class fn {\r constructor() {\r this.baz = 10;\r @@ -26,6 +26,6 @@ class fn {\r bar() {\r console.log('hello world');\r }\r -}\r +} `, });