-
Notifications
You must be signed in to change notification settings - Fork 12.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fix insertNodeAtClassStart
for empty class with comment
#23342
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -208,8 +208,7 @@ namespace ts.textChanges { | |
export class ChangeTracker { | ||
private readonly changes: Change[] = []; | ||
private readonly deletedNodesInLists: true[] = []; // Stores ids of nodes in lists that we already deleted. Used to avoid deleting `, ` twice in `a, b`. | ||
// Map from class id to nodes to insert at the start | ||
private readonly nodesInsertedAtClassStarts = createMap<{ sourceFile: SourceFile, cls: ClassLikeDeclaration, members: ClassElement[] }>(); | ||
private readonly classesWithNodesInsertedAtStart = createMap<ClassDeclaration>(); // Set<ClassDeclaration> implemented as Map<node id, ClassDeclaration> | ||
|
||
public static fromContext(context: TextChangesContext): ChangeTracker { | ||
return new ChangeTracker(getNewLineOrDefaultFromHost(context.host, context.formatContext.options), context.formatContext); | ||
|
@@ -339,8 +338,7 @@ namespace ts.textChanges { | |
} | ||
|
||
public insertNodeBefore(sourceFile: SourceFile, before: Node, newNode: Node, blankLineBetween = false) { | ||
const pos = getAdjustedStartPosition(sourceFile, before, {}, Position.Start); | ||
return this.replaceRange(sourceFile, { pos, end: pos }, newNode, this.getOptionsForInsertNodeBefore(before, blankLineBetween)); | ||
this.insertNodeAt(sourceFile, getAdjustedStartPosition(sourceFile, before, {}, Position.Start), newNode, this.getOptionsForInsertNodeBefore(before, blankLineBetween)); | ||
} | ||
|
||
public insertModifierBefore(sourceFile: SourceFile, modifier: SyntaxKind, before: Node): void { | ||
|
@@ -435,21 +433,20 @@ namespace ts.textChanges { | |
} | ||
|
||
public insertNodeAtClassStart(sourceFile: SourceFile, cls: ClassLikeDeclaration, newElement: ClassElement): void { | ||
const firstMember = firstOrUndefined(cls.members); | ||
if (!firstMember) { | ||
const id = getNodeId(cls).toString(); | ||
const newMembers = this.nodesInsertedAtClassStarts.get(id); | ||
if (newMembers) { | ||
Debug.assert(newMembers.sourceFile === sourceFile && newMembers.cls === cls); | ||
newMembers.members.push(newElement); | ||
const clsStart = cls.getStart(sourceFile); | ||
let prefix = ""; | ||
let suffix = this.newLineCharacter; | ||
if (addToSeen(this.classesWithNodesInsertedAtStart, getNodeId(cls), cls)) { | ||
prefix = this.newLineCharacter; | ||
// For `class C {\n}`, don't add the trailing "\n" | ||
if (cls.members.length === 0 && !(positionsAreOnSameLine as any)(...getClassBraceEnds(cls, sourceFile), sourceFile)) { // TODO: GH#4130 remove 'as any' | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Does the number of members matter? It seems like you want to omit the linebreak as long as the class is on a single line. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's the opposite -- if the class is on a single line I want to add line breaks on both sides to get |
||
suffix = ""; | ||
} | ||
else { | ||
this.nodesInsertedAtClassStarts.set(id, { sourceFile, cls, members: [newElement] }); | ||
} | ||
} | ||
else { | ||
this.insertNodeBefore(sourceFile, firstMember, newElement); | ||
} | ||
|
||
const indentation = formatting.SmartIndenter.findFirstNonWhitespaceColumn(getLineStartPositionForPosition(clsStart, sourceFile), clsStart, sourceFile, this.formatContext.options) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should the indentation be omitted if the class is on a single line? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's about to be multiline since we're adding a member, and that new member should be indented. |
||
+ this.formatContext.options.indentSize; | ||
this.insertNodeAt(sourceFile, cls.members.pos, newElement, { indentation, prefix, suffix }); | ||
} | ||
|
||
public insertNodeAfter(sourceFile: SourceFile, after: Node, newNode: Node): this { | ||
|
@@ -601,12 +598,14 @@ namespace ts.textChanges { | |
return this; | ||
} | ||
|
||
private finishInsertNodeAtClassStart(): void { | ||
this.nodesInsertedAtClassStarts.forEach(({ sourceFile, cls, members }) => { | ||
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); | ||
private finishClassesWithNodesInsertedAtStart(): void { | ||
this.classesWithNodesInsertedAtStart.forEach(cls => { | ||
const sourceFile = cls.getSourceFile(); | ||
const [openBraceEnd, closeBraceEnd] = getClassBraceEnds(cls, sourceFile); | ||
// For `class C { }` remove the whitespace inside the braces. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Leaving the space would result in the class ending in |
||
if (positionsAreOnSameLine(openBraceEnd, closeBraceEnd, sourceFile) && openBraceEnd !== closeBraceEnd - 1) { | ||
this.deleteRange(sourceFile, createTextRange(openBraceEnd, closeBraceEnd - 1)); | ||
} | ||
}); | ||
} | ||
|
||
|
@@ -617,11 +616,15 @@ namespace ts.textChanges { | |
* so we can only call this once and can't get the non-formatted text separately. | ||
*/ | ||
public getChanges(validate?: ValidateNonFormattedText): FileTextChanges[] { | ||
this.finishInsertNodeAtClassStart(); | ||
this.finishClassesWithNodesInsertedAtStart(); | ||
return changesToText.getTextChangesFromChanges(this.changes, this.newLineCharacter, this.formatContext, validate); | ||
} | ||
} | ||
|
||
function getClassBraceEnds(cls: ClassLikeDeclaration, sourceFile: SourceFile): [number, number] { | ||
return [findChildOfKind(cls, SyntaxKind.OpenBraceToken, sourceFile).end, findChildOfKind(cls, SyntaxKind.CloseBraceToken, sourceFile).end]; | ||
} | ||
|
||
export type ValidateNonFormattedText = (node: Node, text: string) => void; | ||
|
||
namespace changesToText { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -11,6 +11,7 @@ verify.codeFix({ | |
index: 0, | ||
newFileContent: `class C { | ||
foo: number; | ||
|
||
method() { | ||
this.foo = 10; | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
/// <reference path='fourslash.ts' /> | ||
|
||
////abstract class A { | ||
//// abstract m() : void; | ||
////} | ||
//// | ||
////class B extends A { | ||
//// // comment | ||
////} | ||
|
||
verify.codeFix({ | ||
description: "Implement inherited abstract class", | ||
newFileContent: | ||
`abstract class A { | ||
abstract m() : void; | ||
} | ||
|
||
class B extends A { | ||
m(): void { | ||
throw new Error("Method not implemented."); | ||
} | ||
// comment | ||
}`, | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -17,6 +17,7 @@ edit.applyRefactor({ | |
public set a(value: string) { | ||
this._a = value; | ||
} | ||
|
||
constructor() { } | ||
}`, | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For my own edification, why not use
{}
as the default value?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've been using
Map<true>
everywhere for sets; to meMap<{}>
would imply actually being a map fromstring
to something, where we didn't specify that something.