Skip to content

Commit d46cbd0

Browse files
committed
fix: #219 - Removing the last statement in a default or case clause will cause a double newline.
1 parent e9caf14 commit d46cbd0

File tree

9 files changed

+59
-18
lines changed

9 files changed

+59
-18
lines changed

src/compiler/base/ModifierableNode.ts

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import {ts, SyntaxKind} from "../../typescript";
22
import {Constructor} from "../../Constructor";
33
import * as errors from "../../errors";
4-
import {insertIntoCreatableSyntaxList, removeChildrenWithFormattingFromCollapsibleSyntaxList, FormattingKind} from "../../manipulation";
4+
import {insertIntoCreatableSyntaxList, removeChildren, FormattingKind} from "../../manipulation";
55
import {ArrayUtils, getSyntaxKindName} from "../../utils";
66
import {Node} from "../common";
77
import {KindToNodeMappings} from "../kindToNodeMappings";
@@ -160,14 +160,16 @@ export function ModifierableNode<T extends Constructor<ModiferableNodeExtensionT
160160
}
161161

162162
removeModifier(text: ModifierTexts) {
163-
const modifier = ArrayUtils.find(this.getModifiers(), m => m.getText() === text);
163+
const modifiers = this.getModifiers();
164+
const modifier = ArrayUtils.find(modifiers, m => m.getText() === text);
164165
if (modifier == null)
165166
return false;
166167

167-
removeChildrenWithFormattingFromCollapsibleSyntaxList({
168-
children: [modifier],
169-
getSiblingFormatting: () => FormattingKind.Space
168+
removeChildren({
169+
children: [modifiers.length === 1 ? modifier.getParentSyntaxListOrThrow() : modifier],
170+
removeFollowingSpaces: true
170171
});
172+
171173
return true;
172174
}
173175

src/manipulation/textChecks/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
export * from "./isBlankLineAtPos";
2+
export * from "./isNewLineAtPos";
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export function isNewLineAtPos(fullText: string, pos: number) {
2+
return fullText[pos] === "\n" || (fullText[pos] === "\r" && fullText[pos + 1] === "\n");
3+
}

src/manipulation/textManipulators/RemoveChildrenWithFormattingTextManipulator.ts

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import {TextManipulator} from "./TextManipulator";
22
import {Node} from "../../compiler";
33
import {FormattingKind, getFormattingKindText} from "../formatting";
4-
import {getPosAtNextNonBlankLine, getNextMatchingPos, getPosAfterPreviousNonBlankLine} from "../textSeek";
4+
import {getPosAtNextNonBlankLine, getNextMatchingPos, getPosAfterPreviousNonBlankLine, getPosAtEndOfPreviousLine} from "../textSeek";
5+
import {isNewLineAtPos} from "../textChecks";
56
import {getSpacingBetweenNodes} from "./getSpacingBetweenNodes";
67
import {getTextForError} from "./getTextForError";
78

@@ -27,6 +28,9 @@ export class RemoveChildrenWithFormattingTextManipulator<TNode extends Node> imp
2728
const removalPos = getRemovalPos();
2829
this.removalPos = removalPos;
2930

31+
// console.log(JSON.stringify(fullText.substring(0, removalPos)));
32+
// console.log(JSON.stringify(fullText.substring(getRemovalEnd())));
33+
3034
return getPrefix() + getSpacing() + getSuffix();
3135

3236
function getPrefix() {
@@ -48,13 +52,15 @@ export class RemoveChildrenWithFormattingTextManipulator<TNode extends Node> imp
4852
}
4953

5054
function getRemovalPos() {
51-
if (previousSibling != null && nextSibling != null)
52-
return previousSibling.getEnd();
55+
if (previousSibling != null) {
56+
const trailingEnd = previousSibling.getTrailingTriviaEnd();
57+
return isNewLineAtPos(fullText, trailingEnd) ? trailingEnd : previousSibling.getEnd();
58+
}
5359

5460
if (parent.getPos() === children[0].getPos())
5561
return children[0].getNonWhitespaceStart(); // do not shift the parent
5662

57-
return children[0].isFirstNodeOnLine() ? getPosAfterPreviousNonBlankLine(fullText, children[0].getNonWhitespaceStart()) : children[0].getNonWhitespaceStart();
63+
return children[0].isFirstNodeOnLine() ? children[0].getPos() : children[0].getNonWhitespaceStart();
5864
}
5965

6066
function getRemovalEnd() {
@@ -66,16 +72,23 @@ export class RemoveChildrenWithFormattingTextManipulator<TNode extends Node> imp
6672
return nextSiblingStartLinePos;
6773
}
6874

69-
return nextSibling.getStart();
75+
return nextSibling.getStart(true);
7076
}
7177

7278
if (parent.getEnd() === children[children.length - 1].getEnd())
7379
return children[children.length - 1].getEnd(); // do not shift the parent
7480

75-
const nextNonSpaceOrTabChar = getNextMatchingPos(fullText, children[children.length - 1].getEnd(), char => char !== " " && char !== "\t");
76-
if (fullText[nextNonSpaceOrTabChar] === "\r" || fullText[nextNonSpaceOrTabChar] === "\n")
77-
return getPosAtNextNonBlankLine(fullText, nextNonSpaceOrTabChar);
78-
return nextNonSpaceOrTabChar;
81+
const triviaEnd = children[children.length - 1].getTrailingTriviaEnd();
82+
if (isNewLineAtPos(fullText, triviaEnd)) {
83+
if (previousSibling == null && children[0].getPos() === 0)
84+
return getPosAtNextNonBlankLine(fullText, triviaEnd);
85+
return getPosAtEndOfPreviousLine(fullText, getPosAtNextNonBlankLine(fullText, triviaEnd));
86+
}
87+
88+
if (previousSibling == null)
89+
return triviaEnd;
90+
else
91+
return children[children.length - 1].getEnd();
7992
}
8093
}
8194

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
export function getPosAfterNewLine(text: string, pos: number) {
2+
while (pos < text.length) {
3+
if (text[pos] === "\n")
4+
return pos + 1;
5+
pos++;
6+
}
7+
return pos;
8+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
export function getPosAtEndOfPreviousLine(fullText: string, pos: number) {
2+
while (pos > 0) {
3+
pos--;
4+
if (fullText[pos] === "\n") {
5+
if (fullText[pos - 1] === "\r")
6+
return pos - 1;
7+
return pos;
8+
}
9+
}
10+
11+
return pos;
12+
}

src/manipulation/textSeek/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
export * from "./getNextMatchingPos";
22
export * from "./getNextNonWhitespacePos";
3+
export * from "./getPosAfterNewLine";
34
export * from "./getPosAfterPreviousNonBlankLine";
45
export * from "./getPosAtNextNonBlankLine";
6+
export * from "./getPosAtEndOfPreviousLine";
57
export * from "./getPreviousMatchingPos";

src/tests/compiler/file/sourceFileTests.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -790,13 +790,13 @@ describe(nameof(SourceFile), () => {
790790
expect(sourceFile.getFullText()).to.equal("");
791791
});
792792

793-
it("should return the default export symbol when one exists", () => {
793+
it("should remove the default export symbol when one exists", () => {
794794
const {sourceFile} = getInfoFromText("export default class Identifier {}");
795795
sourceFile.removeDefaultExport();
796796
expect(sourceFile.getFullText()).to.equal("class Identifier {}");
797797
});
798798

799-
it("should return the default export symbol when default exported on a separate statement", () => {
799+
it("should remove the default export symbol when default exported on a separate statement", () => {
800800
const {sourceFile} = getInfoFromText("namespace Identifier {}\nclass Identifier {}\nexport default Identifier;\n");
801801
sourceFile.removeDefaultExport();
802802
expect(sourceFile.getFullText()).to.equal("namespace Identifier {}\nclass Identifier {}\n");

src/tests/compiler/statement/statementedNodeTests.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -270,7 +270,7 @@ describe(nameof(StatementedNode), () => {
270270
});
271271

272272
it("should remove statements at the end case clause", () => {
273-
doFirstChildTest<CaseClause>(caseClause, [1, 2], "switch (x) {\n case 1:\n x = 0;\n\n}", SyntaxKind.CaseClause);
273+
doFirstChildTest<CaseClause>(caseClause, [1, 2], "switch (x) {\n case 1:\n x = 0;\n}", SyntaxKind.CaseClause);
274274
});
275275

276276
const defaultClause = "switch (x) {\n default:\n x = 0;\n y = 1;\n break;\n}";
@@ -283,7 +283,7 @@ describe(nameof(StatementedNode), () => {
283283
});
284284

285285
it("should remove statements at the end default clause", () => {
286-
doFirstChildTest<DefaultClause>(defaultClause, [1, 2], "switch (x) {\n default:\n x = 0;\n\n}", SyntaxKind.DefaultClause);
286+
doFirstChildTest<DefaultClause>(defaultClause, [1, 2], "switch (x) {\n default:\n x = 0;\n}", SyntaxKind.DefaultClause);
287287
});
288288
});
289289

0 commit comments

Comments
 (0)