diff --git a/src/services/formatting/formatting.ts b/src/services/formatting/formatting.ts index 4fa3b71a3a200..6c19ce01391e4 100644 --- a/src/services/formatting/formatting.ts +++ b/src/services/formatting/formatting.ts @@ -315,7 +315,7 @@ namespace ts.formatting { } /* @internal */ - export function formatNode(node: Node, sourceFileLike: SourceFileLike, languageVariant: LanguageVariant, initialIndentation: number, delta: number, rulesProvider: RulesProvider): TextChange[] { + export function formatNode(node: Node, sourceFileLike: SourceFileLike, languageVariant: LanguageVariant, initialIndentation: number, delta: number, rulesProvider: RulesProvider): TextChange[] { const range = { pos: 0, end: sourceFileLike.text.length }; return formatSpanWorker( range, @@ -536,7 +536,7 @@ namespace ts.formatting { } case SyntaxKind.OpenBracketToken: case SyntaxKind.CloseBracketToken: { - if (container.kind !== SyntaxKind.MappedType) { + if (container.kind !== SyntaxKind.MappedType && container.kind !== SyntaxKind.ElementAccessExpression) { return indentation; } break; @@ -691,6 +691,31 @@ namespace ts.formatting { return inheritedIndentation; } + /** + * In some case even when a node list did not start at the same line with its parent, it should be treated specially because + * of the "effective parent line" was indented. E.g. + * + * Promise + * .then( // this is the "lastIndentedLine" + * a, <- here should be indented + * ) + * + * Here the parent of argument list is the call expression itself, however the indentation of the argument list should carry + * over the indentation in line 1 (".then"). + */ + function shouldListInheritIndentationFromLastIndentedLine(listStartLine: number, parent: Node, nodes: NodeArray) { + if (lastIndentedLine !== listStartLine) { + return false; + } + + switch (parent.kind) { + case SyntaxKind.CallExpression: + return (parent).arguments === nodes; + default: + return false; + } + } + function processChildNodes(nodes: NodeArray, parent: Node, parentStartLine: number, @@ -713,8 +738,13 @@ namespace ts.formatting { else if (tokenInfo.token.kind === listStartToken) { // consume list start token startLine = sourceFile.getLineAndCharacterOfPosition(tokenInfo.token.pos).line; + + if (shouldListInheritIndentationFromLastIndentedLine(startLine, parent, nodes)) { + parentStartLine = lastIndentedLine; + } + const indentation = - computeIndentation(tokenInfo.token, startLine, Constants.Unknown, parent, parentDynamicIndentation, parentStartLine); + computeIndentation(tokenInfo.token, startLine, /*inheritedIndentation*/ Constants.Unknown, parent, parentDynamicIndentation, parentStartLine); listDynamicIndentation = getDynamicIndentation(parent, parentStartLine, indentation.indentation, indentation.delta); consumeTokenAndAdvanceScanner(tokenInfo, parent, listDynamicIndentation, parent); @@ -732,6 +762,13 @@ namespace ts.formatting { inheritedIndentation = processChildNode(child, inheritedIndentation, node, listDynamicIndentation, startLine, startLine, /*isListItem*/ true, /*isFirstListItem*/ i === 0); } + if (nodes.hasTrailingComma && formattingScanner.isOnToken()) { + const tokenInfo = formattingScanner.readTokenInfo(parent); + if (tokenInfo.token.kind === SyntaxKind.CommaToken) { + consumeTokenAndAdvanceScanner(tokenInfo, parent, listDynamicIndentation, parent); + } + } + if (listEndToken !== SyntaxKind.Unknown) { if (formattingScanner.isOnToken()) { const tokenInfo = formattingScanner.readTokenInfo(parent); diff --git a/tests/cases/fourslash/formattingOnChainedCallbacks2.ts b/tests/cases/fourslash/formattingOnChainedCallbacks2.ts new file mode 100644 index 0000000000000..381b44f84c0b1 --- /dev/null +++ b/tests/cases/fourslash/formattingOnChainedCallbacks2.ts @@ -0,0 +1,34 @@ +/// + +////Promise +//// .then( +/////*then1cb1*/cb +//// /*then1*/); + +////Promise +//// .then( +/////*then2cb1*/cb, +//// /*then2*/); + +////Promise +//// ["then"](/*then3_1*/ +/////*then3cb1*/cb, +//// /*then3*/); + +format.document(); +goTo.marker('then1cb1'); +verify.currentLineContentIs(" cb"); +goTo.marker('then1'); +verify.currentLineContentIs(" );"); + +goTo.marker('then2cb1'); +verify.currentLineContentIs(" cb,"); +goTo.marker('then2'); +verify.currentLineContentIs(" );"); + +goTo.marker('then3_1'); +verify.currentLineContentIs(` ["then"](`); +goTo.marker('then3cb1'); +verify.currentLineContentIs(" cb,"); +goTo.marker('then3'); +verify.currentLineContentIs(" );"); \ No newline at end of file diff --git a/tests/cases/fourslash/formattingOptionsChange.ts b/tests/cases/fourslash/formattingOptionsChange.ts index 0e731412ede3c..69b2253de3ee8 100644 --- a/tests/cases/fourslash/formattingOptionsChange.ts +++ b/tests/cases/fourslash/formattingOptionsChange.ts @@ -5,7 +5,7 @@ /////*insertSpaceBeforeAndAfterBinaryOperators*/1+2- 3 /////*insertSpaceAfterKeywordsInControlFlowStatements*/if (true) { } /////*insertSpaceAfterFunctionKeywordForAnonymousFunctions*/(function () { }) -/////*insertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis*/(1 ) +/////*insertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis*/(1 ); /////*insertSpaceAfterOpeningAndBeforeClosingNonemptyBrackets*/[1 ]; [ ]; []; [,]; /////*insertSpaceAfterOpeningAndBeforeClosingTemplateStringBraces*/`${1}`;`${ 1 }` /////*insertSpaceAfterTypeAssertion*/const bar = Thing.getFoo(); @@ -22,7 +22,7 @@ runTest("insertSpaceAfterSemicolonInForStatements", "for (i = 0; i; i++);", "for runTest("insertSpaceBeforeAndAfterBinaryOperators", "1 + 2 - 3", "1+2-3"); runTest("insertSpaceAfterKeywordsInControlFlowStatements", "if (true) { }", "if(true) { }"); runTest("insertSpaceAfterFunctionKeywordForAnonymousFunctions", "(function () { })", "(function() { })"); -runTest("insertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis", " ( 1 )", " (1)"); +runTest("insertSpaceAfterOpeningAndBeforeClosingNonemptyParenthesis", " ( 1 );", " (1);"); runTest("insertSpaceAfterOpeningAndBeforeClosingNonemptyBrackets", "[ 1 ];[];[];[ , ];", "[1];[];[];[,];"); runTest("insertSpaceAfterOpeningAndBeforeClosingTemplateStringBraces", "`${ 1 }`; `${ 1 }`", "`${1}`; `${1}`"); runTest("insertSpaceAfterTypeAssertion", "const bar = Thing.getFoo();", "const bar = Thing.getFoo();");