From 5fb5ff7bf3343a188d5645cdaf4bfa8f5c8d911a Mon Sep 17 00:00:00 2001 From: Arthur Ozga Date: Thu, 29 Mar 2018 15:40:23 -0700 Subject: [PATCH] add option for object literal indent --- src/services/formatting/formatting.ts | 12 ++--- src/services/formatting/smartIndenter.ts | 24 +++++---- src/services/textChanges.ts | 2 +- src/services/types.ts | 1 + tests/baselines/reference/api/typescript.d.ts | 1 + ...extract-method-formatting-objectliteral.ts | 50 +++++++++++++++++++ ...formattingObjectLiteralOpenCurlyNewline.ts | 16 ++++++ ...mattingObjectLiteralOpenCurlySingleLine.ts | 1 - 8 files changed, 89 insertions(+), 18 deletions(-) create mode 100644 tests/cases/fourslash/extract-method-formatting-objectliteral.ts diff --git a/src/services/formatting/formatting.ts b/src/services/formatting/formatting.ts index e1825a4411c..debec04536a 100644 --- a/src/services/formatting/formatting.ts +++ b/src/services/formatting/formatting.ts @@ -328,7 +328,7 @@ namespace ts.formatting { break; } - if (SmartIndenter.shouldIndentChildNode(n, child, sourceFile)) { + if (SmartIndenter.shouldIndentChildNode(options, n, child, sourceFile)) { return options.indentSize; } @@ -470,7 +470,7 @@ namespace ts.formatting { parentDynamicIndentation: DynamicIndentation, effectiveParentStartLine: number ): { indentation: number, delta: number } { - const delta = SmartIndenter.shouldIndentChildNode(node) ? options.indentSize : 0; + const delta = SmartIndenter.shouldIndentChildNode(options, node) ? options.indentSize : 0; if (effectiveParentStartLine === startLine) { // if node is located on the same line with the parent @@ -541,9 +541,9 @@ namespace ts.formatting { getIndentation: () => indentation, getDelta, recomputeIndentation: lineAdded => { - if (node.parent && SmartIndenter.shouldIndentChildNode(node.parent, node, sourceFile)) { + if (node.parent && SmartIndenter.shouldIndentChildNode(options, node.parent, node, sourceFile)) { indentation += lineAdded ? options.indentSize : -options.indentSize; - delta = SmartIndenter.shouldIndentChildNode(node) ? options.indentSize : 0; + delta = SmartIndenter.shouldIndentChildNode(options, node) ? options.indentSize : 0; } } }; @@ -581,9 +581,9 @@ namespace ts.formatting { && !(node.decorators && kind === getFirstNonDecoratorTokenOfNode(node)); } - function getDelta(child: Node) { + function getDelta(child: TextRangeWithKind) { // Delta value should be zero when the node explicitly prevents indentation of the child node - return SmartIndenter.nodeWillIndentChild(node, child, sourceFile, /*indentByDefault*/ true) ? delta : 0; + return SmartIndenter.nodeWillIndentChild(options, node, child, sourceFile, /*indentByDefault*/ true) ? delta : 0; } } diff --git a/src/services/formatting/smartIndenter.ts b/src/services/formatting/smartIndenter.ts index 99d57668b27..a82f11f239b 100644 --- a/src/services/formatting/smartIndenter.ts +++ b/src/services/formatting/smartIndenter.ts @@ -112,7 +112,7 @@ namespace ts.formatting { let previous: Node | undefined; let current = precedingToken; while (current) { - if (positionBelongsToNode(current, position, sourceFile) && shouldIndentChildNode(current, previous, sourceFile, /*isNextChild*/ true)) { + if (positionBelongsToNode(current, position, sourceFile) && shouldIndentChildNode(options, current, previous, sourceFile, /*isNextChild*/ true)) { const currentStart = getStartLineAndCharacterForNode(current, sourceFile); const nextTokenKind = nextTokenIsCurlyBraceOnSameLineAsCursor(precedingToken, current, lineAtPosition, sourceFile); const indentationDelta = nextTokenKind !== NextTokenKind.Unknown @@ -193,7 +193,7 @@ namespace ts.formatting { } // increase indentation if parent node wants its content to be indented and parent and child nodes don't start on the same line - if (shouldIndentChildNode(parent, current, sourceFile, isNextChild) && !parentAndChildShareLine) { + if (shouldIndentChildNode(options, parent, current, sourceFile, isNextChild) && !parentAndChildShareLine) { indentationDelta += options.indentSize; } @@ -531,18 +531,15 @@ namespace ts.formatting { return false; } - export function nodeWillIndentChild(parent: TextRangeWithKind, child: TextRangeWithKind | undefined, sourceFile: SourceFileLike | undefined, indentByDefault: boolean): boolean { + export function nodeWillIndentChild(settings: FormatCodeSettings | undefined, parent: TextRangeWithKind, child: TextRangeWithKind | undefined, sourceFile: SourceFileLike | undefined, indentByDefault: boolean): boolean { const childKind = child ? child.kind : SyntaxKind.Unknown; switch (parent.kind) { case SyntaxKind.VariableDeclaration: case SyntaxKind.PropertyAssignment: case SyntaxKind.ObjectLiteralExpression: - if (sourceFile && childKind === SyntaxKind.ObjectLiteralExpression) { - const childStart = skipTrivia(sourceFile.text, child.pos); - const startLine = sourceFile.getLineAndCharacterOfPosition(childStart).line; - const endLine = sourceFile.getLineAndCharacterOfPosition(child.end).line; - return startLine === endLine; + if (!settings.indentMultiLineObjectLiteralBeginningOnBlankLine && sourceFile && childKind === SyntaxKind.ObjectLiteralExpression) { + return rangeIsOnOneLine(sourceFile, child); } break; case SyntaxKind.DoStatement: @@ -596,9 +593,16 @@ namespace ts.formatting { * True when the parent node should indent the given child by an explicit rule. * @param isNextChild If true, we are judging indent of a hypothetical child *after* this one, not the current child. */ - export function shouldIndentChildNode(parent: TextRangeWithKind, child?: Node, sourceFile?: SourceFileLike, isNextChild = false): boolean { - return (nodeContentIsAlwaysIndented(parent.kind) || nodeWillIndentChild(parent, child, sourceFile, /*indentByDefault*/ false)) + export function shouldIndentChildNode(settings: FormatCodeSettings | undefined, parent: TextRangeWithKind, child?: Node, sourceFile?: SourceFileLike, isNextChild = false): boolean { + return (nodeContentIsAlwaysIndented(parent.kind) || nodeWillIndentChild(settings, parent, child, sourceFile, /*indentByDefault*/ false)) && !(isNextChild && child && isControlFlowEndingStatement(child.kind, parent)); } + + function rangeIsOnOneLine(sourceFile: SourceFileLike, range: TextRangeWithKind) { + const rangeStart = skipTrivia(sourceFile.text, range.pos); + const startLine = sourceFile.getLineAndCharacterOfPosition(rangeStart).line; + const endLine = sourceFile.getLineAndCharacterOfPosition(range.end).line; + return startLine === endLine; + } } } diff --git a/src/services/textChanges.ts b/src/services/textChanges.ts index 24ff0ba2812..6100727a962 100644 --- a/src/services/textChanges.ts +++ b/src/services/textChanges.ts @@ -670,7 +670,7 @@ namespace ts.textChanges { const delta = options.delta !== undefined ? options.delta - : formatting.SmartIndenter.shouldIndentChildNode(nodeIn) + : formatting.SmartIndenter.shouldIndentChildNode(formatContext.options, nodeIn) ? (formatOptions.indentSize || 0) : 0; const file: SourceFileLike = { text, getLineAndCharacterOfPosition(pos) { return getLineAndCharacterOfPosition(this, pos); } }; diff --git a/src/services/types.ts b/src/services/types.ts index af496cfeb11..7179d947cfd 100644 --- a/src/services/types.ts +++ b/src/services/types.ts @@ -629,6 +629,7 @@ namespace ts { placeOpenBraceOnNewLineForFunctions?: boolean; placeOpenBraceOnNewLineForControlBlocks?: boolean; insertSpaceBeforeTypeAnnotation?: boolean; + indentMultiLineObjectLiteralBeginningOnBlankLine?: boolean; } export interface DefinitionInfo { diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index f849dc7f664..19cf8f4f0fb 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -4654,6 +4654,7 @@ declare namespace ts { placeOpenBraceOnNewLineForFunctions?: boolean; placeOpenBraceOnNewLineForControlBlocks?: boolean; insertSpaceBeforeTypeAnnotation?: boolean; + indentMultiLineObjectLiteralBeginningOnBlankLine?: boolean; } interface DefinitionInfo { fileName: string; diff --git a/tests/cases/fourslash/extract-method-formatting-objectliteral.ts b/tests/cases/fourslash/extract-method-formatting-objectliteral.ts new file mode 100644 index 00000000000..cbde4540233 --- /dev/null +++ b/tests/cases/fourslash/extract-method-formatting-objectliteral.ts @@ -0,0 +1,50 @@ +/// + +//// +//// namespace M { +//// class C { +//// foo() { +//// /*a*/let x = {a:1}; +//// let y = { +//// b: 2 +//// }; +//// let z = +//// { +//// c: 3 +//// };/*b*/ +//// return x.a + y.b + z.c; +//// } +//// } +//// } +//// + +goTo.select('a', 'b'); +edit.applyRefactor({ + refactorName: "Extract Symbol", + actionName: "function_scope_1", + actionDescription: "Extract to method in class 'C'", + newContent: +` +namespace M { + class C { + foo() { + let { x, y, z } = this./*RENAME*/newMethod(); + return x.a + y.b + z.c; + } + + private newMethod() { + let x = { a: 1 }; + let y = { + b: 2 + }; + let z = { + c: 3 + }; + return { x, y, z }; + } + } +} +` +}); + + diff --git a/tests/cases/fourslash/formattingObjectLiteralOpenCurlyNewline.ts b/tests/cases/fourslash/formattingObjectLiteralOpenCurlyNewline.ts index da1594df31a..80faace2b09 100644 --- a/tests/cases/fourslash/formattingObjectLiteralOpenCurlyNewline.ts +++ b/tests/cases/fourslash/formattingObjectLiteralOpenCurlyNewline.ts @@ -26,3 +26,19 @@ var clear = }; ` ); + +format.setOption("indentMultiLineObjectLiteralBeginningOnBlankLine", true); +format.document(); +verify.currentFileContentIs( +` +var clear = + { + outerKey: + { + innerKey: 1, + innerKey2: + 2 + } + }; +` +); diff --git a/tests/cases/fourslash/formattingObjectLiteralOpenCurlySingleLine.ts b/tests/cases/fourslash/formattingObjectLiteralOpenCurlySingleLine.ts index ea82efa081e..27d1b9f51b2 100644 --- a/tests/cases/fourslash/formattingObjectLiteralOpenCurlySingleLine.ts +++ b/tests/cases/fourslash/formattingObjectLiteralOpenCurlySingleLine.ts @@ -20,4 +20,3 @@ let obj2 = { y: 10 }; ` ); -