diff --git a/src/compiler/emitter.ts b/src/compiler/emitter.ts index c7bb8da3843..dd5fa857441 100644 --- a/src/compiler/emitter.ts +++ b/src/compiler/emitter.ts @@ -842,6 +842,7 @@ namespace ts { let tempFlags: TempFlags; // TempFlags for the current name generation scope. let reservedNamesStack: Map[]; // Stack of TempFlags reserved in enclosing name generation scopes. let reservedNames: Map; // TempFlags to reserve in nested name generation scopes. + let preserveNewlines = printerOptions.preserveNewlines; // Can be overridden inside nodes with the `IgnoreSourceNewlines` emit flag. let writer: EmitTextWriter; let ownWriter: EmitTextWriter; // Reusable `EmitTextWriter` for basic printing. @@ -1164,8 +1165,12 @@ namespace ts { function pipelineEmit(emitHint: EmitHint, node: Node) { const savedLastNode = lastNode; const savedLastSubstitution = lastSubstitution; + const savedPreserveNewlines = preserveNewlines; lastNode = node; lastSubstitution = undefined; + if (preserveNewlines && !!(getEmitFlags(node) & EmitFlags.IgnoreSourceNewlines)) { + preserveNewlines = false; + } const pipelinePhase = getPipelinePhase(PipelinePhase.Notification, emitHint, node); pipelinePhase(emitHint, node); @@ -1175,6 +1180,7 @@ namespace ts { const substitute = lastSubstitution; lastNode = savedLastNode; lastSubstitution = savedLastSubstitution; + preserveNewlines = savedPreserveNewlines; return substitute || node; } @@ -3991,7 +3997,7 @@ namespace ts { if (isEmpty) { // Write a line terminator if the parent node was multi-line - if (format & ListFormat.MultiLine) { + if (format & ListFormat.MultiLine && !(preserveNewlines && rangeIsOnSingleLine(parentNode, currentSourceFile!))) { writeLine(); } else if (format & ListFormat.SpaceBetweenBraces && !(format & ListFormat.NoSpaceIfEmpty)) { @@ -4262,7 +4268,7 @@ namespace ts { } function getLeadingLineTerminatorCount(parentNode: TextRange, children: NodeArray, format: ListFormat): number { - if (format & ListFormat.PreserveLines || printerOptions.preserveNewlines) { + if (format & ListFormat.PreserveLines || preserveNewlines) { if (format & ListFormat.PreferNewLine) { return 1; } @@ -4283,7 +4289,7 @@ namespace ts { } function getSeparatingLineTerminatorCount(previousNode: Node | undefined, nextNode: Node, format: ListFormat): number { - if (format & ListFormat.PreserveLines || printerOptions.preserveNewlines) { + if (format & ListFormat.PreserveLines || preserveNewlines) { if (previousNode === undefined || nextNode === undefined) { return 0; } @@ -4302,7 +4308,7 @@ namespace ts { } function getClosingLineTerminatorCount(parentNode: TextRange, children: NodeArray, format: ListFormat): number { - if (format & ListFormat.PreserveLines || printerOptions.preserveNewlines) { + if (format & ListFormat.PreserveLines || preserveNewlines) { if (format & ListFormat.PreferNewLine) { return 1; } diff --git a/src/compiler/factoryPublic.ts b/src/compiler/factoryPublic.ts index 4479e6c2e71..90ff58e0a1a 100644 --- a/src/compiler/factoryPublic.ts +++ b/src/compiler/factoryPublic.ts @@ -3559,6 +3559,11 @@ namespace ts { return node; } + export function ignoreSourceNewlines(node: T): T { + getOrCreateEmitNode(node).flags |= EmitFlags.IgnoreSourceNewlines; + return node; + } + /** * Gets the constant value to emit for an expression. */ diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 3743b89dc99..a6a16eebeac 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -5780,6 +5780,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.preserveNewlines` to print this node (and all descendants) with default whitespace. } export interface EmitHelper { diff --git a/src/services/formatting/formatting.ts b/src/services/formatting/formatting.ts index d7d8d1bcb77..572596cde15 100644 --- a/src/services/formatting/formatting.ts +++ b/src/services/formatting/formatting.ts @@ -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[] { @@ -565,8 +565,9 @@ 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, parentIn) => { + const parent = node.parent || parentIn; + if (node !== parent && SmartIndenter.shouldIndentChildNode(options, parent, node, sourceFile)) { indentation += lineAdded ? options.indentSize! : -options.indentSize!; delta = SmartIndenter.shouldIndentChildNode(options, node) ? options.indentSize! : 0; } @@ -991,7 +992,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, currentParent); } break; case LineAction.LineAdded: @@ -999,7 +1000,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, currentParent); } break; default: diff --git a/src/services/refactors/extractType.ts b/src/services/refactors/extractType.ts index 70934ef7a7c..c6886dba28d 100644 --- a/src/services/refactors/extractType.ts +++ b/src/services/refactors/extractType.ts @@ -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)))); } diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index dbc336fccce..d3ea5796bfa 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -4359,6 +4359,7 @@ declare namespace ts { function setSyntheticTrailingComments(node: T, comments: SynthesizedComment[] | undefined): T; function addSyntheticTrailingComment(node: T, kind: SyntaxKind.SingleLineCommentTrivia | SyntaxKind.MultiLineCommentTrivia, text: string, hasTrailingNewLine?: boolean): T; function moveSyntheticComments(node: T, original: Node): T; + function ignoreSourceNewlines(node: T): T; /** * Gets the constant value to emit for an expression. */ diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index 8b600f90fdd..44e11abdb59 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -4359,6 +4359,7 @@ declare namespace ts { function setSyntheticTrailingComments(node: T, comments: SynthesizedComment[] | undefined): T; function addSyntheticTrailingComment(node: T, kind: SyntaxKind.SingleLineCommentTrivia | SyntaxKind.MultiLineCommentTrivia, text: string, hasTrailingNewLine?: boolean): T; function moveSyntheticComments(node: T, original: Node): T; + function ignoreSourceNewlines(node: T): T; /** * Gets the constant value to emit for an expression. */ diff --git a/tests/baselines/reference/objectLiteralShorthandPropertiesErrorFromNotUsingIdentifier.js b/tests/baselines/reference/objectLiteralShorthandPropertiesErrorFromNotUsingIdentifier.js index e0aecb5f188..33ae1f6c8f0 100644 --- a/tests/baselines/reference/objectLiteralShorthandPropertiesErrorFromNotUsingIdentifier.js +++ b/tests/baselines/reference/objectLiteralShorthandPropertiesErrorFromNotUsingIdentifier.js @@ -35,7 +35,8 @@ var y = { "typeof": }; var x = (_a = { - a: a, : .b, + a: a, + : .b, a: a }, _a["ss"] = , diff --git a/tests/baselines/reference/objectLiteralShorthandPropertiesErrorWithModule.js b/tests/baselines/reference/objectLiteralShorthandPropertiesErrorWithModule.js index ee46d4741dd..f48a6c3a4e5 100644 --- a/tests/baselines/reference/objectLiteralShorthandPropertiesErrorWithModule.js +++ b/tests/baselines/reference/objectLiteralShorthandPropertiesErrorWithModule.js @@ -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; diff --git a/tests/baselines/reference/objectTypesWithOptionalProperties2.js b/tests/baselines/reference/objectTypesWithOptionalProperties2.js index 03d2b162847..284ecdea92c 100644 --- a/tests/baselines/reference/objectTypesWithOptionalProperties2.js +++ b/tests/baselines/reference/objectTypesWithOptionalProperties2.js @@ -42,5 +42,6 @@ var C2 = /** @class */ (function () { return C2; }()); var b = { - x: function () { }, 1: // error + x: function () { }, + 1: // error }; diff --git a/tests/baselines/reference/parserErrorRecovery_ObjectLiteral2.js b/tests/baselines/reference/parserErrorRecovery_ObjectLiteral2.js index 1cdf73ddadc..6d723a00d4a 100644 --- a/tests/baselines/reference/parserErrorRecovery_ObjectLiteral2.js +++ b/tests/baselines/reference/parserErrorRecovery_ObjectLiteral2.js @@ -3,5 +3,4 @@ var v = { a return; //// [parserErrorRecovery_ObjectLiteral2.js] -var v = { a: a, - "return": }; +var v = { a: a, "return": }; diff --git a/tests/cases/fourslash/extract-const-callback-function-this3.ts b/tests/cases/fourslash/extract-const-callback-function-this3.ts index 1d9d8ba59e7..099229a11ba 100644 --- a/tests/cases/fourslash/extract-const-callback-function-this3.ts +++ b/tests/cases/fourslash/extract-const-callback-function-this3.ts @@ -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);` }); diff --git a/tests/cases/fourslash/moveToNewFile_declarationKinds.ts b/tests/cases/fourslash/moveToNewFile_declarationKinds.ts index 90c5eb6973a..4e9a0511086 100644 --- a/tests/cases/fourslash/moveToNewFile_declarationKinds.ts +++ b/tests/cases/fourslash/moveToNewFile_declarationKinds.ts @@ -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 { } `, }, }); diff --git a/tests/cases/fourslash/refactorConvertToEs6Module_export_object.ts b/tests/cases/fourslash/refactorConvertToEs6Module_export_object.ts index 97056ceefda..664796b1172 100644 --- a/tests/cases/fourslash/refactorConvertToEs6Module_export_object.ts +++ b/tests/cases/fourslash/refactorConvertToEs6Module_export_object.ts @@ -22,6 +22,5 @@ verify.codeFix({ export function f() { } export function g() { } export function h() { } -export class C { -}`, +export class C { }`, }); diff --git a/tests/cases/fourslash/refactorConvertToEs6Module_expressionToDeclaration.ts b/tests/cases/fourslash/refactorConvertToEs6Module_expressionToDeclaration.ts index c91c726dbfd..73dcff28285 100644 --- a/tests/cases/fourslash/refactorConvertToEs6Module_expressionToDeclaration.ts +++ b/tests/cases/fourslash/refactorConvertToEs6Module_expressionToDeclaration.ts @@ -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 };`, });