Skip to content

Commit 0ea8d79

Browse files
committed
Add tests, fix leading line terminator count
1 parent 839fb8e commit 0ea8d79

7 files changed

+206
-43
lines changed

src/compiler/emitter.ts

+48-43
Original file line numberDiff line numberDiff line change
@@ -2279,10 +2279,10 @@ namespace ts {
22792279
function emitPropertyAccessExpression(node: PropertyAccessExpression) {
22802280
const expression = cast(emitExpression(node.expression), isExpression);
22812281
const token = getDotOrQuestionDotToken(node);
2282-
const indentBeforeDot = needsIndentation(node, node.expression, token);
2283-
const indentAfterDot = needsIndentation(node, token, node.name);
2282+
const linesBeforeDot = getLinesBetweenNodes(node, node.expression, token);
2283+
const linesAfterDot = getLinesBetweenNodes(node, token, node.name);
22842284

2285-
increaseIndentIf(indentBeforeDot, /*writeSpaceIfNotIndenting*/ false);
2285+
writeLinesAndIndent(linesBeforeDot, /*writeSpaceIfNotIndenting*/ false);
22862286

22872287
const shouldEmitDotDot =
22882288
token.kind !== SyntaxKind.QuestionDotToken &&
@@ -2295,9 +2295,9 @@ namespace ts {
22952295
}
22962296

22972297
emitTokenWithComment(token.kind, node.expression.end, writePunctuation, node);
2298-
increaseIndentIf(indentAfterDot, /*writeSpaceIfNotIndenting*/ false);
2298+
writeLinesAndIndent(linesAfterDot, /*writeSpaceIfNotIndenting*/ false);
22992299
emit(node.name);
2300-
decreaseIndentIf(indentBeforeDot, indentAfterDot);
2300+
decreaseIndentIf(linesBeforeDot, linesAfterDot);
23012301
}
23022302

23032303
// 1..toString is a valid property access, emit a dot after the literal
@@ -2462,20 +2462,20 @@ namespace ts {
24622462
}
24632463
case EmitBinaryExpressionState.EmitRight: {
24642464
const isCommaOperator = node.operatorToken.kind !== SyntaxKind.CommaToken;
2465-
const indentBeforeOperator = needsIndentation(node, node.left, node.operatorToken);
2466-
const indentAfterOperator = needsIndentation(node, node.operatorToken, node.right);
2467-
increaseIndentIf(indentBeforeOperator, isCommaOperator);
2465+
const linesBeforeOperator = getLinesBetweenNodes(node, node.left, node.operatorToken);
2466+
const linesAfterOperator = getLinesBetweenNodes(node, node.operatorToken, node.right);
2467+
writeLinesAndIndent(linesBeforeOperator, isCommaOperator);
24682468
emitLeadingCommentsOfPosition(node.operatorToken.pos);
24692469
writeTokenNode(node.operatorToken, node.operatorToken.kind === SyntaxKind.InKeyword ? writeKeyword : writeOperator);
24702470
emitTrailingCommentsOfPosition(node.operatorToken.end, /*prefixSpace*/ true); // Binary operators should have a space before the comment starts
2471-
increaseIndentIf(indentAfterOperator, /*writeSpaceIfNotIndenting*/ true);
2471+
writeLinesAndIndent(linesAfterOperator, /*writeSpaceIfNotIndenting*/ true);
24722472
maybePipelineEmitExpression(node.right);
24732473
break;
24742474
}
24752475
case EmitBinaryExpressionState.FinishEmit: {
2476-
const indentBeforeOperator = needsIndentation(node, node.left, node.operatorToken);
2477-
const indentAfterOperator = needsIndentation(node, node.operatorToken, node.right);
2478-
decreaseIndentIf(indentBeforeOperator, indentAfterOperator);
2476+
const linesBeforeOperator = getLinesBetweenNodes(node, node.left, node.operatorToken);
2477+
const linesAfterOperator = getLinesBetweenNodes(node, node.operatorToken, node.right);
2478+
decreaseIndentIf(linesBeforeOperator, linesAfterOperator);
24792479
stackIndex--;
24802480
break;
24812481
}
@@ -2519,23 +2519,23 @@ namespace ts {
25192519
}
25202520

25212521
function emitConditionalExpression(node: ConditionalExpression) {
2522-
const indentBeforeQuestion = needsIndentation(node, node.condition, node.questionToken);
2523-
const indentAfterQuestion = needsIndentation(node, node.questionToken, node.whenTrue);
2524-
const indentBeforeColon = needsIndentation(node, node.whenTrue, node.colonToken);
2525-
const indentAfterColon = needsIndentation(node, node.colonToken, node.whenFalse);
2522+
const linesBeforeQuestion = getLinesBetweenNodes(node, node.condition, node.questionToken);
2523+
const linesAfterQuestion = getLinesBetweenNodes(node, node.questionToken, node.whenTrue);
2524+
const linesBeforeColon = getLinesBetweenNodes(node, node.whenTrue, node.colonToken);
2525+
const linesAfterColon = getLinesBetweenNodes(node, node.colonToken, node.whenFalse);
25262526

25272527
emitExpression(node.condition);
2528-
increaseIndentIf(indentBeforeQuestion, /*writeSpaceIfNotIndenting*/ true);
2528+
writeLinesAndIndent(linesBeforeQuestion, /*writeSpaceIfNotIndenting*/ true);
25292529
emit(node.questionToken);
2530-
increaseIndentIf(indentAfterQuestion, /*writeSpaceIfNotIndenting*/ true);
2530+
writeLinesAndIndent(linesAfterQuestion, /*writeSpaceIfNotIndenting*/ true);
25312531
emitExpression(node.whenTrue);
2532-
decreaseIndentIf(indentBeforeQuestion, indentAfterQuestion);
2532+
decreaseIndentIf(linesBeforeQuestion, linesAfterQuestion);
25332533

2534-
increaseIndentIf(indentBeforeColon, /*writeSpaceIfNotIndenting*/ true);
2534+
writeLinesAndIndent(linesBeforeColon, /*writeSpaceIfNotIndenting*/ true);
25352535
emit(node.colonToken);
2536-
increaseIndentIf(indentAfterColon, /*writeSpaceIfNotIndenting*/ true);
2536+
writeLinesAndIndent(linesAfterColon, /*writeSpaceIfNotIndenting*/ true);
25372537
emitExpression(node.whenFalse);
2538-
decreaseIndentIf(indentBeforeColon, indentAfterColon);
2538+
decreaseIndentIf(linesBeforeColon, linesAfterColon);
25392539
}
25402540

25412541
function emitTemplateExpression(node: TemplateExpression) {
@@ -4025,7 +4025,7 @@ namespace ts {
40254025
// Emit each child.
40264026
let previousSibling: Node | undefined;
40274027
let previousSourceFileTextKind: ReturnType<typeof recordBundleFileInternalSectionStart>;
4028-
let shouldDecreaseIndentAfterEmit = false;
4028+
let shouldDecreaselinesAfterEmit = false;
40294029
for (let i = 0; i < count; i++) {
40304030
const child = children![start + i];
40314031

@@ -4055,7 +4055,7 @@ namespace ts {
40554055
// line, we should increase the indent.
40564056
if ((format & (ListFormat.LinesMask | ListFormat.Indented)) === ListFormat.SingleLine) {
40574057
increaseIndent();
4058-
shouldDecreaseIndentAfterEmit = true;
4058+
shouldDecreaselinesAfterEmit = true;
40594059
}
40604060

40614061
writeLine(separatingLineTerminatorCount);
@@ -4080,9 +4080,9 @@ namespace ts {
40804080

40814081
emit(child);
40824082

4083-
if (shouldDecreaseIndentAfterEmit) {
4083+
if (shouldDecreaselinesAfterEmit) {
40844084
decreaseIndent();
4085-
shouldDecreaseIndentAfterEmit = false;
4085+
shouldDecreaselinesAfterEmit = false;
40864086
}
40874087

40884088
previousSibling = child;
@@ -4244,10 +4244,10 @@ namespace ts {
42444244
}
42454245
}
42464246

4247-
function increaseIndentIf(value: boolean, writeSpaceIfNotIndenting: boolean) {
4248-
if (value) {
4247+
function writeLinesAndIndent(lineCount: number, writeSpaceIfNotIndenting: boolean) {
4248+
if (lineCount) {
42494249
increaseIndent();
4250-
writeLine();
4250+
writeLine(lineCount);
42514251
}
42524252
else if (writeSpaceIfNotIndenting) {
42534253
writeSpace();
@@ -4258,7 +4258,7 @@ namespace ts {
42584258
// previous indent values to be considered at a time. This also allows caller to just
42594259
// call this once, passing in all their appropriate indent values, instead of needing
42604260
// to call this helper function multiple times.
4261-
function decreaseIndentIf(value1: boolean, value2: boolean) {
4261+
function decreaseIndentIf(value1: boolean | number, value2: boolean | number) {
42624262
if (value1) {
42634263
decreaseIndent();
42644264
}
@@ -4278,7 +4278,10 @@ namespace ts {
42784278
return rangeIsOnSingleLine(parentNode, currentSourceFile!) ? 0 : 1;
42794279
}
42804280
else if (!positionIsSynthesized(parentNode.pos) && !nodeIsSynthesized(firstChild) && firstChild.parent === parentNode) {
4281-
const lines = getEffectiveLinesBetweenRanges(parentNode, firstChild, getLinesBetweenRangeStartPositions);
4281+
let lines = getLinesBetweenPositionAndPrecedingNonWhitespaceCharacter(firstChild.pos, currentSourceFile!, /*includeComments*/ true);
4282+
if (lines === 0) {
4283+
lines = getLinesBetweenPositionAndPrecedingNonWhitespaceCharacter(firstChild.pos, currentSourceFile!, /*includeComments*/ false);
4284+
}
42824285
return printerOptions.preserveNewlines ? lines : Math.min(lines, 1);
42834286
}
42844287
else if (synthesizedNodeStartsOnNewLine(firstChild, format)) {
@@ -4339,15 +4342,15 @@ namespace ts {
43394342
// We start by measuring the line difference from parentNode's start to node2's comments start,
43404343
// so that this is counted as a one line difference, not two:
43414344
//
4342-
// function node1() {
4343-
// // NODE2 COMMENT
4344-
// node2;
4345+
// node1;
4346+
// // NODE2 COMMENT
4347+
// node2;
43454348
const lines = getLinesBetweenPositions(node1, node2, currentSourceFile!, /*includeComments*/ true);
43464349
if (lines === 0) {
43474350
// However, if the line difference considering node2's comments was 0, we might have this:
43484351
//
4349-
// function node1() { // NODE2 COMMENT
4350-
// node2;
4352+
// node1; // NODE2 COMMENT
4353+
// node2;
43514354
//
43524355
// in which case we should be ignoring node2's comment.
43534356
return getLinesBetweenPositions(node1, node2, currentSourceFile!, /*includeComments*/ false);
@@ -4368,9 +4371,9 @@ namespace ts {
43684371
return (format & ListFormat.PreferNewLine) !== 0;
43694372
}
43704373

4371-
function needsIndentation(parent: Node, node1: Node, node2: Node): boolean {
4374+
function getLinesBetweenNodes(parent: Node, node1: Node, node2: Node): number {
43724375
if (getEmitFlags(parent) & EmitFlags.NoIndentation) {
4373-
return false;
4376+
return 0;
43744377
}
43754378

43764379
parent = skipSynthesizedParentheses(parent);
@@ -4379,13 +4382,15 @@ namespace ts {
43794382

43804383
// Always use a newline for synthesized code if the synthesizer desires it.
43814384
if (getStartsOnNewLine(node2)) {
4382-
return true;
4385+
return 1;
4386+
}
4387+
4388+
if (!nodeIsSynthesized(parent) && !nodeIsSynthesized(node1) && !nodeIsSynthesized(node2)) {
4389+
const lines = getEffectiveLinesBetweenRanges(node1, node2, getLinesBetweenRangeEndAndRangeStart);
4390+
return preserveNewlines ? lines : Math.min(lines, 1);
43834391
}
43844392

4385-
return !nodeIsSynthesized(parent)
4386-
&& !nodeIsSynthesized(node1)
4387-
&& !nodeIsSynthesized(node2)
4388-
&& !rangeEndIsOnSameLineAsRangeStart(node1, node2, currentSourceFile!);
4393+
return 0;
43894394
}
43904395

43914396
function isEmptyBlock(block: BlockLike) {

src/compiler/utilities.ts

+14
Original file line numberDiff line numberDiff line change
@@ -4701,6 +4701,20 @@ namespace ts {
47014701
return positionIsSynthesized(range.pos) ? -1 : skipTrivia(sourceFile.text, range.pos, /*stopAfterLineBreak*/ false, includeComments);
47024702
}
47034703

4704+
export function getLinesBetweenPositionAndPrecedingNonWhitespaceCharacter(pos: number, sourceFile: SourceFile, includeComments?: boolean) {
4705+
const startPos = skipTrivia(sourceFile.text, pos, /*stopAfterLineBreak*/ false, includeComments);
4706+
const prevPos = getPreviousNonWhitespacePosition(pos, sourceFile);
4707+
return getLineOfLocalPosition(sourceFile, startPos) - getLineOfLocalPosition(sourceFile, prevPos || 0);
4708+
}
4709+
4710+
function getPreviousNonWhitespacePosition(pos: number, sourceFile: SourceFile) {
4711+
while (pos-- > 0) {
4712+
if (!isWhiteSpaceLike(sourceFile.text.charCodeAt(pos))) {
4713+
return pos;
4714+
}
4715+
}
4716+
}
4717+
47044718
/**
47054719
* Determines whether a name was originally the declaration name of an enum or namespace
47064720
* declaration.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
/// <reference path="fourslash.ts" />
2+
3+
//// /*1*/console.log(1);
4+
////
5+
//// console.log(2);
6+
////
7+
//// console.log(3);/*2*/
8+
9+
goTo.select("1", "2");
10+
edit.applyRefactor({
11+
refactorName: "Extract Symbol",
12+
actionName: "function_scope_0",
13+
actionDescription: "Extract to function in global scope",
14+
newContent:
15+
`/*RENAME*/newFunction();
16+
17+
function newFunction() {
18+
console.log(1);
19+
20+
console.log(2);
21+
22+
console.log(3);
23+
}
24+
`
25+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
/// <reference path="fourslash.ts" />
2+
3+
//// /*1*/1 +
4+
//// 2 +
5+
////
6+
//// 3 +
7+
////
8+
////
9+
//// 4;/*2*/
10+
11+
goTo.select("1", "2");
12+
edit.applyRefactor({
13+
refactorName: "Extract Symbol",
14+
actionName: "function_scope_0",
15+
actionDescription: "Extract to function in global scope",
16+
newContent:
17+
`/*RENAME*/newFunction();
18+
19+
function newFunction() {
20+
1 +
21+
2 +
22+
23+
3 +
24+
25+
26+
4;
27+
}
28+
`
29+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
/// <reference path="fourslash.ts" />
2+
3+
//// /*1*/app
4+
//// .use(foo)
5+
////
6+
//// .use(bar)
7+
////
8+
////
9+
//// .use(
10+
//// baz,
11+
////
12+
//// blob);/*2*/
13+
14+
goTo.select("1", "2");
15+
edit.applyRefactor({
16+
refactorName: "Extract Symbol",
17+
actionName: "function_scope_0",
18+
actionDescription: "Extract to function in global scope",
19+
newContent:
20+
`/*RENAME*/newFunction();
21+
22+
function newFunction() {
23+
app
24+
.use(foo)
25+
26+
.use(bar)
27+
28+
29+
.use(
30+
baz,
31+
32+
blob);
33+
}
34+
`
35+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
/// <reference path="fourslash.ts" />
2+
3+
//// const x = /*1*/function f()
4+
//// {
5+
////
6+
//// console.log();
7+
//// }/*2*/;
8+
9+
goTo.select("1", "2");
10+
edit.applyRefactor({
11+
refactorName: "Extract Symbol",
12+
actionName: "function_scope_0",
13+
actionDescription: "Extract to function in global scope",
14+
newContent:
15+
`const x = /*RENAME*/newFunction();
16+
17+
function newFunction() {
18+
return function f() {
19+
20+
console.log();
21+
};
22+
}
23+
`
24+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
/// <reference path="fourslash.ts" />
2+
3+
//// /*1*/f // call expression
4+
//// (arg)(
5+
//// /** @type {number} */
6+
//// blah,
7+
////
8+
//// blah
9+
////
10+
//// );/*2*/
11+
12+
goTo.select("1", "2");
13+
edit.applyRefactor({
14+
refactorName: "Extract Symbol",
15+
actionName: "function_scope_0",
16+
actionDescription: "Extract to function in global scope",
17+
newContent:
18+
`/*RENAME*/newFunction();
19+
20+
function newFunction() {
21+
f // call expression
22+
(arg)(
23+
/** @type {number} */
24+
blah,
25+
26+
blah
27+
28+
);
29+
}
30+
`
31+
});

0 commit comments

Comments
 (0)