diff --git a/src/harness/fourslash.ts b/src/harness/fourslash.ts index 8f19b83c7ada5..7adaba1c41295 100644 --- a/src/harness/fourslash.ts +++ b/src/harness/fourslash.ts @@ -376,6 +376,7 @@ namespace FourSlash { insertSpaceAfterOpeningAndBeforeClosingTemplateStringBraces: false, insertSpaceAfterOpeningAndBeforeClosingJsxExpressionBraces: false, insertSpaceAfterTypeAssertion: false, + indentConditionalExpressionFalseBranch: false, placeOpenBraceOnNewLineForFunctions: false, placeOpenBraceOnNewLineForControlBlocks: false, }; diff --git a/src/server/protocol.ts b/src/server/protocol.ts index 4e474edd0c441..4058b18016ec9 100644 --- a/src/server/protocol.ts +++ b/src/server/protocol.ts @@ -2321,6 +2321,7 @@ namespace ts.server.protocol { insertSpaceAfterOpeningAndBeforeClosingJsxExpressionBraces?: boolean; insertSpaceAfterTypeAssertion?: boolean; insertSpaceBeforeFunctionParenthesis?: boolean; + indentConditionalExpressionFalseBranch?: boolean; placeOpenBraceOnNewLineForFunctions?: boolean; placeOpenBraceOnNewLineForControlBlocks?: boolean; } diff --git a/src/server/utilities.ts b/src/server/utilities.ts index 093958b60c55f..4cdbd2007ba3f 100644 --- a/src/server/utilities.ts +++ b/src/server/utilities.ts @@ -90,6 +90,7 @@ namespace ts.server { insertSpaceAfterOpeningAndBeforeClosingTemplateStringBraces: false, insertSpaceAfterOpeningAndBeforeClosingJsxExpressionBraces: false, insertSpaceBeforeFunctionParenthesis: false, + indentConditionalExpressionFalseBranch: false, placeOpenBraceOnNewLineForFunctions: false, placeOpenBraceOnNewLineForControlBlocks: false, }; diff --git a/src/services/formatting/formatting.ts b/src/services/formatting/formatting.ts index 531b768f6d80f..d0a0b80f80f09 100644 --- a/src/services/formatting/formatting.ts +++ b/src/services/formatting/formatting.ts @@ -303,7 +303,7 @@ namespace ts.formatting { break; } - if (SmartIndenter.shouldIndentChildNode(n, child)) { + if (SmartIndenter.shouldIndentChildNode(n, options, child)) { return options.indentSize; } @@ -443,7 +443,7 @@ namespace ts.formatting { effectiveParentStartLine: number): Indentation { let indentation = inheritedIndentation; - let delta = SmartIndenter.shouldIndentChildNode(node) ? options.indentSize : 0; + let delta = SmartIndenter.shouldIndentChildNode(node, options) ? options.indentSize : 0; if (effectiveParentStartLine === startLine) { // if node is located on the same line with the parent @@ -547,7 +547,7 @@ namespace ts.formatting { getIndentation: () => indentation, getDelta: child => getEffectiveDelta(delta, child), recomputeIndentation: lineAdded => { - if (node.parent && SmartIndenter.shouldIndentChildNode(node.parent, node)) { + if (node.parent && SmartIndenter.shouldIndentChildNode(node.parent, options, node)) { if (lineAdded) { indentation += options.indentSize; } @@ -555,7 +555,7 @@ namespace ts.formatting { indentation -= options.indentSize; } - if (SmartIndenter.shouldIndentChildNode(node)) { + if (SmartIndenter.shouldIndentChildNode(node, options)) { delta = options.indentSize; } else { @@ -567,7 +567,7 @@ namespace ts.formatting { function getEffectiveDelta(delta: number, child: TextRangeWithKind) { // Delta value should be zero when the node explicitly prevents indentation of the child node - return SmartIndenter.nodeWillIndentChild(node, child, /*indentByDefault*/ true) ? delta : 0; + return SmartIndenter.nodeWillIndentChild(node, child, /*indentByDefault*/ true, options) ? delta : 0; } } diff --git a/src/services/formatting/smartIndenter.ts b/src/services/formatting/smartIndenter.ts index 18b0479c85a30..d6af5ceeb626d 100644 --- a/src/services/formatting/smartIndenter.ts +++ b/src/services/formatting/smartIndenter.ts @@ -20,7 +20,7 @@ namespace ts.formatting { * when inserting some text after open brace we would like to get the value of indentation as if newline was already there. * However by default indentation at position | will be 0 so 'assumeNewLineBeforeCloseBrace' allows to override this behavior, */ - export function getIndentation(position: number, sourceFile: SourceFile, options: EditorSettings, assumeNewLineBeforeCloseBrace = false): number { + export function getIndentation(position: number, sourceFile: SourceFile, options: FormatCodeSettings, assumeNewLineBeforeCloseBrace = false): number { if (position > sourceFile.text.length) { return getBaseIndentation(options); // past EOF } @@ -80,7 +80,7 @@ namespace ts.formatting { let indentationDelta: number; while (current) { - if (positionBelongsToNode(current, position, sourceFile) && shouldIndentChildNode(current, previous)) { + if (positionBelongsToNode(current, position, sourceFile) && shouldIndentChildNode(current, options, previous)) { currentStart = getStartLineAndCharacterForNode(current, sourceFile); const nextTokenKind = nextTokenIsCurlyBraceOnSameLineAsCursor(precedingToken, current, lineAtPosition, sourceFile); @@ -131,7 +131,7 @@ namespace ts.formatting { ignoreActualIndentationRange: TextRange, indentationDelta: number, sourceFile: SourceFile, - options: EditorSettings): number { + options: FormatCodeSettings): number { let parent: Node = current.parent; let parentStart: LineAndCharacter; @@ -170,7 +170,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) && !parentAndChildShareLine) { + if (shouldIndentChildNode(parent, options, current) && !parentAndChildShareLine) { indentationDelta += options.indentSize; } @@ -463,7 +463,6 @@ namespace ts.formatting { case SyntaxKind.VariableDeclaration: case SyntaxKind.ExportAssignment: case SyntaxKind.ReturnStatement: - case SyntaxKind.ConditionalExpression: case SyntaxKind.ArrayBindingPattern: case SyntaxKind.ObjectBindingPattern: case SyntaxKind.JsxOpeningElement: @@ -488,7 +487,7 @@ namespace ts.formatting { } /* @internal */ - export function nodeWillIndentChild(parent: TextRangeWithKind, child: TextRangeWithKind, indentByDefault: boolean) { + export function nodeWillIndentChild(parent: TextRangeWithKind, child: TextRangeWithKind, indentByDefault: boolean, options: FormatCodeSettings) { const childKind = child ? child.kind : SyntaxKind.Unknown; switch (parent.kind) { case SyntaxKind.DoStatement: @@ -512,6 +511,8 @@ namespace ts.formatting { ((child).namedBindings && (child).namedBindings.kind !== SyntaxKind.NamedImports); case SyntaxKind.JsxElement: return childKind !== SyntaxKind.JsxClosingElement; + case SyntaxKind.ConditionalExpression: + return options.indentConditionalExpressionFalseBranch || (parent as ConditionalExpression).whenFalse !== child; } // No explicit rule for given nodes so the result will follow the default value argument return indentByDefault; @@ -520,8 +521,8 @@ namespace ts.formatting { /* Function returns true when the parent node should indent the given child by an explicit rule */ - export function shouldIndentChildNode(parent: TextRangeWithKind, child?: TextRangeWithKind): boolean { - return nodeContentIsAlwaysIndented(parent.kind) || nodeWillIndentChild(parent, child, /*indentByDefault*/ false); + export function shouldIndentChildNode(parent: TextRangeWithKind, options: FormatCodeSettings, child?: TextRangeWithKind): boolean { + return nodeContentIsAlwaysIndented(parent.kind) || nodeWillIndentChild(parent, child, /*indentByDefault*/ false, options); } } } diff --git a/src/services/textChanges.ts b/src/services/textChanges.ts index 5aa4ebca7d088..0b90645d16620 100644 --- a/src/services/textChanges.ts +++ b/src/services/textChanges.ts @@ -470,7 +470,7 @@ namespace ts.textChanges { const delta = change.options.delta !== undefined ? change.options.delta - : formatting.SmartIndenter.shouldIndentChildNode(change.node) + : formatting.SmartIndenter.shouldIndentChildNode(change.node, formatOptions) ? formatOptions.indentSize : 0; diff --git a/src/services/types.ts b/src/services/types.ts index e042276e65002..49858bd2b5601 100644 --- a/src/services/types.ts +++ b/src/services/types.ts @@ -250,7 +250,7 @@ namespace ts { getOutliningSpans(fileName: string): OutliningSpan[]; getTodoComments(fileName: string, descriptors: TodoCommentDescriptor[]): TodoComment[]; getBraceMatchingAtPosition(fileName: string, position: number): TextSpan[]; - getIndentationAtPosition(fileName: string, position: number, options: EditorOptions | EditorSettings): number; + getIndentationAtPosition(fileName: string, position: number, options: EditorOptions | FormatCodeSettings): number; getFormattingEditsForRange(fileName: string, start: number, end: number, options: FormatCodeOptions | FormatCodeSettings): TextChange[]; getFormattingEditsForDocument(fileName: string, options: FormatCodeOptions | FormatCodeSettings): TextChange[]; @@ -472,6 +472,7 @@ namespace ts { insertSpaceAfterOpeningAndBeforeClosingJsxExpressionBraces?: boolean; insertSpaceAfterTypeAssertion?: boolean; insertSpaceBeforeFunctionParenthesis?: boolean; + indentConditionalExpressionFalseBranch?: boolean; placeOpenBraceOnNewLineForFunctions?: boolean; placeOpenBraceOnNewLineForControlBlocks?: boolean; } diff --git a/tests/cases/fourslash/formatConditionalExpressions.ts b/tests/cases/fourslash/formatConditionalExpressions.ts new file mode 100644 index 0000000000000..f810c7e17d34d --- /dev/null +++ b/tests/cases/fourslash/formatConditionalExpressions.ts @@ -0,0 +1,18 @@ +/// + +////let v = +//// 0 ? 1 : +//// // +/////*falseBranchExpression*/ 2 ? 3 : +/////*indent*/ +//// // +/////*falseBranchToken*/ 2; + +format.document(); + +goTo.marker("falseBranchExpression"); +verify.currentLineContentIs(" 2 ? 3 :"); +goTo.marker("indent"); +verify.indentationIs(8); +goTo.marker("falseBranchToken"); +verify.currentLineContentIs(" 2;"); \ No newline at end of file diff --git a/tests/cases/fourslash/formattingOptionsChangeTernary.ts b/tests/cases/fourslash/formattingOptionsChangeTernary.ts new file mode 100644 index 0000000000000..8af415b9b74f1 --- /dev/null +++ b/tests/cases/fourslash/formattingOptionsChangeTernary.ts @@ -0,0 +1,44 @@ +/// + +////const expectedScanAction = +//// shouldRescanGreaterThanToken(n) +//// ? ScanAction.RescanGreaterThanToken +//// : shouldRescanSlashToken(n) +//// ? ScanAction.RescanSlashToken +//// : shouldRescanTemplateToken(n) +//// ? ScanAction.RescanTemplateToken +//// : shouldRescanJsxIdentifier(n) +//// ? ScanAction.RescanJsxIdentifier +//// : shouldRescanJsxText(n) +//// ? ScanAction.RescanJsxText +//// : ScanAction.Scan; + +format.setOption("indentConditionalExpressionFalseBranch", false); +format.document(); +verify.currentFileContentIs(`const expectedScanAction = + shouldRescanGreaterThanToken(n) + ? ScanAction.RescanGreaterThanToken + : shouldRescanSlashToken(n) + ? ScanAction.RescanSlashToken + : shouldRescanTemplateToken(n) + ? ScanAction.RescanTemplateToken + : shouldRescanJsxIdentifier(n) + ? ScanAction.RescanJsxIdentifier + : shouldRescanJsxText(n) + ? ScanAction.RescanJsxText + : ScanAction.Scan;`) + +format.setOption("indentConditionalExpressionFalseBranch", true); +format.document(); +verify.currentFileContentIs(`const expectedScanAction = + shouldRescanGreaterThanToken(n) + ? ScanAction.RescanGreaterThanToken + : shouldRescanSlashToken(n) + ? ScanAction.RescanSlashToken + : shouldRescanTemplateToken(n) + ? ScanAction.RescanTemplateToken + : shouldRescanJsxIdentifier(n) + ? ScanAction.RescanJsxIdentifier + : shouldRescanJsxText(n) + ? ScanAction.RescanJsxText + : ScanAction.Scan;`) \ No newline at end of file