From 3de9fe682e7a70fb06187ed90b67629cda8bd0be Mon Sep 17 00:00:00 2001 From: Andy Hanson Date: Mon, 4 Dec 2017 09:15:54 -0800 Subject: [PATCH 1/2] Add textChanges methods to insert nodes at the start of multiline bodies --- src/harness/fourslash.ts | 2 +- src/services/codefixes/fixAddMissingMember.ts | 7 +++-- .../fixConstructorForDerivedNeedSuperCall.ts | 2 +- src/services/textChanges.ts | 26 +++++++++++++++++-- tests/cases/fourslash/codeFixSuperCall.ts | 18 +++++++++---- 5 files changed, 42 insertions(+), 13 deletions(-) diff --git a/src/harness/fourslash.ts b/src/harness/fourslash.ts index 2d1d225f8a230..3532a88cdc28f 100644 --- a/src/harness/fourslash.ts +++ b/src/harness/fourslash.ts @@ -2472,7 +2472,7 @@ Actual: ${stringify(fullActual)}`); } private verifyNewContent(options: FourSlashInterface.NewContentOptions) { - if (options.newFileContent) { + if (options.newFileContent !== undefined) { assert(!options.newRangeContent); this.verifyCurrentFileContent(options.newFileContent); } diff --git a/src/services/codefixes/fixAddMissingMember.ts b/src/services/codefixes/fixAddMissingMember.ts index 19bd592b7a7d6..1e18267929fe0 100644 --- a/src/services/codefixes/fixAddMissingMember.ts +++ b/src/services/codefixes/fixAddMissingMember.ts @@ -62,7 +62,6 @@ namespace ts.codefix { } const classDeclarationSourceFile = getSourceFileOfNode(classDeclaration); - const classOpenBrace = getOpenBraceOfClassLike(classDeclaration, classDeclarationSourceFile); return isInJavaScriptFile(classDeclarationSourceFile) ? getActionsForAddMissingMemberInJavaScriptFile(classDeclaration, makeStatic) : @@ -154,7 +153,7 @@ namespace ts.codefix { typeNode, /*initializer*/ undefined); const propertyChangeTracker = textChanges.ChangeTracker.fromContext(context); - propertyChangeTracker.insertNodeAfter(classDeclarationSourceFile, classOpenBrace, property, { suffix: context.newLineCharacter }); + propertyChangeTracker.insertNodeAtClassStart(classDeclarationSourceFile, classDeclaration, property, context.newLineCharacter); const diag = makeStatic ? Diagnostics.Declare_static_property_0 : Diagnostics.Declare_property_0; actions = append(actions, { @@ -180,7 +179,7 @@ namespace ts.codefix { typeNode); const indexSignatureChangeTracker = textChanges.ChangeTracker.fromContext(context); - indexSignatureChangeTracker.insertNodeAfter(classDeclarationSourceFile, classOpenBrace, indexSignature, { suffix: context.newLineCharacter }); + indexSignatureChangeTracker.insertNodeAtClassStart(classDeclarationSourceFile, classDeclaration, indexSignature, context.newLineCharacter); actions.push({ description: formatStringFromArgs(getLocaleSpecificMessage(Diagnostics.Add_index_signature_for_property_0), [tokenName]), @@ -197,7 +196,7 @@ namespace ts.codefix { const methodDeclaration = createMethodFromCallExpression(callExpression, tokenName, includeTypeScriptSyntax, makeStatic); const methodDeclarationChangeTracker = textChanges.ChangeTracker.fromContext(context); - methodDeclarationChangeTracker.insertNodeAfter(classDeclarationSourceFile, classOpenBrace, methodDeclaration, { suffix: context.newLineCharacter }); + methodDeclarationChangeTracker.insertNodeAtClassStart(classDeclarationSourceFile, classDeclaration, methodDeclaration, context.newLineCharacter); const diag = makeStatic ? Diagnostics.Declare_static_method_0 : Diagnostics.Declare_method_0; return { description: formatStringFromArgs(getLocaleSpecificMessage(diag), [tokenName]), diff --git a/src/services/codefixes/fixConstructorForDerivedNeedSuperCall.ts b/src/services/codefixes/fixConstructorForDerivedNeedSuperCall.ts index 24f44a877b34e..d61adfee470e0 100644 --- a/src/services/codefixes/fixConstructorForDerivedNeedSuperCall.ts +++ b/src/services/codefixes/fixConstructorForDerivedNeedSuperCall.ts @@ -12,7 +12,7 @@ namespace ts.codefix { const changeTracker = textChanges.ChangeTracker.fromContext(context); const superCall = createStatement(createCall(createSuper(), /*typeArguments*/ undefined, /*argumentsArray*/ emptyArray)); - changeTracker.insertNodeAfter(sourceFile, getOpenBrace(token.parent, sourceFile), superCall, { suffix: context.newLineCharacter }); + changeTracker.insertNodeAtConstructorStart(sourceFile, token.parent, superCall, context.newLineCharacter); return [{ description: getLocaleSpecificMessage(Diagnostics.Add_missing_super_call), diff --git a/src/services/textChanges.ts b/src/services/textChanges.ts index 7c4e25537efbe..373d4a7846a9f 100644 --- a/src/services/textChanges.ts +++ b/src/services/textChanges.ts @@ -337,8 +337,30 @@ namespace ts.textChanges { return this.replaceWithSingle(sourceFile, startPosition, startPosition, newNode, options); } - public insertNodeAfter(sourceFile: SourceFile, after: Node, newNode: Node, options: InsertNodeOptions & ConfigurableEnd = {}) { - if ((isStatementButNotDeclaration(after)) || + public insertNodeAtConstructorStart(sourceFile: SourceFile, ctr: ConstructorDeclaration, newStatement: Statement, newLineCharacter: string): void { + if (ctr.body.statements.length === 0) { + this.replaceNode(sourceFile, ctr.body, createBlock([newStatement], /*multiLine*/ true), { useNonAdjustedEndPosition: true }); + } + else { + this.insertNodeAfter(sourceFile, getOpenBrace(ctr, sourceFile), newStatement, { suffix: newLineCharacter }); + } + } + + public insertNodeAtClassStart(sourceFile: SourceFile, cls: ClassLikeDeclaration, newElement: ClassElement, newLineCharacter: string): void { + if (cls.members.length === 0) { + const members = [newElement]; + const newCls = cls.kind === SyntaxKind.ClassDeclaration + ? updateClassDeclaration(cls, cls.decorators, cls.modifiers, cls.name, cls.typeParameters, cls.heritageClauses, members) + : updateClassExpression(cls, cls.modifiers, cls.name, cls.typeParameters, cls.heritageClauses, members); + this.replaceNode(sourceFile, cls, newCls, { useNonAdjustedEndPosition: true }); + } + else { + this.insertNodeAfter(sourceFile, getOpenBraceOfClassLike(cls, sourceFile), newElement, { suffix: newLineCharacter }); + } + } + + public insertNodeAfter(sourceFile: SourceFile, after: Node, newNode: Node, options: InsertNodeOptions & ConfigurableEnd = {}): this { + if (isStatementButNotDeclaration(after) || after.kind === SyntaxKind.PropertyDeclaration || after.kind === SyntaxKind.PropertySignature || after.kind === SyntaxKind.MethodSignature) { diff --git a/tests/cases/fourslash/codeFixSuperCall.ts b/tests/cases/fourslash/codeFixSuperCall.ts index 28f7d34a2bd4c..402d419636064 100644 --- a/tests/cases/fourslash/codeFixSuperCall.ts +++ b/tests/cases/fourslash/codeFixSuperCall.ts @@ -3,10 +3,18 @@ ////class Base{ ////} ////class C extends Base{ -//// constructor() {[| -//// |]} +//// constructor() {} ////} -// TODO: GH#18445 -verify.rangeAfterCodeFix(` + +verify.codeFix({ + description: "Add missing 'super()' call", + // TODO: GH#18445 + newFileContent: +`class Base{ +} +class C extends Base{ + constructor() {\r super();\r - `, /*includeWhitespace*/ true); + } +}`, +}); From a5578f2bd72d828182836fd38e845179579c01a6 Mon Sep 17 00:00:00 2001 From: Andy Hanson Date: Mon, 4 Dec 2017 15:04:25 -0800 Subject: [PATCH 2/2] Replace constructor body if not already multiline --- src/services/textChanges.ts | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/services/textChanges.ts b/src/services/textChanges.ts index 373d4a7846a9f..94a928c9cf1fc 100644 --- a/src/services/textChanges.ts +++ b/src/services/textChanges.ts @@ -338,16 +338,18 @@ namespace ts.textChanges { } public insertNodeAtConstructorStart(sourceFile: SourceFile, ctr: ConstructorDeclaration, newStatement: Statement, newLineCharacter: string): void { - if (ctr.body.statements.length === 0) { - this.replaceNode(sourceFile, ctr.body, createBlock([newStatement], /*multiLine*/ true), { useNonAdjustedEndPosition: true }); + const firstStatement = firstOrUndefined(ctr.body.statements); + if (!firstStatement || !ctr.body.multiLine) { + this.replaceNode(sourceFile, ctr.body, createBlock([newStatement, ...ctr.body.statements], /*multiLine*/ true), { useNonAdjustedEndPosition: true }); } else { - this.insertNodeAfter(sourceFile, getOpenBrace(ctr, sourceFile), newStatement, { suffix: newLineCharacter }); + this.insertNodeBefore(sourceFile, firstStatement, newStatement, { suffix: newLineCharacter }); } } public insertNodeAtClassStart(sourceFile: SourceFile, cls: ClassLikeDeclaration, newElement: ClassElement, newLineCharacter: string): void { - if (cls.members.length === 0) { + const firstMember = firstOrUndefined(cls.members); + if (!firstMember) { const members = [newElement]; const newCls = cls.kind === SyntaxKind.ClassDeclaration ? updateClassDeclaration(cls, cls.decorators, cls.modifiers, cls.name, cls.typeParameters, cls.heritageClauses, members) @@ -355,7 +357,7 @@ namespace ts.textChanges { this.replaceNode(sourceFile, cls, newCls, { useNonAdjustedEndPosition: true }); } else { - this.insertNodeAfter(sourceFile, getOpenBraceOfClassLike(cls, sourceFile), newElement, { suffix: newLineCharacter }); + this.insertNodeBefore(sourceFile, firstMember, newElement, { suffix: newLineCharacter }); } }