Skip to content

Fix child element indentation in multiline lists #3827

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 34 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
82b2d19
indentation inheritance in call/new expression
saschanaz Jul 6, 2015
82bbf52
getPrecedingArgumentIndentationInMultiLineExpression
saschanaz Jul 6, 2015
90106b3
single line argument should not affect next indentation
saschanaz Jul 6, 2015
1ef9027
adding test for multi-line arguments
saschanaz Jul 6, 2015
f1308f2
adding some comments
saschanaz Jul 6, 2015
a301dc0
small fix
saschanaz Jul 6, 2015
ee9afc6
fix for template string literal
saschanaz Jul 10, 2015
4f622bb
test fix
saschanaz Jul 10, 2015
6c7cdb2
indentation lock
saschanaz Jul 10, 2015
d232290
indentation preventer
saschanaz Jul 11, 2015
3c0234d
removing position argument
saschanaz Jul 11, 2015
09287fb
nodes.pos
saschanaz Jul 14, 2015
a3fea2b
Merge https://github.com/Microsoft/TypeScript into shorterParamListIn…
saschanaz Jul 14, 2015
0bf0b22
formatting for more lists
saschanaz Jul 14, 2015
7155a13
getlistbyposition
saschanaz Jul 15, 2015
b07168f
listElementIndentationPrevented
saschanaz Jul 15, 2015
c455f92
remove todos
saschanaz Jul 15, 2015
e624488
name change
saschanaz Jul 15, 2015
ddeaf07
remove indentationLockable
saschanaz Jul 15, 2015
4ecdd98
quick fix for incorrect access
saschanaz Jul 15, 2015
8ba7aa0
Merge remote-tracking branch 'refs/remotes/Microsoft/master' into sho…
saschanaz Dec 31, 2015
957f116
simpler approach
saschanaz Jan 2, 2016
89669fc
pass test
saschanaz Jan 2, 2016
c137e50
shared nodeStretchesFromLine
saschanaz Jan 2, 2016
791220a
merge conflict
saschanaz Mar 30, 2016
b865cff
whitespace / filemode
saschanaz Mar 30, 2016
f52aa4b
filemode
saschanaz Mar 30, 2016
10e2a1b
Merge branch 'master' of https://github.com/Microsoft/TypeScript into…
saschanaz Jul 1, 2016
638305d
remove duplication in getListByPosition
saschanaz Jul 1, 2016
2fc9c73
linter issue fix
saschanaz Jul 29, 2016
6b27eaf
Merge branch 'master' of https://github.com/SaschaNaz/TypeScript into…
saschanaz Dec 16, 2016
4bf0c01
I->i
saschanaz Dec 16, 2016
dd6b90f
linter fix
saschanaz Dec 16, 2016
431152a
Merge branch 'master' of https://github.com/Microsoft/TypeScript into…
saschanaz Dec 29, 2016
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 14 additions & 11 deletions src/services/formatting/formatting.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ namespace ts.formatting {
*
*/
getDelta(child: TextRangeWithKind): number;
clearDelta(): void;
/**
* Formatter calls this function when rule adds or deletes new lines from the text
* so indentation scope can adjust values of indentation and delta.
Expand Down Expand Up @@ -166,7 +167,7 @@ namespace ts.formatting {
return current;
}

// Returns true if node is a element in some list in parent
// Returns true if node is an element in some list in parent
// i.e. parent is class declaration with the list of members and node is one of members.
function isListElement(parent: Node, node: Node): boolean {
switch (parent.kind) {
Expand Down Expand Up @@ -517,6 +518,9 @@ namespace ts.formatting {
},
getIndentation: () => indentation,
getDelta: child => getEffectiveDelta(delta, child),
clearDelta: () => {
delta = 0;
},
recomputeIndentation: lineAdded => {
if (node.parent && SmartIndenter.shouldIndentChildNode(node.parent, node)) {
if (lineAdded) {
Expand Down Expand Up @@ -590,8 +594,7 @@ namespace ts.formatting {
parentDynamicIndentation: DynamicIndentation,
parentStartLine: number,
undecoratedParentStartLine: number,
isListItem: boolean,
isFirstListItem?: boolean): number {
isListItem: boolean): number {

const childStartPos = child.getStart(sourceFile);

Expand Down Expand Up @@ -648,16 +651,12 @@ namespace ts.formatting {
}

const effectiveParentStartLine = child.kind === SyntaxKind.Decorator ? childStartLine : undecoratedParentStartLine;
const childIndentation = computeIndentation(child, childStartLine, childIndentationAmount, node, parentDynamicIndentation, effectiveParentStartLine);
const childIndentation = computeIndentation(child, childStartLine, childIndentationAmount, parent, parentDynamicIndentation, effectiveParentStartLine);

processNode(child, childContextNode, childStartLine, undecoratedChildStartLine, childIndentation.indentation, childIndentation.delta);

childContextNode = node;

if (isFirstListItem && parent.kind === SyntaxKind.ArrayLiteralExpression && inheritedIndentation === Constants.Unknown) {
inheritedIndentation = childIndentation.indentation;
}

return inheritedIndentation;
}

Expand Down Expand Up @@ -697,9 +696,13 @@ namespace ts.formatting {
}

let inheritedIndentation = Constants.Unknown;
for (let i = 0; i < nodes.length; i++) {
const child = nodes[i];
inheritedIndentation = processChildNode(child, inheritedIndentation, node, listDynamicIndentation, startLine, startLine, /*isListItem*/ true, /*isFirstListItem*/ i === 0);
let listDeltaRemoved = false;
for (const child of nodes) {
inheritedIndentation = processChildNode(child, inheritedIndentation, node, listDynamicIndentation, startLine, startLine, /*isListItem*/ true);
if (!listDeltaRemoved && SmartIndenter.nodeStretchesFromLine(child, startLine, sourceFile)) {
listDynamicIndentation.clearDelta();
listDeltaRemoved = true;
}
}

