Skip to content
81 changes: 34 additions & 47 deletions src/services/formatting/formatting.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,8 @@ namespace ts.formatting {
* the first token in line so it should be indented
*/
interface DynamicIndentation {
getIndentationForToken(tokenLine: number, tokenKind: SyntaxKind): number;
getIndentationForComment(owningToken: SyntaxKind, tokenIndentation: number): number;
getIndentationForToken(tokenLine: number, tokenKind: SyntaxKind, container: Node): number;
getIndentationForComment(owningToken: SyntaxKind, tokenIndentation: number, container: Node): number;
/**
* Indentation for open and close tokens of the node if it is block or another node that needs special indentation
* ... {
Expand All @@ -54,7 +54,7 @@ namespace ts.formatting {
* so bar inherits indentation from foo and bar.delta will be 4
*
*/
getDelta(): number;
getDelta(child: TextRangeWithKind): number;
/**
* 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 @@ -282,19 +282,19 @@ namespace ts.formatting {
*/
function getOwnOrInheritedDelta(n: Node, options: FormatCodeOptions, sourceFile: SourceFile): number {
let previousLine = Constants.Unknown;
let childKind = SyntaxKind.Unknown;
let child: Node;
while (n) {
let line = sourceFile.getLineAndCharacterOfPosition(n.getStart(sourceFile)).line;
if (previousLine !== Constants.Unknown && line !== previousLine) {
break;
}

if (SmartIndenter.shouldIndentChildNode(n.kind, childKind)) {
if (SmartIndenter.shouldIndentChildNode(n, child)) {
return options.IndentSize;
}

previousLine = line;
childKind = n.kind;
child = n;
n = n.parent;
}
return 0;
Expand Down Expand Up @@ -386,34 +386,7 @@ namespace ts.formatting {
effectiveParentStartLine: number): Indentation {

let indentation = inheritedIndentation;
if (indentation === Constants.Unknown) {
if (isSomeBlock(node.kind)) {
// blocks should be indented in
// - other blocks
// - source file
// - switch\default clauses
if (isSomeBlock(parent.kind) ||
parent.kind === SyntaxKind.SourceFile ||
parent.kind === SyntaxKind.CaseClause ||
parent.kind === SyntaxKind.DefaultClause) {

indentation = parentDynamicIndentation.getIndentation() + parentDynamicIndentation.getDelta();
}
else {
indentation = parentDynamicIndentation.getIndentation();
}
}
else {
if (SmartIndenter.childStartsOnTheSameLineWithElseInIfStatement(parent, node, startLine, sourceFile)) {
indentation = parentDynamicIndentation.getIndentation();
}
else {
indentation = parentDynamicIndentation.getIndentation() + parentDynamicIndentation.getDelta();
}
}
}

var delta = SmartIndenter.shouldIndentChildNode(node.kind, SyntaxKind.Unknown) ? options.IndentSize : 0;
var delta = SmartIndenter.shouldIndentChildNode(node) ? options.IndentSize : 0;

if (effectiveParentStartLine === startLine) {
// if node is located on the same line with the parent
Expand All @@ -422,8 +395,17 @@ namespace ts.formatting {
indentation = startLine === lastIndentedLine
? indentationOnLastIndentedLine
: parentDynamicIndentation.getIndentation();
delta = Math.min(options.IndentSize, parentDynamicIndentation.getDelta() + delta);
delta = Math.min(options.IndentSize, parentDynamicIndentation.getDelta(node) + delta);
}
else if (indentation === Constants.Unknown) {
if (SmartIndenter.childStartsOnTheSameLineWithElseInIfStatement(parent, node, startLine, sourceFile)) {
indentation = parentDynamicIndentation.getIndentation();
}
else {
indentation = parentDynamicIndentation.getIndentation() + parentDynamicIndentation.getDelta(node);
}
}

return {
indentation,
delta
Expand Down Expand Up @@ -455,7 +437,7 @@ namespace ts.formatting {

function getDynamicIndentation(node: Node, nodeStartLine: number, indentation: number, delta: number): DynamicIndentation {
return {
getIndentationForComment: (kind, tokenIndentation) => {
getIndentationForComment: (kind, tokenIndentation, container) => {
switch (kind) {
// preceding comment to the token that closes the indentation scope inherits the indentation from the scope
// .. {
Expand All @@ -464,11 +446,11 @@ namespace ts.formatting {
case SyntaxKind.CloseBraceToken:
case SyntaxKind.CloseBracketToken:
case SyntaxKind.CloseParenToken:
return indentation + delta;
return indentation + getEffectiveDelta(delta, container);
}
return tokenIndentation !== Constants.Unknown ? tokenIndentation : indentation;
},
getIndentationForToken: (line, kind) => {
getIndentationForToken: (line, kind, container) => {
if (nodeStartLine !== line && node.decorators) {
if (kind === getFirstNonDecoratorTokenOfNode(node)) {
// if this token is the first token following the list of decorators, we do not need to indent
Expand All @@ -489,28 +471,33 @@ namespace ts.formatting {
return indentation;
default:
// if token line equals to the line of containing node (this is a first token in the node) - use node indentation
return nodeStartLine !== line ? indentation + delta : indentation;
return nodeStartLine !== line ? indentation + getEffectiveDelta(delta, container) : indentation;
}
},
getIndentation: () => indentation,
getDelta: () => delta,
getDelta: child => getEffectiveDelta(delta, child),
recomputeIndentation: lineAdded => {
if (node.parent && SmartIndenter.shouldIndentChildNode(node.parent.kind, node.kind)) {
if (node.parent && SmartIndenter.shouldIndentChildNode(node.parent, node)) {
if (lineAdded) {
indentation += options.IndentSize;
}
else {
indentation -= options.IndentSize;
}

if (SmartIndenter.shouldIndentChildNode(node.kind, SyntaxKind.Unknown)) {
if (SmartIndenter.shouldIndentChildNode(node)) {
delta = options.IndentSize;
}
else {
delta = 0;
}
}
},
}
}

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, true) ? delta : 0;
}
}

Expand Down Expand Up @@ -610,7 +597,7 @@ namespace ts.formatting {
// if child node is a token, it does not impact indentation, proceed it using parent indentation scope rules
let tokenInfo = formattingScanner.readTokenInfo(child);
Debug.assert(tokenInfo.token.end === child.end);
consumeTokenAndAdvanceScanner(tokenInfo, node, parentDynamicIndentation);
consumeTokenAndAdvanceScanner(tokenInfo, node, parentDynamicIndentation, child);
return inheritedIndentation;
}

Expand Down Expand Up @@ -679,7 +666,7 @@ namespace ts.formatting {
}
}

function consumeTokenAndAdvanceScanner(currentTokenInfo: TokenInfo, parent: Node, dynamicIndentation: DynamicIndentation): void {
function consumeTokenAndAdvanceScanner(currentTokenInfo: TokenInfo, parent: Node, dynamicIndentation: DynamicIndentation, container?: Node): void {
Debug.assert(rangeContainsRange(parent, currentTokenInfo.token));

let lastTriviaWasNewLine = formattingScanner.lastTrailingTriviaWasNewLine();
Expand Down Expand Up @@ -720,11 +707,11 @@ namespace ts.formatting {

if (indentToken) {
let tokenIndentation = (isTokenInRange && !rangeContainsError(currentTokenInfo.token)) ?
dynamicIndentation.getIndentationForToken(tokenStart.line, currentTokenInfo.token.kind) :
dynamicIndentation.getIndentationForToken(tokenStart.line, currentTokenInfo.token.kind, container) :
Constants.Unknown;

if (currentTokenInfo.leadingTrivia) {
let commentIndentation = dynamicIndentation.getIndentationForComment(currentTokenInfo.token.kind, tokenIndentation);
let commentIndentation = dynamicIndentation.getIndentationForComment(currentTokenInfo.token.kind, tokenIndentation, container);
let indentNextTokenOrTrivia = true;

for (let triviaItem of currentTokenInfo.leadingTrivia) {
Expand Down
27 changes: 16 additions & 11 deletions src/services/formatting/smartIndenter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ namespace ts.formatting {
let indentationDelta: number;

while (current) {
if (positionBelongsToNode(current, position, sourceFile) && shouldIndentChildNode(current.kind, previous ? previous.kind : SyntaxKind.Unknown)) {
if (positionBelongsToNode(current, position, sourceFile) && shouldIndentChildNode(current, previous)) {
currentStart = getStartLineAndCharacterForNode(current, sourceFile);

if (nextTokenIsCurlyBraceOnSameLineAsCursor(precedingToken, current, lineAtPosition, sourceFile)) {
Expand Down Expand Up @@ -153,7 +153,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.kind, current.kind) && !parentAndChildShareLine) {
if (shouldIndentChildNode(parent, current) && !parentAndChildShareLine) {
indentationDelta += options.IndentSize;
}

Expand Down Expand Up @@ -465,12 +465,10 @@ namespace ts.formatting {
}
return false;
}

export function shouldIndentChildNode(parent: SyntaxKind, child: SyntaxKind): boolean {
if (nodeContentIsAlwaysIndented(parent)) {
return true;
}
switch (parent) {

export function nodeWillIndentChild(parent: TextRangeWithKind, child: TextRangeWithKind, indentByDefault: boolean) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this should be marked as /@internal/

let childKind = child ? child.kind : SyntaxKind.Unknown;
switch (parent.kind) {
case SyntaxKind.DoStatement:
case SyntaxKind.WhileStatement:
case SyntaxKind.ForInStatement:
Expand All @@ -484,10 +482,17 @@ namespace ts.formatting {
case SyntaxKind.Constructor:
case SyntaxKind.GetAccessor:
case SyntaxKind.SetAccessor:
return child !== SyntaxKind.Block;
default:
return false;
return childKind !== SyntaxKind.Block;
}
// No explicit rule for given nodes so the result will follow the default value argument
return indentByDefault;
}

/*
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, false);
}
}
}