mirror of
https://github.com/microsoft/TypeScript.git
synced 2025-11-18 17:21:48 +00:00
Preserve newlines from original source when printing nodes from TextChanges (#36688)
* Allow emitter to write multiple newlines in node lists * Progress * Progress * Fix recomputeIndentation * Add tests, fix leading line terminator count * Do a bit less work when `preserveNewlines` is off * Fix accidental find/replace rename * Restore some monomorphism * Fix single line writer * Fix other writers * Revert "Fix other writers" This reverts commit21b0cb8f3b. * Revert "Fix single line writer" This reverts commite535e279f9. * Revert "Restore some monomorphism" This reverts commite3ef42743a. * Add equal position optimization to getLinesBetweenRangeEndAndRangeStart * Add one more test * Actually save the test file * Rename preserveNewlines to preserveSourceNewlines * Make ignoreSourceNewlines internal * Optimize lines-between functions * Add comment; * Fix trailing line terminator count bug for function parameters * Preserve newlines around parenthesized expressions * Back to speculative microoptimizations, yay * Don’t call getEffectiveLines during tsc emit at all
This commit is contained in:
+158
-85
@@ -841,6 +841,7 @@ namespace ts {
|
||||
let tempFlags: TempFlags; // TempFlags for the current name generation scope.
|
||||
let reservedNamesStack: Map<true>[]; // Stack of TempFlags reserved in enclosing name generation scopes.
|
||||
let reservedNames: Map<true>; // TempFlags to reserve in nested name generation scopes.
|
||||
let preserveSourceNewlines = printerOptions.preserveSourceNewlines; // Can be overridden inside nodes with the `IgnoreSourceNewlines` emit flag.
|
||||
|
||||
let writer: EmitTextWriter;
|
||||
let ownWriter: EmitTextWriter; // Reusable `EmitTextWriter` for basic printing.
|
||||
@@ -1163,8 +1164,12 @@ namespace ts {
|
||||
function pipelineEmit(emitHint: EmitHint, node: Node) {
|
||||
const savedLastNode = lastNode;
|
||||
const savedLastSubstitution = lastSubstitution;
|
||||
const savedPreserveSourceNewlines = preserveSourceNewlines;
|
||||
lastNode = node;
|
||||
lastSubstitution = undefined;
|
||||
if (preserveSourceNewlines && !!(getEmitFlags(node) & EmitFlags.IgnoreSourceNewlines)) {
|
||||
preserveSourceNewlines = false;
|
||||
}
|
||||
|
||||
const pipelinePhase = getPipelinePhase(PipelinePhase.Notification, emitHint, node);
|
||||
pipelinePhase(emitHint, node);
|
||||
@@ -1174,6 +1179,7 @@ namespace ts {
|
||||
const substitute = lastSubstitution;
|
||||
lastNode = savedLastNode;
|
||||
lastSubstitution = savedLastSubstitution;
|
||||
preserveSourceNewlines = savedPreserveSourceNewlines;
|
||||
|
||||
return substitute || node;
|
||||
}
|
||||
@@ -2273,10 +2279,10 @@ namespace ts {
|
||||
function emitPropertyAccessExpression(node: PropertyAccessExpression) {
|
||||
const expression = cast(emitExpression(node.expression), isExpression);
|
||||
const token = node.questionDotToken || createNode(SyntaxKind.DotToken, node.expression.end, node.name.pos) as DotToken;
|
||||
const indentBeforeDot = needsIndentation(node, node.expression, token);
|
||||
const indentAfterDot = needsIndentation(node, token, node.name);
|
||||
const linesBeforeDot = getLinesBetweenNodes(node, node.expression, token);
|
||||
const linesAfterDot = getLinesBetweenNodes(node, token, node.name);
|
||||
|
||||
increaseIndentIf(indentBeforeDot, /*writeSpaceIfNotIndenting*/ false);
|
||||
writeLinesAndIndent(linesBeforeDot, /*writeSpaceIfNotIndenting*/ false);
|
||||
|
||||
const shouldEmitDotDot =
|
||||
token.kind !== SyntaxKind.QuestionDotToken &&
|
||||
@@ -2294,9 +2300,9 @@ namespace ts {
|
||||
else {
|
||||
emitTokenWithComment(token.kind, node.expression.end, writePunctuation, node);
|
||||
}
|
||||
increaseIndentIf(indentAfterDot, /*writeSpaceIfNotIndenting*/ false);
|
||||
writeLinesAndIndent(linesAfterDot, /*writeSpaceIfNotIndenting*/ false);
|
||||
emit(node.name);
|
||||
decreaseIndentIf(indentBeforeDot, indentAfterDot);
|
||||
decreaseIndentIf(linesBeforeDot, linesAfterDot);
|
||||
}
|
||||
|
||||
// 1..toString is a valid property access, emit a dot after the literal
|
||||
@@ -2358,7 +2364,16 @@ namespace ts {
|
||||
|
||||
function emitParenthesizedExpression(node: ParenthesizedExpression) {
|
||||
const openParenPos = emitTokenWithComment(SyntaxKind.OpenParenToken, node.pos, writePunctuation, node);
|
||||
const leadingNewlines = preserveSourceNewlines && getLeadingLineTerminatorCount(node, [node.expression], ListFormat.None);
|
||||
if (leadingNewlines) {
|
||||
writeLinesAndIndent(leadingNewlines, /*writeLinesIfNotIndenting*/ false);
|
||||
}
|
||||
emitExpression(node.expression);
|
||||
const trailingNewlines = preserveSourceNewlines && getClosingLineTerminatorCount(node, [node.expression], ListFormat.None);
|
||||
if (trailingNewlines) {
|
||||
writeLine(trailingNewlines);
|
||||
}
|
||||
decreaseIndentIf(leadingNewlines);
|
||||
emitTokenWithComment(SyntaxKind.CloseParenToken, node.expression ? node.expression.end : openParenPos, writePunctuation, node);
|
||||
}
|
||||
|
||||
@@ -2461,20 +2476,20 @@ namespace ts {
|
||||
}
|
||||
case EmitBinaryExpressionState.EmitRight: {
|
||||
const isCommaOperator = node.operatorToken.kind !== SyntaxKind.CommaToken;
|
||||
const indentBeforeOperator = needsIndentation(node, node.left, node.operatorToken);
|
||||
const indentAfterOperator = needsIndentation(node, node.operatorToken, node.right);
|
||||
increaseIndentIf(indentBeforeOperator, isCommaOperator);
|
||||
const linesBeforeOperator = getLinesBetweenNodes(node, node.left, node.operatorToken);
|
||||
const linesAfterOperator = getLinesBetweenNodes(node, node.operatorToken, node.right);
|
||||
writeLinesAndIndent(linesBeforeOperator, isCommaOperator);
|
||||
emitLeadingCommentsOfPosition(node.operatorToken.pos);
|
||||
writeTokenNode(node.operatorToken, node.operatorToken.kind === SyntaxKind.InKeyword ? writeKeyword : writeOperator);
|
||||
emitTrailingCommentsOfPosition(node.operatorToken.end, /*prefixSpace*/ true); // Binary operators should have a space before the comment starts
|
||||
increaseIndentIf(indentAfterOperator, /*writeSpaceIfNotIndenting*/ true);
|
||||
writeLinesAndIndent(linesAfterOperator, /*writeSpaceIfNotIndenting*/ true);
|
||||
maybePipelineEmitExpression(node.right);
|
||||
break;
|
||||
}
|
||||
case EmitBinaryExpressionState.FinishEmit: {
|
||||
const indentBeforeOperator = needsIndentation(node, node.left, node.operatorToken);
|
||||
const indentAfterOperator = needsIndentation(node, node.operatorToken, node.right);
|
||||
decreaseIndentIf(indentBeforeOperator, indentAfterOperator);
|
||||
const linesBeforeOperator = getLinesBetweenNodes(node, node.left, node.operatorToken);
|
||||
const linesAfterOperator = getLinesBetweenNodes(node, node.operatorToken, node.right);
|
||||
decreaseIndentIf(linesBeforeOperator, linesAfterOperator);
|
||||
stackIndex--;
|
||||
break;
|
||||
}
|
||||
@@ -2518,23 +2533,23 @@ namespace ts {
|
||||
}
|
||||
|
||||
function emitConditionalExpression(node: ConditionalExpression) {
|
||||
const indentBeforeQuestion = needsIndentation(node, node.condition, node.questionToken);
|
||||
const indentAfterQuestion = needsIndentation(node, node.questionToken, node.whenTrue);
|
||||
const indentBeforeColon = needsIndentation(node, node.whenTrue, node.colonToken);
|
||||
const indentAfterColon = needsIndentation(node, node.colonToken, node.whenFalse);
|
||||
const linesBeforeQuestion = getLinesBetweenNodes(node, node.condition, node.questionToken);
|
||||
const linesAfterQuestion = getLinesBetweenNodes(node, node.questionToken, node.whenTrue);
|
||||
const linesBeforeColon = getLinesBetweenNodes(node, node.whenTrue, node.colonToken);
|
||||
const linesAfterColon = getLinesBetweenNodes(node, node.colonToken, node.whenFalse);
|
||||
|
||||
emitExpression(node.condition);
|
||||
increaseIndentIf(indentBeforeQuestion, /*writeSpaceIfNotIndenting*/ true);
|
||||
writeLinesAndIndent(linesBeforeQuestion, /*writeSpaceIfNotIndenting*/ true);
|
||||
emit(node.questionToken);
|
||||
increaseIndentIf(indentAfterQuestion, /*writeSpaceIfNotIndenting*/ true);
|
||||
writeLinesAndIndent(linesAfterQuestion, /*writeSpaceIfNotIndenting*/ true);
|
||||
emitExpression(node.whenTrue);
|
||||
decreaseIndentIf(indentBeforeQuestion, indentAfterQuestion);
|
||||
decreaseIndentIf(linesBeforeQuestion, linesAfterQuestion);
|
||||
|
||||
increaseIndentIf(indentBeforeColon, /*writeSpaceIfNotIndenting*/ true);
|
||||
writeLinesAndIndent(linesBeforeColon, /*writeSpaceIfNotIndenting*/ true);
|
||||
emit(node.colonToken);
|
||||
increaseIndentIf(indentAfterColon, /*writeSpaceIfNotIndenting*/ true);
|
||||
writeLinesAndIndent(linesAfterColon, /*writeSpaceIfNotIndenting*/ true);
|
||||
emitExpression(node.whenFalse);
|
||||
decreaseIndentIf(indentBeforeColon, indentAfterColon);
|
||||
decreaseIndentIf(linesBeforeColon, linesAfterColon);
|
||||
}
|
||||
|
||||
function emitTemplateExpression(node: TemplateExpression) {
|
||||
@@ -2929,14 +2944,14 @@ namespace ts {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (shouldWriteLeadingLineTerminator(body, body.statements, ListFormat.PreserveLines)
|
||||
|| shouldWriteClosingLineTerminator(body, body.statements, ListFormat.PreserveLines)) {
|
||||
if (getLeadingLineTerminatorCount(body, body.statements, ListFormat.PreserveLines)
|
||||
|| getClosingLineTerminatorCount(body, body.statements, ListFormat.PreserveLines)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let previousStatement: Statement | undefined;
|
||||
for (const statement of body.statements) {
|
||||
if (shouldWriteSeparatingLineTerminator(previousStatement, statement, ListFormat.PreserveLines)) {
|
||||
if (getSeparatingLineTerminatorCount(previousStatement, statement, ListFormat.PreserveLines) > 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -3996,7 +4011,7 @@ namespace ts {
|
||||
|
||||
if (isEmpty) {
|
||||
// Write a line terminator if the parent node was multi-line
|
||||
if (format & ListFormat.MultiLine) {
|
||||
if (format & ListFormat.MultiLine && !(preserveSourceNewlines && rangeIsOnSingleLine(parentNode, currentSourceFile!))) {
|
||||
writeLine();
|
||||
}
|
||||
else if (format & ListFormat.SpaceBetweenBraces && !(format & ListFormat.NoSpaceIfEmpty)) {
|
||||
@@ -4007,8 +4022,9 @@ namespace ts {
|
||||
// Write the opening line terminator or leading whitespace.
|
||||
const mayEmitInterveningComments = (format & ListFormat.NoInterveningComments) === 0;
|
||||
let shouldEmitInterveningComments = mayEmitInterveningComments;
|
||||
if (shouldWriteLeadingLineTerminator(parentNode, children!, format)) { // TODO: GH#18217
|
||||
writeLine();
|
||||
const leadingLineTerminatorCount = getLeadingLineTerminatorCount(parentNode, children!, format); // TODO: GH#18217
|
||||
if (leadingLineTerminatorCount) {
|
||||
writeLine(leadingLineTerminatorCount);
|
||||
shouldEmitInterveningComments = false;
|
||||
}
|
||||
else if (format & ListFormat.SpaceBetweenBraces) {
|
||||
@@ -4047,7 +4063,8 @@ namespace ts {
|
||||
recordBundleFileInternalSectionEnd(previousSourceFileTextKind);
|
||||
|
||||
// Write either a line terminator or whitespace to separate the elements.
|
||||
if (shouldWriteSeparatingLineTerminator(previousSibling, child, format)) {
|
||||
const separatingLineTerminatorCount = getSeparatingLineTerminatorCount(previousSibling, child, format);
|
||||
if (separatingLineTerminatorCount > 0) {
|
||||
// If a synthesized node in a single-line list starts on a new
|
||||
// line, we should increase the indent.
|
||||
if ((format & (ListFormat.LinesMask | ListFormat.Indented)) === ListFormat.SingleLine) {
|
||||
@@ -4055,7 +4072,7 @@ namespace ts {
|
||||
shouldDecreaseIndentAfterEmit = true;
|
||||
}
|
||||
|
||||
writeLine();
|
||||
writeLine(separatingLineTerminatorCount);
|
||||
shouldEmitInterveningComments = false;
|
||||
}
|
||||
else if (previousSibling && format & ListFormat.SpaceBetweenSiblings) {
|
||||
@@ -4110,8 +4127,9 @@ namespace ts {
|
||||
recordBundleFileInternalSectionEnd(previousSourceFileTextKind);
|
||||
|
||||
// Write the closing line terminator or closing whitespace.
|
||||
if (shouldWriteClosingLineTerminator(parentNode, children!, format)) {
|
||||
writeLine();
|
||||
const closingLineTerminatorCount = getClosingLineTerminatorCount(parentNode, children!, format);
|
||||
if (closingLineTerminatorCount) {
|
||||
writeLine(closingLineTerminatorCount);
|
||||
}
|
||||
else if (format & ListFormat.SpaceBetweenBraces) {
|
||||
writeSpace();
|
||||
@@ -4181,8 +4199,10 @@ namespace ts {
|
||||
writer.writeProperty(s);
|
||||
}
|
||||
|
||||
function writeLine() {
|
||||
writer.writeLine();
|
||||
function writeLine(count = 1) {
|
||||
for (let i = 0; i < count; i++) {
|
||||
writer.writeLine(i > 0);
|
||||
}
|
||||
}
|
||||
|
||||
function increaseIndent() {
|
||||
@@ -4238,10 +4258,10 @@ namespace ts {
|
||||
}
|
||||
}
|
||||
|
||||
function increaseIndentIf(value: boolean, writeSpaceIfNotIndenting: boolean) {
|
||||
if (value) {
|
||||
function writeLinesAndIndent(lineCount: number, writeSpaceIfNotIndenting: boolean) {
|
||||
if (lineCount) {
|
||||
increaseIndent();
|
||||
writeLine();
|
||||
writeLine(lineCount);
|
||||
}
|
||||
else if (writeSpaceIfNotIndenting) {
|
||||
writeSpace();
|
||||
@@ -4252,7 +4272,7 @@ namespace ts {
|
||||
// previous indent values to be considered at a time. This also allows caller to just
|
||||
// call this once, passing in all their appropriate indent values, instead of needing
|
||||
// to call this helper function multiple times.
|
||||
function decreaseIndentIf(value1: boolean, value2: boolean) {
|
||||
function decreaseIndentIf(value1: boolean | number | undefined, value2?: boolean | number) {
|
||||
if (value1) {
|
||||
decreaseIndent();
|
||||
}
|
||||
@@ -4261,75 +4281,119 @@ namespace ts {
|
||||
}
|
||||
}
|
||||
|
||||
function shouldWriteLeadingLineTerminator(parentNode: TextRange, children: NodeArray<Node>, format: ListFormat) {
|
||||
if (format & ListFormat.MultiLine) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (format & ListFormat.PreserveLines) {
|
||||
function getLeadingLineTerminatorCount(parentNode: TextRange, children: readonly Node[], format: ListFormat): number {
|
||||
if (format & ListFormat.PreserveLines || preserveSourceNewlines) {
|
||||
if (format & ListFormat.PreferNewLine) {
|
||||
return true;
|
||||
return 1;
|
||||
}
|
||||
|
||||
const firstChild = children[0];
|
||||
if (firstChild === undefined) {
|
||||
return !rangeIsOnSingleLine(parentNode, currentSourceFile!);
|
||||
return rangeIsOnSingleLine(parentNode, currentSourceFile!) ? 0 : 1;
|
||||
}
|
||||
else if (positionIsSynthesized(parentNode.pos) || nodeIsSynthesized(firstChild)) {
|
||||
return synthesizedNodeStartsOnNewLine(firstChild, format);
|
||||
if (firstChild.kind === SyntaxKind.JsxText) {
|
||||
// JsxText will be written with its leading whitespace, so don't add more manually.
|
||||
return 0;
|
||||
}
|
||||
else {
|
||||
return !rangeStartPositionsAreOnSameLine(parentNode, firstChild, currentSourceFile!);
|
||||
if (!positionIsSynthesized(parentNode.pos) && !nodeIsSynthesized(firstChild) && firstChild.parent === parentNode) {
|
||||
if (preserveSourceNewlines) {
|
||||
return getEffectiveLines(
|
||||
includeComments => getLinesBetweenPositionAndPrecedingNonWhitespaceCharacter(
|
||||
firstChild.pos,
|
||||
currentSourceFile!,
|
||||
includeComments));
|
||||
}
|
||||
return rangeStartPositionsAreOnSameLine(parentNode, firstChild, currentSourceFile!) ? 0 : 1;
|
||||
}
|
||||
if (synthesizedNodeStartsOnNewLine(firstChild, format)) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
return format & ListFormat.MultiLine ? 1 : 0;
|
||||
}
|
||||
|
||||
function shouldWriteSeparatingLineTerminator(previousNode: Node | undefined, nextNode: Node, format: ListFormat) {
|
||||
if (format & ListFormat.MultiLine) {
|
||||
return true;
|
||||
}
|
||||
else if (format & ListFormat.PreserveLines) {
|
||||
function getSeparatingLineTerminatorCount(previousNode: Node | undefined, nextNode: Node, format: ListFormat): number {
|
||||
if (format & ListFormat.PreserveLines || preserveSourceNewlines) {
|
||||
if (previousNode === undefined || nextNode === undefined) {
|
||||
return false;
|
||||
return 0;
|
||||
}
|
||||
else if (nodeIsSynthesized(previousNode) || nodeIsSynthesized(nextNode)) {
|
||||
return synthesizedNodeStartsOnNewLine(previousNode, format) || synthesizedNodeStartsOnNewLine(nextNode, format);
|
||||
if (nextNode.kind === SyntaxKind.JsxText) {
|
||||
// JsxText will be written with its leading whitespace, so don't add more manually.
|
||||
return 0;
|
||||
}
|
||||
else {
|
||||
return !rangeEndIsOnSameLineAsRangeStart(previousNode, nextNode, currentSourceFile!);
|
||||
else if (!nodeIsSynthesized(previousNode) && !nodeIsSynthesized(nextNode) && previousNode.parent === nextNode.parent) {
|
||||
if (preserveSourceNewlines) {
|
||||
return getEffectiveLines(
|
||||
includeComments => getLinesBetweenRangeEndAndRangeStart(
|
||||
previousNode,
|
||||
nextNode,
|
||||
currentSourceFile!,
|
||||
includeComments));
|
||||
}
|
||||
return rangeEndIsOnSameLineAsRangeStart(previousNode, nextNode, currentSourceFile!) ? 0 : 1;
|
||||
}
|
||||
else if (synthesizedNodeStartsOnNewLine(previousNode, format) || synthesizedNodeStartsOnNewLine(nextNode, format)) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
else {
|
||||
return getStartsOnNewLine(nextNode);
|
||||
else if (getStartsOnNewLine(nextNode)) {
|
||||
return 1;
|
||||
}
|
||||
return format & ListFormat.MultiLine ? 1 : 0;
|
||||
}
|
||||
|
||||
function shouldWriteClosingLineTerminator(parentNode: TextRange, children: NodeArray<Node>, format: ListFormat) {
|
||||
if (format & ListFormat.MultiLine) {
|
||||
return (format & ListFormat.NoTrailingNewLine) === 0;
|
||||
}
|
||||
else if (format & ListFormat.PreserveLines) {
|
||||
function getClosingLineTerminatorCount(parentNode: TextRange, children: readonly Node[], format: ListFormat): number {
|
||||
if (format & ListFormat.PreserveLines || preserveSourceNewlines) {
|
||||
if (format & ListFormat.PreferNewLine) {
|
||||
return true;
|
||||
return 1;
|
||||
}
|
||||
|
||||
const lastChild = lastOrUndefined(children);
|
||||
if (lastChild === undefined) {
|
||||
return !rangeIsOnSingleLine(parentNode, currentSourceFile!);
|
||||
return rangeIsOnSingleLine(parentNode, currentSourceFile!) ? 0 : 1;
|
||||
}
|
||||
else if (positionIsSynthesized(parentNode.pos) || nodeIsSynthesized(lastChild)) {
|
||||
return synthesizedNodeStartsOnNewLine(lastChild, format);
|
||||
if (!positionIsSynthesized(parentNode.pos) && !nodeIsSynthesized(lastChild) && lastChild.parent === parentNode) {
|
||||
if (preserveSourceNewlines) {
|
||||
return getEffectiveLines(
|
||||
includeComments => getLinesBetweenPositionAndNextNonWhitespaceCharacter(
|
||||
lastChild.end,
|
||||
currentSourceFile!,
|
||||
includeComments));
|
||||
}
|
||||
return rangeEndPositionsAreOnSameLine(parentNode, lastChild, currentSourceFile!) ? 0 : 1;
|
||||
}
|
||||
else {
|
||||
return !rangeEndPositionsAreOnSameLine(parentNode, lastChild, currentSourceFile!);
|
||||
if (synthesizedNodeStartsOnNewLine(lastChild, format)) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
else {
|
||||
return false;
|
||||
if (format & ListFormat.MultiLine && !(format & ListFormat.NoTrailingNewLine)) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
function getEffectiveLines(getLineDifference: (includeComments: boolean) => number) {
|
||||
// If 'preserveSourceNewlines' is disabled, we should never call this function
|
||||
// because it could be more expensive than alternative approximations.
|
||||
Debug.assert(!!preserveSourceNewlines);
|
||||
// We start by measuring the line difference from a position to its adjacent comments,
|
||||
// so that this is counted as a one-line difference, not two:
|
||||
//
|
||||
// node1;
|
||||
// // NODE2 COMMENT
|
||||
// node2;
|
||||
const lines = getLineDifference(/*includeComments*/ true);
|
||||
if (lines === 0) {
|
||||
// However, if the line difference considering comments was 0, we might have this:
|
||||
//
|
||||
// node1; // NODE2 COMMENT
|
||||
// node2;
|
||||
//
|
||||
// in which case we should be ignoring node2's comment, so this too is counted as
|
||||
// a one-line difference, not zero.
|
||||
return getLineDifference(/*includeComments*/ false);
|
||||
}
|
||||
return lines;
|
||||
}
|
||||
|
||||
function synthesizedNodeStartsOnNewLine(node: Node, format: ListFormat) {
|
||||
@@ -4345,9 +4409,9 @@ namespace ts {
|
||||
return (format & ListFormat.PreferNewLine) !== 0;
|
||||
}
|
||||
|
||||
function needsIndentation(parent: Node, node1: Node, node2: Node): boolean {
|
||||
function getLinesBetweenNodes(parent: Node, node1: Node, node2: Node): number {
|
||||
if (getEmitFlags(parent) & EmitFlags.NoIndentation) {
|
||||
return false;
|
||||
return 0;
|
||||
}
|
||||
|
||||
parent = skipSynthesizedParentheses(parent);
|
||||
@@ -4356,13 +4420,22 @@ namespace ts {
|
||||
|
||||
// Always use a newline for synthesized code if the synthesizer desires it.
|
||||
if (getStartsOnNewLine(node2)) {
|
||||
return true;
|
||||
return 1;
|
||||
}
|
||||
|
||||
return !nodeIsSynthesized(parent)
|
||||
&& !nodeIsSynthesized(node1)
|
||||
&& !nodeIsSynthesized(node2)
|
||||
&& !rangeEndIsOnSameLineAsRangeStart(node1, node2, currentSourceFile!);
|
||||
if (!nodeIsSynthesized(parent) && !nodeIsSynthesized(node1) && !nodeIsSynthesized(node2)) {
|
||||
if (preserveSourceNewlines) {
|
||||
return getEffectiveLines(
|
||||
includeComments => getLinesBetweenRangeEndAndRangeStart(
|
||||
node1,
|
||||
node2,
|
||||
currentSourceFile!,
|
||||
includeComments));
|
||||
}
|
||||
return rangeEndIsOnSameLineAsRangeStart(node1, node2, currentSourceFile!) ? 0 : 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
function isEmptyBlock(block: BlockLike) {
|
||||
|
||||
@@ -3567,6 +3567,12 @@ namespace ts {
|
||||
return node;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
export function ignoreSourceNewlines<T extends Node>(node: T): T {
|
||||
getOrCreateEmitNode(node).flags |= EmitFlags.IgnoreSourceNewlines;
|
||||
return node;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the constant value to emit for an expression.
|
||||
*/
|
||||
|
||||
+24
-6
@@ -409,11 +409,20 @@ namespace ts {
|
||||
}
|
||||
|
||||
/* @internal */
|
||||
export function computeLineAndCharacterOfPosition(lineStarts: readonly number[], position: number): LineAndCharacter {
|
||||
const lineNumber = computeLineOfPosition(lineStarts, position);
|
||||
return {
|
||||
line: lineNumber,
|
||||
character: position - lineStarts[lineNumber]
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* We assume the first line starts at position 0 and 'position' is non-negative.
|
||||
*/
|
||||
export function computeLineAndCharacterOfPosition(lineStarts: readonly number[], position: number): LineAndCharacter {
|
||||
let lineNumber = binarySearch(lineStarts, position, identity, compareValues);
|
||||
export function computeLineOfPosition(lineStarts: readonly number[], position: number, lowerBound?: number) {
|
||||
let lineNumber = binarySearch(lineStarts, position, identity, compareValues, lowerBound);
|
||||
if (lineNumber < 0) {
|
||||
// If the actual position was not found,
|
||||
// the binary search returns the 2's-complement of the next line start
|
||||
@@ -425,10 +434,19 @@ namespace ts {
|
||||
lineNumber = ~lineNumber - 1;
|
||||
Debug.assert(lineNumber !== -1, "position cannot precede the beginning of the file");
|
||||
}
|
||||
return {
|
||||
line: lineNumber,
|
||||
character: position - lineStarts[lineNumber]
|
||||
};
|
||||
return lineNumber;
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
export function getLinesBetweenPositions(sourceFile: SourceFileLike, pos1: number, pos2: number) {
|
||||
if (pos1 === pos2) return 0;
|
||||
const lineStarts = getLineStarts(sourceFile);
|
||||
const lower = Math.min(pos1, pos2);
|
||||
const isNegative = lower === pos2;
|
||||
const upper = isNegative ? pos1 : pos2;
|
||||
const lowerLine = computeLineOfPosition(lineStarts, lower);
|
||||
const upperLine = computeLineOfPosition(lineStarts, upper, lowerLine);
|
||||
return isNegative ? lowerLine - upperLine : upperLine - lowerLine;
|
||||
}
|
||||
|
||||
export function getLineAndCharacterOfPosition(sourceFile: SourceFileLike, position: number): LineAndCharacter {
|
||||
|
||||
@@ -3784,7 +3784,7 @@ namespace ts {
|
||||
writeParameter(text: string): void;
|
||||
writeProperty(text: string): void;
|
||||
writeSymbol(text: string, symbol: Symbol): void;
|
||||
writeLine(): void;
|
||||
writeLine(force?: boolean): void;
|
||||
increaseIndent(): void;
|
||||
decreaseIndent(): void;
|
||||
clear(): void;
|
||||
@@ -5860,6 +5860,7 @@ namespace ts {
|
||||
NoAsciiEscaping = 1 << 24, // When synthesizing nodes that lack an original node or textSourceNode, we want to write the text on the node with ASCII escaping substitutions.
|
||||
/*@internal*/ TypeScriptClassWrapper = 1 << 25, // The node is an IIFE class wrapper created by the ts transform.
|
||||
/*@internal*/ NeverApplyImportHelper = 1 << 26, // Indicates the node should never be wrapped with an import star helper (because, for example, it imports tslib itself)
|
||||
/*@internal*/ IgnoreSourceNewlines = 1 << 27, // Overrides `printerOptions.preserveSourceNewlines` to print this node (and all descendants) with default whitespace.
|
||||
}
|
||||
|
||||
export interface EmitHelper {
|
||||
@@ -6324,6 +6325,7 @@ namespace ts {
|
||||
/*@internal*/ writeBundleFileInfo?: boolean;
|
||||
/*@internal*/ recordInternalSection?: boolean;
|
||||
/*@internal*/ stripInternal?: boolean;
|
||||
/*@internal*/ preserveSourceNewlines?: boolean;
|
||||
/*@internal*/ relativeToBuildInfo?: (path: string) => string;
|
||||
}
|
||||
|
||||
|
||||
+43
-12
@@ -3633,8 +3633,8 @@ namespace ts {
|
||||
}
|
||||
}
|
||||
|
||||
function writeLine() {
|
||||
if (!lineStart) {
|
||||
function writeLine(force?: boolean) {
|
||||
if (!lineStart || force) {
|
||||
output += newLine;
|
||||
lineCount++;
|
||||
linePos = output.length;
|
||||
@@ -3913,12 +3913,13 @@ namespace ts {
|
||||
}
|
||||
}
|
||||
|
||||
export function getLineOfLocalPosition(currentSourceFile: SourceFile, pos: number) {
|
||||
return getLineAndCharacterOfPosition(currentSourceFile, pos).line;
|
||||
export function getLineOfLocalPosition(sourceFile: SourceFile, pos: number) {
|
||||
const lineStarts = getLineStarts(sourceFile);
|
||||
return computeLineOfPosition(lineStarts, pos);
|
||||
}
|
||||
|
||||
export function getLineOfLocalPositionFromLineMap(lineMap: readonly number[], pos: number) {
|
||||
return computeLineAndCharacterOfPosition(lineMap, pos).line;
|
||||
return computeLineOfPosition(lineMap, pos);
|
||||
}
|
||||
|
||||
export function getFirstConstructorWithBody(node: ClassLikeDeclaration): ConstructorDeclaration & { body: FunctionBody } | undefined {
|
||||
@@ -4743,7 +4744,10 @@ namespace ts {
|
||||
}
|
||||
|
||||
export function rangeStartPositionsAreOnSameLine(range1: TextRange, range2: TextRange, sourceFile: SourceFile) {
|
||||
return positionsAreOnSameLine(getStartPositionOfRange(range1, sourceFile), getStartPositionOfRange(range2, sourceFile), sourceFile);
|
||||
return positionsAreOnSameLine(
|
||||
getStartPositionOfRange(range1, sourceFile, /*includeComments*/ false),
|
||||
getStartPositionOfRange(range2, sourceFile, /*includeComments*/ false),
|
||||
sourceFile);
|
||||
}
|
||||
|
||||
export function rangeEndPositionsAreOnSameLine(range1: TextRange, range2: TextRange, sourceFile: SourceFile) {
|
||||
@@ -4751,11 +4755,20 @@ namespace ts {
|
||||
}
|
||||
|
||||
export function rangeStartIsOnSameLineAsRangeEnd(range1: TextRange, range2: TextRange, sourceFile: SourceFile) {
|
||||
return positionsAreOnSameLine(getStartPositionOfRange(range1, sourceFile), range2.end, sourceFile);
|
||||
return positionsAreOnSameLine(getStartPositionOfRange(range1, sourceFile, /*includeComments*/ false), range2.end, sourceFile);
|
||||
}
|
||||
|
||||
export function rangeEndIsOnSameLineAsRangeStart(range1: TextRange, range2: TextRange, sourceFile: SourceFile) {
|
||||
return positionsAreOnSameLine(range1.end, getStartPositionOfRange(range2, sourceFile), sourceFile);
|
||||
return positionsAreOnSameLine(range1.end, getStartPositionOfRange(range2, sourceFile, /*includeComments*/ false), sourceFile);
|
||||
}
|
||||
|
||||
export function getLinesBetweenRangeEndAndRangeStart(range1: TextRange, range2: TextRange, sourceFile: SourceFile, includeSecondRangeComments: boolean) {
|
||||
const range2Start = getStartPositionOfRange(range2, sourceFile, includeSecondRangeComments);
|
||||
return getLinesBetweenPositions(sourceFile, range1.end, range2Start);
|
||||
}
|
||||
|
||||
export function getLinesBetweenRangeEndPositions(range1: TextRange, range2: TextRange, sourceFile: SourceFile) {
|
||||
return getLinesBetweenPositions(sourceFile, range1.end, range2.end);
|
||||
}
|
||||
|
||||
export function isNodeArrayMultiLine(list: NodeArray<Node>, sourceFile: SourceFile): boolean {
|
||||
@@ -4763,12 +4776,30 @@ namespace ts {
|
||||
}
|
||||
|
||||
export function positionsAreOnSameLine(pos1: number, pos2: number, sourceFile: SourceFile) {
|
||||
return pos1 === pos2 ||
|
||||
getLineOfLocalPosition(sourceFile, pos1) === getLineOfLocalPosition(sourceFile, pos2);
|
||||
return getLinesBetweenPositions(sourceFile, pos1, pos2) === 0;
|
||||
}
|
||||
|
||||
export function getStartPositionOfRange(range: TextRange, sourceFile: SourceFile) {
|
||||
return positionIsSynthesized(range.pos) ? -1 : skipTrivia(sourceFile.text, range.pos);
|
||||
export function getStartPositionOfRange(range: TextRange, sourceFile: SourceFile, includeComments: boolean) {
|
||||
return positionIsSynthesized(range.pos) ? -1 : skipTrivia(sourceFile.text, range.pos, /*stopAfterLineBreak*/ false, includeComments);
|
||||
}
|
||||
|
||||
export function getLinesBetweenPositionAndPrecedingNonWhitespaceCharacter(pos: number, sourceFile: SourceFile, includeComments?: boolean) {
|
||||
const startPos = skipTrivia(sourceFile.text, pos, /*stopAfterLineBreak*/ false, includeComments);
|
||||
const prevPos = getPreviousNonWhitespacePosition(startPos, sourceFile);
|
||||
return getLinesBetweenPositions(sourceFile, prevPos || 0, startPos);
|
||||
}
|
||||
|
||||
export function getLinesBetweenPositionAndNextNonWhitespaceCharacter(pos: number, sourceFile: SourceFile, includeComments?: boolean) {
|
||||
const nextPos = skipTrivia(sourceFile.text, pos, /*stopAfterLineBreak*/ false, includeComments);
|
||||
return getLinesBetweenPositions(sourceFile, pos, nextPos);
|
||||
}
|
||||
|
||||
function getPreviousNonWhitespacePosition(pos: number, sourceFile: SourceFile) {
|
||||
while (pos-- > 0) {
|
||||
if (!isWhiteSpaceLike(sourceFile.text.charCodeAt(pos))) {
|
||||
return pos;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -3306,7 +3306,7 @@ namespace FourSlash {
|
||||
}
|
||||
|
||||
public moveToNewFile(options: FourSlashInterface.MoveToNewFileOptions): void {
|
||||
assert(this.getRanges().length === 1);
|
||||
assert(this.getRanges().length === 1, "Must have exactly one fourslash range (source enclosed between '[|' and '|]' delimiters) in the source file");
|
||||
const range = this.getRanges()[0];
|
||||
const refactor = ts.find(this.getApplicableRefactors(range, { allowTextChangesInNewFiles: true }), r => r.name === "Move to a new file")!;
|
||||
assert(refactor.actions.length === 1);
|
||||
|
||||
@@ -70,7 +70,7 @@ namespace ts.formatting {
|
||||
* Formatter calls this function when rule adds or deletes new lines from the text
|
||||
* so indentation scope can adjust values of indentation and delta.
|
||||
*/
|
||||
recomputeIndentation(lineAddedByFormatting: boolean): void;
|
||||
recomputeIndentation(lineAddedByFormatting: boolean, parent: Node): void;
|
||||
}
|
||||
|
||||
export function formatOnEnter(position: number, sourceFile: SourceFile, formatContext: FormatContext): TextChange[] {
|
||||
@@ -567,8 +567,8 @@ namespace ts.formatting {
|
||||
!suppressDelta && shouldAddDelta(line, kind, container) ? indentation + getDelta(container) : indentation,
|
||||
getIndentation: () => indentation,
|
||||
getDelta,
|
||||
recomputeIndentation: lineAdded => {
|
||||
if (node.parent && SmartIndenter.shouldIndentChildNode(options, node.parent, node, sourceFile)) {
|
||||
recomputeIndentation: (lineAdded, parent) => {
|
||||
if (SmartIndenter.shouldIndentChildNode(options, parent, node, sourceFile)) {
|
||||
indentation += lineAdded ? options.indentSize! : -options.indentSize!;
|
||||
delta = SmartIndenter.shouldIndentChildNode(options, node) ? options.indentSize! : 0;
|
||||
}
|
||||
@@ -996,7 +996,7 @@ namespace ts.formatting {
|
||||
// Handle the case where the next line is moved to be the end of this line.
|
||||
// In this case we don't indent the next line in the next pass.
|
||||
if (currentParent.getStart(sourceFile) === currentItem.pos) {
|
||||
dynamicIndentation.recomputeIndentation(/*lineAddedByFormatting*/ false);
|
||||
dynamicIndentation.recomputeIndentation(/*lineAddedByFormatting*/ false, contextNode);
|
||||
}
|
||||
break;
|
||||
case LineAction.LineAdded:
|
||||
@@ -1004,7 +1004,7 @@ namespace ts.formatting {
|
||||
// In this case we indent token2 in the next pass but we set
|
||||
// sameLineIndent flag to notify the indenter that the indentation is within the line.
|
||||
if (currentParent.getStart(sourceFile) === currentItem.pos) {
|
||||
dynamicIndentation.recomputeIndentation(/*lineAddedByFormatting*/ true);
|
||||
dynamicIndentation.recomputeIndentation(/*lineAddedByFormatting*/ true, contextNode);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
|
||||
@@ -159,7 +159,7 @@ namespace ts.refactor {
|
||||
typeParameters.map(id => updateTypeParameterDeclaration(id, id.name, id.constraint, /* defaultType */ undefined)),
|
||||
selection
|
||||
);
|
||||
changes.insertNodeBefore(file, firstStatement, newTypeNode, /* blankLineBetween */ true);
|
||||
changes.insertNodeBefore(file, firstStatement, ignoreSourceNewlines(newTypeNode), /* blankLineBetween */ true);
|
||||
changes.replaceNode(file, selection, createTypeReferenceNode(name, typeParameters.map(id => createTypeReferenceNode(id.name, /* typeArguments */ undefined))));
|
||||
}
|
||||
|
||||
@@ -174,7 +174,7 @@ namespace ts.refactor {
|
||||
/* heritageClauses */ undefined,
|
||||
typeElements
|
||||
);
|
||||
changes.insertNodeBefore(file, firstStatement, newTypeNode, /* blankLineBetween */ true);
|
||||
changes.insertNodeBefore(file, firstStatement, ignoreSourceNewlines(newTypeNode), /* blankLineBetween */ true);
|
||||
changes.replaceNode(file, selection, createTypeReferenceNode(name, typeParameters.map(id => createTypeReferenceNode(id.name, /* typeArguments */ undefined))));
|
||||
}
|
||||
|
||||
|
||||
@@ -935,7 +935,7 @@ namespace ts.textChanges {
|
||||
export function getNonformattedText(node: Node, sourceFile: SourceFile | undefined, newLineCharacter: string): { text: string, node: Node } {
|
||||
const writer = createWriter(newLineCharacter);
|
||||
const newLine = newLineCharacter === "\n" ? NewLineKind.LineFeed : NewLineKind.CarriageReturnLineFeed;
|
||||
createPrinter({ newLine, neverAsciiEscape: true }, writer).writeNode(EmitHint.Unspecified, node, sourceFile, writer);
|
||||
createPrinter({ newLine, neverAsciiEscape: true, preserveSourceNewlines: true }, writer).writeNode(EmitHint.Unspecified, node, sourceFile, writer);
|
||||
return { text: writer.getText(), node: assignPositionsToNode(node) };
|
||||
}
|
||||
}
|
||||
@@ -1064,8 +1064,8 @@ namespace ts.textChanges {
|
||||
writer.writeSymbol(s, sym);
|
||||
setLastNonTriviaPosition(s, /*force*/ false);
|
||||
}
|
||||
function writeLine(): void {
|
||||
writer.writeLine();
|
||||
function writeLine(force?: boolean): void {
|
||||
writer.writeLine(force);
|
||||
}
|
||||
function increaseIndent(): void {
|
||||
writer.increaseIndent();
|
||||
|
||||
+2
-1
@@ -35,7 +35,8 @@ var y = {
|
||||
"typeof":
|
||||
};
|
||||
var x = (_a = {
|
||||
a: a, : .b,
|
||||
a: a,
|
||||
: .b,
|
||||
a: a
|
||||
},
|
||||
_a["ss"] = ,
|
||||
|
||||
@@ -25,7 +25,8 @@ var n;
|
||||
(function (n) {
|
||||
var z = 10000;
|
||||
n.y = {
|
||||
m: m, : .x // error
|
||||
m: m,
|
||||
: .x // error
|
||||
};
|
||||
})(n || (n = {}));
|
||||
m.y.x;
|
||||
|
||||
@@ -42,5 +42,6 @@ var C2 = /** @class */ (function () {
|
||||
return C2;
|
||||
}());
|
||||
var b = {
|
||||
x: function () { }, 1: // error
|
||||
x: function () { },
|
||||
1: // error
|
||||
};
|
||||
|
||||
@@ -3,5 +3,4 @@ var v = { a
|
||||
return;
|
||||
|
||||
//// [parserErrorRecovery_ObjectLiteral2.js]
|
||||
var v = { a: a,
|
||||
"return": };
|
||||
var v = { a: a, "return": };
|
||||
|
||||
@@ -10,8 +10,6 @@ edit.applyRefactor({
|
||||
actionDescription: "Extract to constant in enclosing scope",
|
||||
newContent:
|
||||
`declare function fWithThis(fn: (this: { a: string }, a: string) => string): void;
|
||||
const newLocal = function(this: {
|
||||
a: string;
|
||||
}, a: string): string { return this.a; };
|
||||
const newLocal = function(this: { a: string; }, a: string): string { return this.a; };
|
||||
fWithThis(/*RENAME*/newLocal);`
|
||||
});
|
||||
|
||||
@@ -24,16 +24,11 @@ type U = T; type V = I;`,
|
||||
"/x.ts":
|
||||
`export const x = 0;
|
||||
export function f() { }
|
||||
export class C {
|
||||
}
|
||||
export enum E {
|
||||
}
|
||||
export namespace N {
|
||||
export const x = 0;
|
||||
}
|
||||
export class C { }
|
||||
export enum E { }
|
||||
export namespace N { export const x = 0; }
|
||||
export type T = number;
|
||||
export interface I {
|
||||
}
|
||||
export interface I { }
|
||||
`,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -22,6 +22,5 @@ verify.codeFix({
|
||||
export function f() { }
|
||||
export function g() { }
|
||||
export function h() { }
|
||||
export class C {
|
||||
}`,
|
||||
export class C { }`,
|
||||
});
|
||||
|
||||
@@ -15,8 +15,6 @@ verify.codeFix({
|
||||
`var C = {};
|
||||
console.log(C);
|
||||
export async function* f(p) { p; }
|
||||
const _C = class C extends D {
|
||||
m() { }
|
||||
};
|
||||
const _C = class C extends D { m() { } };
|
||||
export { _C as C };`,
|
||||
});
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
/// <reference path="fourslash.ts" />
|
||||
|
||||
//// /*1*/console.log(1);
|
||||
////
|
||||
//// console.log(2);
|
||||
////
|
||||
//// console.log(3);/*2*/
|
||||
|
||||
goTo.select("1", "2");
|
||||
edit.applyRefactor({
|
||||
refactorName: "Extract Symbol",
|
||||
actionName: "function_scope_0",
|
||||
actionDescription: "Extract to function in global scope",
|
||||
newContent:
|
||||
`/*RENAME*/newFunction();
|
||||
|
||||
function newFunction() {
|
||||
console.log(1);
|
||||
|
||||
console.log(2);
|
||||
|
||||
console.log(3);
|
||||
}
|
||||
`
|
||||
});
|
||||
@@ -0,0 +1,29 @@
|
||||
/// <reference path="fourslash.ts" />
|
||||
|
||||
//// /*1*/1 +
|
||||
//// 2 +
|
||||
////
|
||||
//// 3 +
|
||||
////
|
||||
////
|
||||
//// 4;/*2*/
|
||||
|
||||
goTo.select("1", "2");
|
||||
edit.applyRefactor({
|
||||
refactorName: "Extract Symbol",
|
||||
actionName: "function_scope_0",
|
||||
actionDescription: "Extract to function in global scope",
|
||||
newContent:
|
||||
`/*RENAME*/newFunction();
|
||||
|
||||
function newFunction() {
|
||||
1 +
|
||||
2 +
|
||||
|
||||
3 +
|
||||
|
||||
|
||||
4;
|
||||
}
|
||||
`
|
||||
});
|
||||
@@ -0,0 +1,35 @@
|
||||
/// <reference path="fourslash.ts" />
|
||||
|
||||
//// /*1*/app
|
||||
//// .use(foo)
|
||||
////
|
||||
//// .use(bar)
|
||||
////
|
||||
////
|
||||
//// .use(
|
||||
//// baz,
|
||||
////
|
||||
//// blob);/*2*/
|
||||
|
||||
goTo.select("1", "2");
|
||||
edit.applyRefactor({
|
||||
refactorName: "Extract Symbol",
|
||||
actionName: "function_scope_0",
|
||||
actionDescription: "Extract to function in global scope",
|
||||
newContent:
|
||||
`/*RENAME*/newFunction();
|
||||
|
||||
function newFunction() {
|
||||
app
|
||||
.use(foo)
|
||||
|
||||
.use(bar)
|
||||
|
||||
|
||||
.use(
|
||||
baz,
|
||||
|
||||
blob);
|
||||
}
|
||||
`
|
||||
});
|
||||
@@ -0,0 +1,24 @@
|
||||
/// <reference path="fourslash.ts" />
|
||||
|
||||
//// const x = /*1*/function f()
|
||||
//// {
|
||||
////
|
||||
//// console.log();
|
||||
//// }/*2*/;
|
||||
|
||||
goTo.select("1", "2");
|
||||
edit.applyRefactor({
|
||||
refactorName: "Extract Symbol",
|
||||
actionName: "function_scope_0",
|
||||
actionDescription: "Extract to function in global scope",
|
||||
newContent:
|
||||
`const x = /*RENAME*/newFunction();
|
||||
|
||||
function newFunction() {
|
||||
return function f() {
|
||||
|
||||
console.log();
|
||||
};
|
||||
}
|
||||
`
|
||||
});
|
||||
@@ -0,0 +1,31 @@
|
||||
/// <reference path="fourslash.ts" />
|
||||
|
||||
//// /*1*/f // call expression
|
||||
//// (arg)(
|
||||
//// /** @type {number} */
|
||||
//// blah,
|
||||
////
|
||||
//// blah
|
||||
////
|
||||
//// );/*2*/
|
||||
|
||||
goTo.select("1", "2");
|
||||
edit.applyRefactor({
|
||||
refactorName: "Extract Symbol",
|
||||
actionName: "function_scope_0",
|
||||
actionDescription: "Extract to function in global scope",
|
||||
newContent:
|
||||
`/*RENAME*/newFunction();
|
||||
|
||||
function newFunction() {
|
||||
f // call expression
|
||||
(arg)(
|
||||
/** @type {number} */
|
||||
blah,
|
||||
|
||||
blah
|
||||
|
||||
);
|
||||
}
|
||||
`
|
||||
});
|
||||
@@ -0,0 +1,30 @@
|
||||
/// <reference path="fourslash.ts" />
|
||||
|
||||
//// /*1*/f // call expression
|
||||
//// (arg)(
|
||||
//// /** @type {number} */
|
||||
//// blah, /* another param */ blah // TODO: name variable not 'blah'
|
||||
////
|
||||
//// );/*2*/
|
||||
|
||||
goTo.select("1", "2");
|
||||
|
||||
// Note: the loss of `// TODO: name variable not 'blah'`
|
||||
// is not desirable, but not related to this test.
|
||||
edit.applyRefactor({
|
||||
refactorName: "Extract Symbol",
|
||||
actionName: "function_scope_0",
|
||||
actionDescription: "Extract to function in global scope",
|
||||
newContent:
|
||||
`/*RENAME*/newFunction();
|
||||
|
||||
function newFunction() {
|
||||
f // call expression
|
||||
(arg)(
|
||||
/** @type {number} */
|
||||
blah, /* another param */ blah
|
||||
|
||||
);
|
||||
}
|
||||
`
|
||||
});
|
||||
@@ -0,0 +1,44 @@
|
||||
/// <reference path="fourslash.ts" />
|
||||
|
||||
// @Filename: /index.tsx
|
||||
|
||||
////[|function Foo({ label }: { label: string }) {
|
||||
//// return (
|
||||
//// <div
|
||||
//// id="time-label-top-container">
|
||||
//// <div
|
||||
//// id="time-label-container"
|
||||
//// style={{
|
||||
//// marginRight: '10px',
|
||||
//// border: 'none',
|
||||
//// }}
|
||||
//// >
|
||||
//// <div className="currentTimeLabel">{label}</div>
|
||||
//// </div>
|
||||
//// </div>
|
||||
//// );
|
||||
////}|]
|
||||
|
||||
verify.moveToNewFile({
|
||||
newFileContents: {
|
||||
"/index.tsx": "",
|
||||
"/Foo.tsx":
|
||||
`function Foo({ label }: { label: string; }) {
|
||||
return (
|
||||
<div
|
||||
id="time-label-top-container">
|
||||
<div
|
||||
id="time-label-container"
|
||||
style={{
|
||||
marginRight: '10px',
|
||||
border: 'none',
|
||||
}}
|
||||
>
|
||||
<div className="currentTimeLabel">{label}</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
`
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user