Skip to content

CSharp Style Object Literal Formatting #22849

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

Merged
merged 9 commits into from
Mar 30, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
12 changes: 6 additions & 6 deletions src/services/formatting/formatting.ts
Original file line number Diff line number Diff line change
Expand Up @@ -328,7 +328,7 @@ namespace ts.formatting {
break;
}

if (SmartIndenter.shouldIndentChildNode(n, child)) {
if (SmartIndenter.shouldIndentChildNode(options, n, child, sourceFile)) {
return options.indentSize;
}

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -514,7 +514,7 @@ namespace ts.formatting {
if ((<MethodDeclaration>node).asteriskToken) {
return SyntaxKind.AsteriskToken;
}
// falls through
// falls through
case SyntaxKind.PropertyDeclaration:
case SyntaxKind.Parameter:
return getNameOfDeclaration(<Declaration>node).kind;
Expand All @@ -541,9 +541,9 @@ namespace ts.formatting {
getIndentation: () => indentation,
getDelta,
recomputeIndentation: lineAdded => {
if (node.parent && SmartIndenter.shouldIndentChildNode(node.parent, node)) {
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;
}
}
};
Expand Down Expand Up @@ -583,7 +583,7 @@ namespace ts.formatting {

function getDelta(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(options, node, child, sourceFile, /*indentByDefault*/ true) ? delta : 0;
}
}

Expand Down
25 changes: 20 additions & 5 deletions src/services/formatting/smartIndenter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ namespace ts.formatting {
let previous: Node | undefined;
let current = precedingToken;
while (current) {
if (positionBelongsToNode(current, position, sourceFile) && shouldIndentChildNode(current, previous, /*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
Expand Down Expand Up @@ -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, isNextChild) && !parentAndChildShareLine) {
if (shouldIndentChildNode(options, parent, current, sourceFile, isNextChild) && !parentAndChildShareLine) {
indentationDelta += options.indentSize;
}

Expand Down Expand Up @@ -531,9 +531,17 @@ namespace ts.formatting {
return false;
}

export function nodeWillIndentChild(parent: TextRangeWithKind, child: TextRangeWithKind | 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 (!settings.indentMultiLineObjectLiteralBeginningOnBlankLine && sourceFile && childKind === SyntaxKind.ObjectLiteralExpression) {
return rangeIsOnOneLine(sourceFile, child);
}
break;
case SyntaxKind.DoStatement:
case SyntaxKind.WhileStatement:
case SyntaxKind.ForInStatement:
Expand Down Expand Up @@ -585,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?: TextRangeWithKind, isNextChild = false): boolean {
return (nodeContentIsAlwaysIndented(parent.kind) || nodeWillIndentChild(parent, child, /*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;
}
}
}
3 changes: 2 additions & 1 deletion src/services/textChanges.ts
Original file line number Diff line number Diff line change
Expand Up @@ -654,8 +654,9 @@ namespace ts.textChanges {
? indentation
: formatting.SmartIndenter.getIndentation(pos, sourceFile, formatOptions, prefix === newLineCharacter || getLineStartPositionForPosition(pos, sourceFile) === pos);
if (delta === undefined) {
delta = formatting.SmartIndenter.shouldIndentChildNode(nodeIn) ? (formatOptions.indentSize || 0) : 0;
delta = formatting.SmartIndenter.shouldIndentChildNode(formatContext.options, nodeIn) ? (formatOptions.indentSize || 0) : 0;
}

const file: SourceFileLike = { text, getLineAndCharacterOfPosition(pos) { return getLineAndCharacterOfPosition(this, pos); } };
const changes = formatting.formatNodeGivenIndentation(node, file, sourceFile.languageVariant, initialIndentation, delta, formatContext);
return applyChanges(text, changes);
Expand Down
1 change: 1 addition & 0 deletions src/services/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -630,6 +630,7 @@ namespace ts {
placeOpenBraceOnNewLineForFunctions?: boolean;
placeOpenBraceOnNewLineForControlBlocks?: boolean;
insertSpaceBeforeTypeAnnotation?: boolean;
indentMultiLineObjectLiteralBeginningOnBlankLine?: boolean;
}

export interface DefinitionInfo {
Expand Down
1 change: 1 addition & 0 deletions tests/baselines/reference/api/tsserverlibrary.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4406,6 +4406,7 @@ declare namespace ts {
placeOpenBraceOnNewLineForFunctions?: boolean;
placeOpenBraceOnNewLineForControlBlocks?: boolean;
insertSpaceBeforeTypeAnnotation?: boolean;
indentMultiLineObjectLiteralBeginningOnBlankLine?: boolean;
}
interface DefinitionInfo {
fileName: string;
Expand Down
1 change: 1 addition & 0 deletions tests/baselines/reference/api/typescript.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4659,6 +4659,7 @@ declare namespace ts {
placeOpenBraceOnNewLineForFunctions?: boolean;
placeOpenBraceOnNewLineForControlBlocks?: boolean;
insertSpaceBeforeTypeAnnotation?: boolean;
indentMultiLineObjectLiteralBeginningOnBlankLine?: boolean;
}
interface DefinitionInfo {
fileName: string;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/// <reference path='fourslash.ts' />

//// interface IPerson {
//// coordinate: {
//// x: number;
//// y: number;
//// }
//// }
//// class Person implements IPerson { }

verify.codeFix({
description: "Implement interface 'IPerson'",
newFileContent:
`interface IPerson {
coordinate: {
x: number;
y: number;
}
}
class Person implements IPerson {
coordinate: { x: number; y: number; };
}`,
});
50 changes: 50 additions & 0 deletions tests/cases/fourslash/extract-method-formatting-objectliteral.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/// <reference path='fourslash.ts' />

////
//// 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 };
}
}
}
`
});


44 changes: 44 additions & 0 deletions tests/cases/fourslash/formattingObjectLiteralOpenCurlyNewline.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/// <reference path='fourslash.ts' />

////
//// var clear =
//// {
//// outerKey:
//// {
//// innerKey: 1,
//// innerKey2:
//// 2
//// }
//// };
////

format.document();
verify.currentFileContentIs(
`
var clear =
{
outerKey:
{
innerKey: 1,
innerKey2:
2
}
};
`
);

format.setOption("indentMultiLineObjectLiteralBeginningOnBlankLine", true);
format.document();
verify.currentFileContentIs(
`
var clear =
{
outerKey:
{
innerKey: 1,
innerKey2:
2
}
};
`
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/// <reference path='fourslash.ts' />

////
//// var varName =/**/
////

goTo.marker();
edit.insert("\n{");
verify.currentFileContentIs(
`
var varName =
{
`
);

edit.insert("\na: 1");
format.document();
verify.currentFileContentIs(
`
var varName =
{
a: 1
`
);

edit.insert("\n};");

format.document();
verify.currentFileContentIs(
`
var varName =
{
a: 1
};
`
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/// <reference path='fourslash.ts' />

////
//// let obj1 =
//// { x: 10 };
////
//// let obj2 =
//// // leading trivia
//// { y: 10 };
////

format.document();
verify.currentFileContentIs(
`
let obj1 =
{ x: 10 };

let obj2 =
// leading trivia
{ y: 10 };
`
);