if (listEndToken !== SyntaxKind.Unknown) {
Expand Down
129 changes: 91 additions & 38 deletions src/services/formatting/smartIndenter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,10 +68,14 @@ namespace ts.formatting {
let indentationDelta: number;

while (current) {
if (positionBelongsToNode(current, position, sourceFile) && shouldIndentChildNode(current, previous)) {
if (positionBelongsToNode(current, position, sourceFile) &&
shouldIndentChildNode(current, previous)) {

currentStart = getStartLineAndCharacterForNode(current, sourceFile);

if (nextTokenIsCurlyBraceOnSameLineAsCursor(precedingToken, current, lineAtPosition, sourceFile)) {
if (listAtPositionPreventsIndentation(current, position, sourceFile) ||
nextTokenIsCurlyBraceOnSameLineAsCursor(precedingToken, current, lineAtPosition, sourceFile)) {

indentationDelta = 0;
}
else {
Expand Down Expand Up @@ -157,7 +161,10 @@ 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, current) &&
!parentAndChildShareLine &&
!containingListPreventsIndentation(current, sourceFile)) {

indentationDelta += options.indentSize;
}

Expand Down Expand Up @@ -264,49 +271,54 @@ namespace ts.formatting {
function getContainingList(node: Node, sourceFile: SourceFile): NodeArray<Node> {
if (node.parent) {
switch (node.parent.kind) {
case SyntaxKind.TypeReference:
if ((<TypeReferenceNode>node.parent).typeArguments &&
rangeContainsStartEnd((<TypeReferenceNode>node.parent).typeArguments, node.getStart(sourceFile), node.getEnd())) {
return (<TypeReferenceNode>node.parent).typeArguments;
}
break;
case SyntaxKind.ObjectLiteralExpression:
return (<ObjectLiteralExpression>node.parent).properties;
case SyntaxKind.ArrayLiteralExpression:
return (<ArrayLiteralExpression>node.parent).elements;
case SyntaxKind.FunctionDeclaration:
case SyntaxKind.FunctionExpression:
case SyntaxKind.ArrowFunction:
case SyntaxKind.MethodDeclaration:
case SyntaxKind.MethodSignature:
case SyntaxKind.CallSignature:
case SyntaxKind.ConstructSignature: {
const start = node.getStart(sourceFile);
if ((<SignatureDeclaration>node.parent).typeParameters &&
rangeContainsStartEnd((<SignatureDeclaration>node.parent).typeParameters, start, node.getEnd())) {
return (<SignatureDeclaration>node.parent).typeParameters;
}
if (rangeContainsStartEnd((<SignatureDeclaration>node.parent).parameters, start, node.getEnd())) {
return (<SignatureDeclaration>node.parent).parameters;
}
break;
default:
return getListByPosition(node.getStart(sourceFile), node.getEnd(), node.parent);
}
}
return undefined;
}

function getListByPosition(start: number, end: number, node: Node): NodeArray<Node> {
switch (node.kind) {
case SyntaxKind.TypeReference:
if ((<TypeReferenceNode>node).typeArguments &&
rangeContainsStartEnd((<TypeReferenceNode>node).typeArguments, start, end)) {
return (<TypeReferenceNode>node).typeArguments;
}
case SyntaxKind.NewExpression:
case SyntaxKind.CallExpression: {
const start = node.getStart(sourceFile);
if ((<CallExpression>node.parent).typeArguments &&
rangeContainsStartEnd((<CallExpression>node.parent).typeArguments, start, node.getEnd())) {
return (<CallExpression>node.parent).typeArguments;
}
if ((<CallExpression>node.parent).arguments &&
rangeContainsStartEnd((<CallExpression>node.parent).arguments, start, node.getEnd())) {
return (<CallExpression>node.parent).arguments;
}
break;
break;
case SyntaxKind.FunctionDeclaration:
case SyntaxKind.FunctionExpression:
case SyntaxKind.ArrowFunction:
case SyntaxKind.MethodDeclaration:
case SyntaxKind.MethodSignature:
case SyntaxKind.CallSignature:
case SyntaxKind.ConstructSignature: {
if ((<SignatureDeclaration>node).typeParameters &&
rangeContainsStartEnd((<SignatureDeclaration>node).typeParameters, start, end)) {
return (<SignatureDeclaration>node).typeParameters;
}
if (rangeContainsStartEnd((<SignatureDeclaration>node).parameters, start, end)) {
return (<SignatureDeclaration>node).parameters;
}
break;
}
case SyntaxKind.NewExpression:
case SyntaxKind.CallExpression: {
if ((<CallExpression>node).typeArguments &&
rangeContainsStartEnd((<CallExpression>node).typeArguments, start, end)) {
return (<CallExpression>node).typeArguments;
}
if ((<CallExpression>node).arguments &&
rangeContainsStartEnd((<CallExpression>node).arguments, start, end)) {
return (<CallExpression>node).arguments;
}
break;
}
}
return undefined;
}

function getActualIndentationForListItem(node: Node, sourceFile: SourceFile, options: EditorSettings): number {
Expand Down Expand Up @@ -425,6 +437,47 @@ namespace ts.formatting {
return findFirstNonWhitespaceCharacterAndColumn(startPos, endPos, sourceFile, options).column;
}

function listAtPositionPreventsIndentation(parent: Node, position: number, sourceFile: SourceFile) {
const list = getListByPosition(position, position, parent);
if (!list || !list.length) {
return false;
}
return listPreventsIndentation(list, sourceFile);
}

function containingListPreventsIndentation(listItem: Node, sourceFile: SourceFile) {
const list = getContainingList(listItem, sourceFile);
if (!list) {
return false;
}
return listPreventsIndentation(list, sourceFile);
}

function listPreventsIndentation(list: NodeArray<Node>, sourceFile: SourceFile) {
const listStartLine = sourceFile.getLineAndCharacterOfPosition(list.pos).line;

for (const child of list) {
if (nodeStretchesFromLine(child, listStartLine, sourceFile)) {
return true;
}
}

return false;
}

/* @internal */
export function nodeStretchesFromLine(node: Node, line: number, sourceFile: SourceFile) {
const childEndLine = sourceFile.getLineAndCharacterOfPosition(node.getEnd()).line;
if (childEndLine === line) {
return false;
}
const childStartLine = sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile)).line;
if (childStartLine !== line) {
return false;
}
return true;
}

function nodeContentIsAlwaysIndented(kind: SyntaxKind): boolean {
switch (kind) {
case SyntaxKind.ExpressionStatement:
Expand Down
2 changes: 1 addition & 1 deletion tests/cases/fourslash/chainedFunctionFunctionArgIndent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,4 @@ goTo.marker();
edit.insert("\n");
verify.indentationIs(4);
edit.insert("}");
verify.indentationIs(4); // keep arguments indented
verify.indentationIs(0); // arguments should be aligned with the preceding argument
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,25 @@

////foo({
////}, {/*1*/
////});/*2*/
/////*2*/
////});/*3*/

////[{
////}, {/*a1*/
/////*a2*/
////}];/*a3*/

format.document();
goTo.marker("1");
verify.currentLineContentIs("}, {");
goTo.marker("2");
verify.currentLineContentIs(" });");
verify.indentationIs(4);
goTo.marker("3");
verify.currentLineContentIs("});");

goTo.marker("a1");
verify.currentLineContentIs("}, {");
goTo.marker("a2");
verify.indentationIs(4);
goTo.marker("a3");
verify.currentLineContentIs("}];");
14 changes: 7 additions & 7 deletions tests/cases/fourslash/formatVariableDeclarationList.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,16 +25,16 @@ verify.currentLineContentIs(" return x;");
goTo.marker("5");
verify.currentLineContentIs("},");
goTo.marker("6");
verify.currentLineContentIs(" fun2 = (function(f) {");
verify.currentLineContentIs("fun2 = (function(f) {");
goTo.marker("7");
verify.currentLineContentIs(" var fun = function() {");
verify.currentLineContentIs(" var fun = function() {");
goTo.marker("8");
verify.currentLineContentIs(" console.log(f());");
verify.currentLineContentIs(" console.log(f());");
goTo.marker("9");
verify.currentLineContentIs(" },");
verify.currentLineContentIs(" },");
goTo.marker("10");
verify.currentLineContentIs(" x = 'Foo';");
verify.currentLineContentIs(" x = 'Foo';");
goTo.marker("11");
verify.currentLineContentIs(" return fun;");
verify.currentLineContentIs(" return fun;");
goTo.marker("12");
verify.currentLineContentIs(" }(fun1));");
verify.currentLineContentIs("}(fun1));");
48 changes: 48 additions & 0 deletions tests/cases/fourslash/formattingMultiLineArguments.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
///<reference path="fourslash.ts"/>

////foo({
////},
/////*1*/
////3);

////foo({},
/////*2*/
////3)/*3*/

////foo([
////],
////3, {/*4v*//*4n*/
////});

////foo({
////}, {
////bar: 3/*5v*//*5n*/
////});

////foo(`
////`,
////3, {
////})/*6*/


goTo.marker('1');
verify.indentationIs(0);
goTo.marker('2');
verify.indentationIs(4);
goTo.marker('3');
edit.insert(';');
verify.currentLineContentIs(' 3);');

goTo.marker('4n');
edit.insertLine("");
goTo.marker('4v');
verify.currentLineContentIs('3, {');

goTo.marker('5n');
edit.insertLine("");
goTo.marker('5v');
verify.currentLineContentIs(" bar: 3");

goTo.marker('6');
edit.insert(';');
verify.currentLineContentIs('});');