mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-05-19 01:33:15 -05:00
Merge pull request #28340 from saschanaz/listindent-revive
List position based formatting
This commit is contained in:
@@ -34,7 +34,7 @@ namespace ts.formatting {
|
||||
* the first token in line so it should be indented
|
||||
*/
|
||||
interface DynamicIndentation {
|
||||
getIndentationForToken(tokenLine: number, tokenKind: SyntaxKind, container: Node): number;
|
||||
getIndentationForToken(tokenLine: number, tokenKind: SyntaxKind, container: Node, suppressDelta: boolean): 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
|
||||
@@ -398,7 +398,7 @@ namespace ts.formatting {
|
||||
let previousRangeStartLine: number;
|
||||
|
||||
let lastIndentedLine: number;
|
||||
let indentationOnLastIndentedLine: number;
|
||||
let indentationOnLastIndentedLine = Constants.Unknown;
|
||||
|
||||
const edits: TextChange[] = [];
|
||||
|
||||
@@ -539,8 +539,18 @@ namespace ts.formatting {
|
||||
}
|
||||
return tokenIndentation !== Constants.Unknown ? tokenIndentation : indentation;
|
||||
},
|
||||
getIndentationForToken: (line, kind, container) =>
|
||||
shouldAddDelta(line, kind, container) ? indentation + getDelta(container) : indentation,
|
||||
// if list end token is LessThanToken '>' then its delta should be explicitly suppressed
|
||||
// so that LessThanToken as a binary operator can still be indented.
|
||||
// foo.then
|
||||
// <
|
||||
// number,
|
||||
// string,
|
||||
// >();
|
||||
// vs
|
||||
// var a = xValue
|
||||
// > yValue;
|
||||
getIndentationForToken: (line, kind, container, suppressDelta) =>
|
||||
!suppressDelta && shouldAddDelta(line, kind, container) ? indentation + getDelta(container) : indentation,
|
||||
getIndentation: () => indentation,
|
||||
getDelta,
|
||||
recomputeIndentation: lineAdded => {
|
||||
@@ -556,7 +566,6 @@ namespace ts.formatting {
|
||||
// open and close brace, 'else' and 'while' (in do statement) tokens has indentation of the parent
|
||||
case SyntaxKind.OpenBraceToken:
|
||||
case SyntaxKind.CloseBraceToken:
|
||||
case SyntaxKind.OpenParenToken:
|
||||
case SyntaxKind.CloseParenToken:
|
||||
case SyntaxKind.ElseKeyword:
|
||||
case SyntaxKind.WhileKeyword:
|
||||
@@ -737,11 +746,23 @@ namespace ts.formatting {
|
||||
else if (tokenInfo.token.kind === listStartToken) {
|
||||
// consume list start token
|
||||
startLine = sourceFile.getLineAndCharacterOfPosition(tokenInfo.token.pos).line;
|
||||
const indentation =
|
||||
computeIndentation(tokenInfo.token, startLine, Constants.Unknown, parent, parentDynamicIndentation, parentStartLine);
|
||||
|
||||
listDynamicIndentation = getDynamicIndentation(parent, parentStartLine, indentation.indentation, indentation.delta);
|
||||
consumeTokenAndAdvanceScanner(tokenInfo, parent, listDynamicIndentation, parent);
|
||||
consumeTokenAndAdvanceScanner(tokenInfo, parent, parentDynamicIndentation, parent);
|
||||
|
||||
let indentationOnListStartToken: number;
|
||||
if (indentationOnLastIndentedLine !== Constants.Unknown) {
|
||||
// scanner just processed list start token so consider last indentation as list indentation
|
||||
// function foo(): { // last indentation was 0, list item will be indented based on this value
|
||||
// foo: number;
|
||||
// }: {};
|
||||
indentationOnListStartToken = indentationOnLastIndentedLine;
|
||||
}
|
||||
else {
|
||||
const startLinePosition = getLineStartPositionForPosition(tokenInfo.token.pos, sourceFile);
|
||||
indentationOnListStartToken = SmartIndenter.findFirstNonWhitespaceColumn(startLinePosition, tokenInfo.token.pos, sourceFile, options);
|
||||
}
|
||||
|
||||
listDynamicIndentation = getDynamicIndentation(parent, parentStartLine, indentationOnListStartToken, options.indentSize!); // TODO: GH#18217
|
||||
}
|
||||
else {
|
||||
// consume any tokens that precede the list as child elements of 'node' using its indentation scope
|
||||
@@ -770,12 +791,12 @@ namespace ts.formatting {
|
||||
// without this check close paren will be interpreted as list end token for function expression which is wrong
|
||||
if (tokenInfo && tokenInfo.token.kind === listEndToken && rangeContainsRange(parent, tokenInfo.token)) {
|
||||
// consume list end token
|
||||
consumeTokenAndAdvanceScanner(tokenInfo, parent, listDynamicIndentation, parent);
|
||||
consumeTokenAndAdvanceScanner(tokenInfo, parent, listDynamicIndentation, parent, /*isListEndToken*/ true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function consumeTokenAndAdvanceScanner(currentTokenInfo: TokenInfo, parent: Node, dynamicIndentation: DynamicIndentation, container: Node): void {
|
||||
function consumeTokenAndAdvanceScanner(currentTokenInfo: TokenInfo, parent: Node, dynamicIndentation: DynamicIndentation, container: Node, isListEndToken?: boolean): void {
|
||||
Debug.assert(rangeContainsRange(parent, currentTokenInfo.token));
|
||||
|
||||
const lastTriviaWasNewLine = formattingScanner.lastTrailingTriviaWasNewLine();
|
||||
@@ -813,7 +834,7 @@ namespace ts.formatting {
|
||||
|
||||
if (indentToken) {
|
||||
const tokenIndentation = (isTokenInRange && !rangeContainsError(currentTokenInfo.token)) ?
|
||||
dynamicIndentation.getIndentationForToken(tokenStart.line, currentTokenInfo.token.kind, container) :
|
||||
dynamicIndentation.getIndentationForToken(tokenStart.line, currentTokenInfo.token.kind, container, !!isListEndToken) :
|
||||
Constants.Unknown;
|
||||
|
||||
let indentNextTokenOrTrivia = true;
|
||||
@@ -1227,6 +1248,9 @@ namespace ts.formatting {
|
||||
if ((<TypeReferenceNode>node).typeArguments === list) {
|
||||
return SyntaxKind.LessThanToken;
|
||||
}
|
||||
break;
|
||||
case SyntaxKind.TypeLiteral:
|
||||
return SyntaxKind.OpenBraceToken;
|
||||
}
|
||||
|
||||
return SyntaxKind.Unknown;
|
||||
@@ -1238,6 +1262,8 @@ namespace ts.formatting {
|
||||
return SyntaxKind.CloseParenToken;
|
||||
case SyntaxKind.LessThanToken:
|
||||
return SyntaxKind.GreaterThanToken;
|
||||
case SyntaxKind.OpenBraceToken:
|
||||
return SyntaxKind.CloseBraceToken;
|
||||
}
|
||||
|
||||
return SyntaxKind.Unknown;
|
||||
|
||||
@@ -66,6 +66,12 @@ namespace ts.formatting {
|
||||
}
|
||||
}
|
||||
|
||||
const containerList = getListByPosition(position, precedingToken.parent, sourceFile);
|
||||
// use list position if the preceding token is before any list items
|
||||
if (containerList && !rangeContainsRange(containerList, precedingToken)) {
|
||||
return getActualIndentationForListStartLine(containerList, sourceFile, options) + options.indentSize!; // TODO: GH#18217
|
||||
}
|
||||
|
||||
return getSmartIndent(sourceFile, position, precedingToken, lineAtPosition, assumeNewLineBeforeCloseBrace, options);
|
||||
}
|
||||
|
||||
@@ -124,14 +130,13 @@ namespace ts.formatting {
|
||||
}
|
||||
|
||||
// check if current node is a list item - if yes, take indentation from it
|
||||
let actualIndentation = getActualIndentationForListItem(current, sourceFile, options);
|
||||
// do not consider parent-child line sharing yet:
|
||||
// function foo(a
|
||||
// | preceding node 'a' does share line with its parent but indentation is expected
|
||||
const actualIndentation = getActualIndentationForListItem(current, sourceFile, options, /*listIndentsChild*/ true);
|
||||
if (actualIndentation !== Value.Unknown) {
|
||||
return actualIndentation;
|
||||
}
|
||||
actualIndentation = getLineIndentationWhenExpressionIsInMultiLine(current, sourceFile, options);
|
||||
if (actualIndentation !== Value.Unknown) {
|
||||
return actualIndentation + options.indentSize!; // TODO: GH#18217
|
||||
}
|
||||
|
||||
previous = current;
|
||||
current = current.parent;
|
||||
@@ -169,26 +174,20 @@ namespace ts.formatting {
|
||||
useActualIndentation = start < ignoreActualIndentationRange.pos || start > ignoreActualIndentationRange.end;
|
||||
}
|
||||
|
||||
if (useActualIndentation) {
|
||||
// check if current node is a list item - if yes, take indentation from it
|
||||
const actualIndentation = getActualIndentationForListItem(current, sourceFile, options);
|
||||
if (actualIndentation !== Value.Unknown) {
|
||||
return actualIndentation + indentationDelta;
|
||||
}
|
||||
}
|
||||
|
||||
const containingListOrParentStart = getContainingListOrParentStart(parent, current, sourceFile);
|
||||
const parentAndChildShareLine =
|
||||
containingListOrParentStart.line === currentStart.line ||
|
||||
childStartsOnTheSameLineWithElseInIfStatement(parent, current, currentStart.line, sourceFile);
|
||||
|
||||
if (useActualIndentation) {
|
||||
// try to fetch actual indentation for current node from source text
|
||||
let actualIndentation = getActualIndentationForNode(current, parent, currentStart, parentAndChildShareLine, sourceFile, options);
|
||||
// check if current node is a list item - if yes, take indentation from it
|
||||
let actualIndentation = getActualIndentationForListItem(current, sourceFile, options, !parentAndChildShareLine);
|
||||
if (actualIndentation !== Value.Unknown) {
|
||||
return actualIndentation + indentationDelta;
|
||||
}
|
||||
actualIndentation = getLineIndentationWhenExpressionIsInMultiLine(current, sourceFile, options);
|
||||
|
||||
// try to fetch actual indentation for current node from source text
|
||||
actualIndentation = getActualIndentationForNode(current, parent, currentStart, parentAndChildShareLine, sourceFile, options);
|
||||
if (actualIndentation !== Value.Unknown) {
|
||||
return actualIndentation + indentationDelta;
|
||||
}
|
||||
@@ -323,112 +322,109 @@ namespace ts.formatting {
|
||||
return false;
|
||||
}
|
||||
|
||||
function getListIfVisualStartEndIsInListRange(list: NodeArray<Node> | undefined, start: number, end: number, node: Node, sourceFile: SourceFile) {
|
||||
return list && rangeContainsVisualStartEnd(list) ? list : undefined;
|
||||
|
||||
// Assumes a list is wrapped by list tokens
|
||||
function rangeContainsVisualStartEnd(textRange: TextRange): boolean {
|
||||
const children = node.getChildren();
|
||||
for (let i = 1; i < children.length - 1; i++) {
|
||||
if (children[i].pos === textRange.pos && children[i].end === textRange.end) {
|
||||
return rangeContainsStartEnd({ pos: children[i - 1].end, end: children[i + 1].getStart(sourceFile) }, start, end);
|
||||
}
|
||||
}
|
||||
return rangeContainsStartEnd(textRange, start, end);
|
||||
}
|
||||
}
|
||||
|
||||
function getListIfStartEndIsInListRange(list: NodeArray<Node> | undefined, start: number, end: number) {
|
||||
return list && rangeContainsStartEnd(list, start, end) ? list : undefined;
|
||||
}
|
||||
|
||||
export function getContainingList(node: Node, sourceFile: SourceFile): NodeArray<Node> | undefined {
|
||||
if (node.parent) {
|
||||
const { end } = node;
|
||||
switch (node.parent.kind) {
|
||||
case SyntaxKind.TypeReference:
|
||||
return getListIfStartEndIsInListRange((<TypeReferenceNode>node.parent).typeArguments, node.getStart(sourceFile), end);
|
||||
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.Constructor:
|
||||
case SyntaxKind.ConstructorType:
|
||||
case SyntaxKind.ConstructSignature: {
|
||||
const start = node.getStart(sourceFile);
|
||||
return getListIfStartEndIsInListRange((<SignatureDeclaration>node.parent).typeParameters, start, end) ||
|
||||
getListIfStartEndIsInListRange((<SignatureDeclaration>node.parent).parameters, start, end);
|
||||
}
|
||||
case SyntaxKind.ClassDeclaration:
|
||||
case SyntaxKind.ClassExpression:
|
||||
case SyntaxKind.InterfaceDeclaration:
|
||||
case SyntaxKind.TypeAliasDeclaration:
|
||||
case SyntaxKind.JSDocTemplateTag: {
|
||||
const { typeParameters } = <ClassDeclaration | ClassExpression | InterfaceDeclaration | TypeAliasDeclaration | JSDocTemplateTag>node.parent;
|
||||
return getListIfStartEndIsInListRange(typeParameters, node.getStart(sourceFile), end);
|
||||
}
|
||||
case SyntaxKind.NewExpression:
|
||||
case SyntaxKind.CallExpression: {
|
||||
const start = node.getStart(sourceFile);
|
||||
return getListIfStartEndIsInListRange((<CallExpression | NewExpression>node.parent).typeArguments, start, end) ||
|
||||
getListIfStartEndIsInListRange((<CallExpression | NewExpression>node.parent).arguments, start, end);
|
||||
}
|
||||
case SyntaxKind.VariableDeclarationList:
|
||||
return getListIfStartEndIsInListRange((<VariableDeclarationList>node.parent).declarations, node.getStart(sourceFile), end);
|
||||
case SyntaxKind.NamedImports:
|
||||
case SyntaxKind.NamedExports:
|
||||
return getListIfStartEndIsInListRange((<NamedImportsOrExports>node.parent).elements, node.getStart(sourceFile), end);
|
||||
case SyntaxKind.ObjectBindingPattern:
|
||||
case SyntaxKind.ArrayBindingPattern:
|
||||
return getListIfStartEndIsInListRange((<ObjectBindingPattern | ArrayBindingPattern>node.parent).elements, node.getStart(sourceFile), end);
|
||||
}
|
||||
return getListByRange(node.getStart(sourceFile), node.getEnd(), node.parent, sourceFile);
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function getActualIndentationForListItem(node: Node, sourceFile: SourceFile, options: EditorSettings): number {
|
||||
function getListByPosition(pos: number, node: Node, sourceFile: SourceFile): NodeArray<Node> | undefined {
|
||||
if (!node) {
|
||||
return;
|
||||
}
|
||||
return getListByRange(pos, pos, node, sourceFile);
|
||||
}
|
||||
|
||||
function getListByRange(start: number, end: number, node: Node, sourceFile: SourceFile): NodeArray<Node> | undefined {
|
||||
switch (node.kind) {
|
||||
case SyntaxKind.TypeReference:
|
||||
return getListIfVisualStartEndIsInListRange((<TypeReferenceNode>node).typeArguments, start, end, node, sourceFile);
|
||||
case SyntaxKind.ObjectLiteralExpression:
|
||||
return getListIfVisualStartEndIsInListRange((<ObjectLiteralExpression>node).properties, start, end, node, sourceFile);
|
||||
case SyntaxKind.ArrayLiteralExpression:
|
||||
return getListIfVisualStartEndIsInListRange((<ArrayLiteralExpression>node).elements, start, end, node, sourceFile);
|
||||
case SyntaxKind.TypeLiteral:
|
||||
return getListIfVisualStartEndIsInListRange((<TypeLiteralNode>node).members, start, end, node, sourceFile);
|
||||
case SyntaxKind.FunctionDeclaration:
|
||||
case SyntaxKind.FunctionExpression:
|
||||
case SyntaxKind.ArrowFunction:
|
||||
case SyntaxKind.MethodDeclaration:
|
||||
case SyntaxKind.MethodSignature:
|
||||
case SyntaxKind.CallSignature:
|
||||
case SyntaxKind.Constructor:
|
||||
case SyntaxKind.ConstructorType:
|
||||
case SyntaxKind.ConstructSignature: {
|
||||
return getListIfVisualStartEndIsInListRange((<SignatureDeclaration>node).typeParameters, start, end, node, sourceFile) ||
|
||||
getListIfVisualStartEndIsInListRange((<SignatureDeclaration>node).parameters, start, end, node, sourceFile);
|
||||
}
|
||||
case SyntaxKind.ClassDeclaration:
|
||||
case SyntaxKind.ClassExpression:
|
||||
case SyntaxKind.InterfaceDeclaration:
|
||||
case SyntaxKind.TypeAliasDeclaration:
|
||||
case SyntaxKind.JSDocTemplateTag: {
|
||||
const { typeParameters } = <ClassDeclaration | ClassExpression | InterfaceDeclaration | TypeAliasDeclaration | JSDocTemplateTag>node;
|
||||
return getListIfVisualStartEndIsInListRange(typeParameters, start, end, node, sourceFile);
|
||||
}
|
||||
case SyntaxKind.NewExpression:
|
||||
case SyntaxKind.CallExpression: {
|
||||
return getListIfVisualStartEndIsInListRange((<CallExpression>node).typeArguments, start, end, node, sourceFile) ||
|
||||
getListIfVisualStartEndIsInListRange((<CallExpression>node).arguments, start, end, node, sourceFile);
|
||||
}
|
||||
case SyntaxKind.VariableDeclarationList:
|
||||
return getListIfStartEndIsInListRange((<VariableDeclarationList>node).declarations, start, end);
|
||||
case SyntaxKind.NamedImports:
|
||||
case SyntaxKind.NamedExports:
|
||||
return getListIfVisualStartEndIsInListRange((<NamedImportsOrExports>node).elements, start, end, node, sourceFile);
|
||||
case SyntaxKind.ObjectBindingPattern:
|
||||
case SyntaxKind.ArrayBindingPattern:
|
||||
return getListIfVisualStartEndIsInListRange((<ObjectBindingPattern | ArrayBindingPattern>node).elements, start, end, node, sourceFile);
|
||||
}
|
||||
}
|
||||
|
||||
function getActualIndentationForListStartLine(list: NodeArray<Node>, sourceFile: SourceFile, options: EditorSettings): number {
|
||||
if (!list) {
|
||||
return Value.Unknown;
|
||||
}
|
||||
return findColumnForFirstNonWhitespaceCharacterInLine(sourceFile.getLineAndCharacterOfPosition(list.pos), sourceFile, options);
|
||||
}
|
||||
|
||||
function getActualIndentationForListItem(node: Node, sourceFile: SourceFile, options: EditorSettings, listIndentsChild: boolean): number {
|
||||
if (node.parent && node.parent.kind === SyntaxKind.VariableDeclarationList) {
|
||||
// VariableDeclarationList has no wrapping tokens
|
||||
return Value.Unknown;
|
||||
}
|
||||
const containingList = getContainingList(node, sourceFile);
|
||||
if (containingList) {
|
||||
const index = containingList.indexOf(node);
|
||||
if (index !== -1) {
|
||||
return deriveActualIndentationFromList(containingList, index, sourceFile, options);
|
||||
}
|
||||
}
|
||||
return Value.Unknown;
|
||||
}
|
||||
|
||||
function getLineIndentationWhenExpressionIsInMultiLine(node: Node, sourceFile: SourceFile, options: EditorSettings): number {
|
||||
// actual indentation should not be used when:
|
||||
// - node is close parenthesis - this is the end of the expression
|
||||
if (node.kind === SyntaxKind.CloseParenToken) {
|
||||
return Value.Unknown;
|
||||
}
|
||||
|
||||
if (node.parent && isCallOrNewExpression(node.parent) && node.parent.expression !== node) {
|
||||
const fullCallOrNewExpression = node.parent.expression;
|
||||
const startingExpression = getStartingExpression(fullCallOrNewExpression);
|
||||
|
||||
if (fullCallOrNewExpression === startingExpression) {
|
||||
return Value.Unknown;
|
||||
}
|
||||
|
||||
const fullCallOrNewExpressionEnd = sourceFile.getLineAndCharacterOfPosition(fullCallOrNewExpression.end);
|
||||
const startingExpressionEnd = sourceFile.getLineAndCharacterOfPosition(startingExpression.end);
|
||||
|
||||
if (fullCallOrNewExpressionEnd.line === startingExpressionEnd.line) {
|
||||
return Value.Unknown;
|
||||
}
|
||||
|
||||
return findColumnForFirstNonWhitespaceCharacterInLine(fullCallOrNewExpressionEnd, sourceFile, options);
|
||||
}
|
||||
|
||||
return Value.Unknown;
|
||||
|
||||
function getStartingExpression(node: Expression) {
|
||||
while (true) {
|
||||
switch (node.kind) {
|
||||
case SyntaxKind.CallExpression:
|
||||
case SyntaxKind.NewExpression:
|
||||
case SyntaxKind.PropertyAccessExpression:
|
||||
case SyntaxKind.ElementAccessExpression:
|
||||
node = (<PropertyAccessExpression | CallExpression | NewExpression | ElementAccessExpression | PropertyAccessExpression>node).expression;
|
||||
break;
|
||||
default:
|
||||
return node;
|
||||
const result = deriveActualIndentationFromList(containingList, index, sourceFile, options);
|
||||
if (result !== Value.Unknown) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
return getActualIndentationForListStartLine(containingList, sourceFile, options) + (listIndentsChild ? options.indentSize! : 0); // TODO: GH#18217
|
||||
}
|
||||
return Value.Unknown;
|
||||
}
|
||||
|
||||
function deriveActualIndentationFromList(list: ReadonlyArray<Node>, index: number, sourceFile: SourceFile, options: EditorSettings): number {
|
||||
|
||||
Reference in New Issue
Block a user