Skip to content

Offload all jsx text indentation handling to indentMultilineCommentOrJsxText #1

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 1 commit into from
Feb 1, 2020
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
36 changes: 28 additions & 8 deletions src/services/formatting/formatting.ts
Original file line number Diff line number Diff line change
Expand Up @@ -501,9 +501,6 @@ namespace ts.formatting {
else if (SmartIndenter.argumentStartsOnSameLineAsPreviousArgument(parent, node, startLine, sourceFile)) {
return { indentation: parentDynamicIndentation.getIndentation(), delta };
}
else if (SmartIndenter.isJSXClosingWithMultiLineText(parent, node, startLine, sourceFile)) {
Copy link
Author

Choose a reason for hiding this comment

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

So this is where we were trying to compensate for the old bad scanner behavior; we'd essentially dedent (or at least choose not to increase the indent) in this case to try to compensate for the extra whitespace the scanner used to provide.

return { indentation: parentDynamicIndentation.getIndentation(), delta };
}
else {
return { indentation: parentDynamicIndentation.getIndentation() + parentDynamicIndentation.getDelta(node), delta };
}
Expand Down Expand Up @@ -652,6 +649,11 @@ namespace ts.formatting {
if (tokenInfo.token.end > node.end) {
break;
}
if (node.kind === SyntaxKind.JsxText) {
// Intentation rules for jsx text are handled by `indentMultilineCommentOrJsxText` inside `processChildNode`; just fastforward past it here
Copy link
Author

Choose a reason for hiding this comment

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

Notably, the formatting scanner actually doesn't handle scanning JSX correctly, like, at all; so the only thing that even makes sense is to fastforward over it - otherwise we do silly things like interpret words within the jsxtext as identifiers.

formattingScanner.advance();
continue;
}
consumeTokenAndAdvanceScanner(tokenInfo, node, nodeDynamicIndentation, node);
}

Expand Down Expand Up @@ -741,7 +743,20 @@ namespace ts.formatting {
processNode(child, childContextNode, childStartLine, undecoratedChildStartLine, childIndentation.indentation, childIndentation.delta);
if (child.kind === SyntaxKind.JsxText) {
const range: TextRange = { pos: child.getStart(), end: child.getEnd() };
indentMultilineCommentOrJsxText(range, childIndentation.indentation, /*firstLineIsIndented*/ true, /*indentFinalLine*/ false);
if (range.pos !== range.end) { // don't indent zero-width jsx text
const siblings = parent.getChildren(sourceFile);
const currentNode = find(siblings, arg => arg.pos === child.pos);
const currentIndex = siblings.indexOf(currentNode!);

Choose a reason for hiding this comment

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

Maybe findIndex?

const previousNode = siblings[currentIndex - 1];
if (previousNode) {
Copy link
Author

Choose a reason for hiding this comment

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

I'm... unclear on why this can happen, but formatTsx produces a case where you have a JsxText node, and it has no previous sibling. Which should be impossible, as it should always have an associated opening tag. The input does have a syntax error, so that's probably why.

// The jsx text needs no indentation whatsoever if it ends on the same line the previous sibling ends on
if (sourceFile.getLineAndCharacterOfPosition(range.end).line !== sourceFile.getLineAndCharacterOfPosition(previousNode.end).line) {
// The first line is (already) "indented" if the text starts on the same line as the previous sibling element ends on
const firstLineIsIndented = sourceFile.getLineAndCharacterOfPosition(range.pos).line === sourceFile.getLineAndCharacterOfPosition(previousNode.end).line;
indentMultilineCommentOrJsxText(range, childIndentation.indentation, firstLineIsIndented, /*indentFinalLine*/ false, /*jsxStyle*/ true);
}
}
}
}

childContextNode = node;
Expand Down Expand Up @@ -1041,7 +1056,7 @@ namespace ts.formatting {
return indentationString !== sourceFile.text.substr(startLinePosition, indentationString.length);
}

function indentMultilineCommentOrJsxText(commentRange: TextRange, indentation: number, firstLineIsIndented: boolean, indentFinalLine = true) {
function indentMultilineCommentOrJsxText(commentRange: TextRange, indentation: number, firstLineIsIndented: boolean, indentFinalLine = true, jsxTextStyleIndent?: boolean) {
// split comment in lines
let startLine = sourceFile.getLineAndCharacterOfPosition(commentRange.pos).line;
const endLine = sourceFile.getLineAndCharacterOfPosition(commentRange.end).line;
Expand Down Expand Up @@ -1072,7 +1087,7 @@ namespace ts.formatting {
const nonWhitespaceColumnInFirstPart =
SmartIndenter.findFirstNonWhitespaceCharacterAndColumn(startLinePos, parts[0].pos, sourceFile, options);

if (indentation === nonWhitespaceColumnInFirstPart.column) {
if (indentation === nonWhitespaceColumnInFirstPart.column && !jsxTextStyleIndent) {
Copy link
Author

Choose a reason for hiding this comment

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

Do note - if we choose to remove this line because we prefer the whitespace-preserving style, then we can revert this line, too.

return;
}

Expand All @@ -1083,14 +1098,19 @@ namespace ts.formatting {
}

// shift all parts on the delta size
const delta = indentation - nonWhitespaceColumnInFirstPart.column;
let delta = indentation - nonWhitespaceColumnInFirstPart.column;
for (let i = startIndex; i < parts.length; i++ , startLine++) {
const startLinePos = getStartPositionOfLine(startLine, sourceFile);
const nonWhitespaceCharacterAndColumn =
i === 0
? nonWhitespaceColumnInFirstPart
: SmartIndenter.findFirstNonWhitespaceCharacterAndColumn(parts[i].pos, parts[i].end, sourceFile, options);

if (jsxTextStyleIndent) {
// skip adding indentation to blank lines
Copy link
Author

Choose a reason for hiding this comment

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

So for comments we actually add trailing whitespace, apparently. This seems silly, and causes some jsx text formatting baselines to fail, so we choose not to emit any whitespace on empty lines here to avoid emitting that trailing whitespace.

if (isLineBreak(sourceFile.text.charCodeAt(getStartPositionOfLine(startLine, sourceFile)))) continue;
// reset delta on every line
delta = indentation - nonWhitespaceCharacterAndColumn.column;
}
const newIndentation = nonWhitespaceCharacterAndColumn.column + delta;
if (newIndentation > 0) {
const indentationString = getIndentationString(newIndentation, options);
Expand Down
18 changes: 0 additions & 18 deletions src/services/formatting/smartIndenter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -343,24 +343,6 @@ namespace ts.formatting {
return false;
}

export function isJSXClosingWithMultiLineText(parent: Node, child: TextRangeWithKind, _childStartLine: number, sourceFile: SourceFileLike): boolean {
Copy link
Author

Choose a reason for hiding this comment

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

Most of this logic is just now directly in processNode instead.

if (isJsxElement(parent) && isParenthesizedExpression(parent.parent)) {
const siblings = parent.getChildren(sourceFile);
const currentNode = find(siblings, arg => arg.pos === child.pos);
if (!currentNode) return false;

const currentIndex = siblings.indexOf(currentNode);
if (currentIndex === 0) return false;

const previousNode = siblings[currentIndex - 1];
const startLineOfPreviousNode = getLineAndCharacterOfPosition(sourceFile, previousNode.getStart()).line;
const endLineOfPreviousNode = getLineAndCharacterOfPosition(sourceFile, previousNode.getEnd()).line;

return startLineOfPreviousNode !== endLineOfPreviousNode;
}
return false;
}

export function getContainingList(node: Node, sourceFile: SourceFile): NodeArray<Node> | undefined {
return node.parent && getListByRange(node.getStart(sourceFile), node.getEnd(), node.parent, sourceFile);
}
Expand Down