560 lines
22 KiB
Go
560 lines
22 KiB
Go
package format
|
|
|
|
import (
|
|
"slices"
|
|
"unicode/utf8"
|
|
|
|
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/ast"
|
|
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/astnav"
|
|
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/core"
|
|
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/debug"
|
|
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/scanner"
|
|
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/stringutil"
|
|
)
|
|
|
|
func GetIndentationForNode(n *ast.Node, ignoreActualIndentationRange *core.TextRange, sourceFile *ast.SourceFile, options *FormatCodeSettings) int {
|
|
startline, startpos := scanner.GetECMALineAndCharacterOfPosition(sourceFile, scanner.GetTokenPosOfNode(n, sourceFile, false))
|
|
return getIndentationForNodeWorker(n, startline, startpos, ignoreActualIndentationRange /*indentationDelta*/, 0, sourceFile /*isNextChild*/, false, options)
|
|
}
|
|
|
|
func getIndentationForNodeWorker(
|
|
current *ast.Node,
|
|
currentStartLine int,
|
|
currentStartCharacter int,
|
|
ignoreActualIndentationRange *core.TextRange,
|
|
indentationDelta int,
|
|
sourceFile *ast.SourceFile,
|
|
isNextChild bool,
|
|
options *FormatCodeSettings,
|
|
) int {
|
|
parent := current.Parent
|
|
|
|
// Walk up the tree and collect indentation for parent-child node pairs. Indentation is not added if
|
|
// * parent and child nodes start on the same line, or
|
|
// * parent is an IfStatement and child starts on the same line as an 'else clause'.
|
|
for parent != nil {
|
|
useActualIndentation := true
|
|
if ignoreActualIndentationRange != nil {
|
|
start := scanner.GetTokenPosOfNode(current, sourceFile, false)
|
|
useActualIndentation = start < ignoreActualIndentationRange.Pos() || start > ignoreActualIndentationRange.End()
|
|
}
|
|
|
|
containingListOrParentStartLine, containingListOrParentStartCharacter := getContainingListOrParentStart(parent, current, sourceFile)
|
|
parentAndChildShareLine := containingListOrParentStartLine == currentStartLine ||
|
|
childStartsOnTheSameLineWithElseInIfStatement(parent, current, currentStartLine, sourceFile)
|
|
|
|
if useActualIndentation {
|
|
// check if current node is a list item - if yes, take indentation from it
|
|
var firstListChild *ast.Node
|
|
containerList := GetContainingList(current, sourceFile)
|
|
if containerList != nil {
|
|
firstListChild = core.FirstOrNil(containerList.Nodes)
|
|
}
|
|
// A list indents its children if the children begin on a later line than the list itself:
|
|
//
|
|
// f1( L0 - List start
|
|
// { L1 - First child start: indented, along with all other children
|
|
// prop: 0
|
|
// },
|
|
// {
|
|
// prop: 1
|
|
// }
|
|
// )
|
|
//
|
|
// f2({ L0 - List start and first child start: children are not indented.
|
|
// prop: 0 Object properties are indented only one level, because the list
|
|
// }, { itself contributes nothing.
|
|
// prop: 1 L3 - The indentation of the second object literal is best understood by
|
|
// }) looking at the relationship between the list and *first* list item.
|
|
listLine, _ := getStartLineAndCharacterForNode(firstListChild, sourceFile)
|
|
listIndentsChild := firstListChild != nil && listLine > containingListOrParentStartLine
|
|
actualIndentation := getActualIndentationForListItem(current, sourceFile, options, listIndentsChild)
|
|
if actualIndentation != -1 {
|
|
return actualIndentation + indentationDelta
|
|
}
|
|
|
|
// try to fetch actual indentation for current node from source text
|
|
actualIndentation = getActualIndentationForNode(current, parent, currentStartLine, currentStartCharacter, parentAndChildShareLine, sourceFile, options)
|
|
if actualIndentation != -1 {
|
|
return actualIndentation + indentationDelta
|
|
}
|
|
}
|
|
|
|
// 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(options, parent, current, sourceFile, isNextChild) && !parentAndChildShareLine {
|
|
indentationDelta += options.IndentSize
|
|
}
|
|
|
|
// In our AST, a call argument's `parent` is the call-expression, not the argument list.
|
|
// We would like to increase indentation based on the relationship between an argument and its argument-list,
|
|
// so we spoof the starting position of the (parent) call-expression to match the (non-parent) argument-list.
|
|
// But, the spoofed start-value could then cause a problem when comparing the start position of the call-expression
|
|
// to *its* parent (in the case of an iife, an expression statement), adding an extra level of indentation.
|
|
//
|
|
// Instead, when at an argument, we unspoof the starting position of the enclosing call expression
|
|
// *after* applying indentation for the argument.
|
|
|
|
useTrueStart := isArgumentAndStartLineOverlapsExpressionBeingCalled(parent, current, currentStartLine, sourceFile)
|
|
|
|
current = parent
|
|
parent = current.Parent
|
|
|
|
if useTrueStart {
|
|
currentStartLine, currentStartCharacter = scanner.GetECMALineAndCharacterOfPosition(sourceFile, scanner.GetTokenPosOfNode(current, sourceFile, false))
|
|
} else {
|
|
currentStartLine = containingListOrParentStartLine
|
|
currentStartCharacter = containingListOrParentStartCharacter
|
|
}
|
|
}
|
|
|
|
return indentationDelta + options.BaseIndentSize
|
|
}
|
|
|
|
/*
|
|
* Function returns -1 if actual indentation for node should not be used (i.e because node is nested expression)
|
|
*/
|
|
func getActualIndentationForNode(current *ast.Node, parent *ast.Node, cuurentLine int, currentChar int, parentAndChildShareLine bool, sourceFile *ast.SourceFile, options *FormatCodeSettings) int {
|
|
// actual indentation is used for statements\declarations if one of cases below is true:
|
|
// - parent is SourceFile - by default immediate children of SourceFile are not indented except when user indents them manually
|
|
// - parent and child are not on the same line
|
|
useActualIndentation := (ast.IsDeclaration(current) || ast.IsStatementButNotDeclaration(current)) && (parent.Kind == ast.KindSourceFile || !parentAndChildShareLine)
|
|
|
|
if !useActualIndentation {
|
|
return -1
|
|
}
|
|
|
|
return findColumnForFirstNonWhitespaceCharacterInLine(cuurentLine, currentChar, sourceFile, options)
|
|
}
|
|
|
|
func isArgumentAndStartLineOverlapsExpressionBeingCalled(parent *ast.Node, child *ast.Node, childStartLine int, sourceFile *ast.SourceFile) bool {
|
|
if !(ast.IsCallExpression(child) && slices.Contains(parent.AsCallExpression().Arguments.Nodes, child)) {
|
|
return false
|
|
}
|
|
expressionOfCallExpressionEnd := parent.Expression().End()
|
|
expressionOfCallExpressionEndLine, _ := scanner.GetECMALineAndCharacterOfPosition(sourceFile, expressionOfCallExpressionEnd)
|
|
return expressionOfCallExpressionEndLine == childStartLine
|
|
}
|
|
|
|
func getActualIndentationForListItem(node *ast.Node, sourceFile *ast.SourceFile, options *FormatCodeSettings, listIndentsChild bool) int {
|
|
if node.Parent != nil && node.Parent.Kind == ast.KindVariableDeclarationList {
|
|
// VariableDeclarationList has no wrapping tokens
|
|
return -1
|
|
}
|
|
containingList := GetContainingList(node, sourceFile)
|
|
if containingList != nil {
|
|
index := core.FindIndex(containingList.Nodes, func(e *ast.Node) bool { return e == node })
|
|
if index != -1 {
|
|
result := deriveActualIndentationFromList(containingList, index, sourceFile, options)
|
|
if result != -1 {
|
|
return result
|
|
}
|
|
}
|
|
delta := 0
|
|
if listIndentsChild {
|
|
delta = options.IndentSize
|
|
}
|
|
res := getActualIndentationForListStartLine(containingList, sourceFile, options)
|
|
if res == -1 {
|
|
return delta
|
|
}
|
|
return res + delta
|
|
}
|
|
return -1
|
|
}
|
|
|
|
func getActualIndentationForListStartLine(list *ast.NodeList, sourceFile *ast.SourceFile, options *FormatCodeSettings) int {
|
|
if list == nil {
|
|
return -1
|
|
}
|
|
line, char := scanner.GetECMALineAndCharacterOfPosition(sourceFile, list.Loc.Pos())
|
|
return findColumnForFirstNonWhitespaceCharacterInLine(line, char, sourceFile, options)
|
|
}
|
|
|
|
func deriveActualIndentationFromList(list *ast.NodeList, index int, sourceFile *ast.SourceFile, options *FormatCodeSettings) int {
|
|
debug.Assert(list != nil && index >= 0 && index < len(list.Nodes))
|
|
|
|
node := list.Nodes[index]
|
|
|
|
// walk toward the start of the list starting from current node and check if the line is the same for all items.
|
|
// if end line for item [i - 1] differs from the start line for item [i] - find column of the first non-whitespace character on the line of item [i]
|
|
|
|
line, char := getStartLineAndCharacterForNode(node, sourceFile)
|
|
|
|
for i := index; i >= 0; i-- {
|
|
if list.Nodes[i].Kind == ast.KindCommaToken {
|
|
continue
|
|
}
|
|
// skip list items that ends on the same line with the current list element
|
|
prevEndLine, _ := scanner.GetECMALineAndCharacterOfPosition(sourceFile, list.Nodes[i].End())
|
|
if prevEndLine != line {
|
|
return findColumnForFirstNonWhitespaceCharacterInLine(line, char, sourceFile, options)
|
|
}
|
|
|
|
line, char = getStartLineAndCharacterForNode(list.Nodes[i], sourceFile)
|
|
}
|
|
return -1
|
|
}
|
|
|
|
func findColumnForFirstNonWhitespaceCharacterInLine(line int, char int, sourceFile *ast.SourceFile, options *FormatCodeSettings) int {
|
|
lineStart := scanner.GetECMAPositionOfLineAndCharacter(sourceFile, line, 0)
|
|
return FindFirstNonWhitespaceColumn(lineStart, lineStart+char, sourceFile, options)
|
|
}
|
|
|
|
func FindFirstNonWhitespaceColumn(startPos int, endPos int, sourceFile *ast.SourceFile, options *FormatCodeSettings) int {
|
|
_, col := findFirstNonWhitespaceCharacterAndColumn(startPos, endPos, sourceFile, options)
|
|
return col
|
|
}
|
|
|
|
/**
|
|
* Character is the actual index of the character since the beginning of the line.
|
|
* Column - position of the character after expanding tabs to spaces.
|
|
* "0\t2$"
|
|
* value of 'character' for '$' is 3
|
|
* value of 'column' for '$' is 6 (assuming that tab size is 4)
|
|
*/
|
|
func findFirstNonWhitespaceCharacterAndColumn(startPos int, endPos int, sourceFile *ast.SourceFile, options *FormatCodeSettings) (character int, column int) {
|
|
character = 0
|
|
column = 0
|
|
text := sourceFile.Text()
|
|
for pos := startPos; pos < endPos; pos++ {
|
|
ch, size := utf8.DecodeRuneInString(text[pos:])
|
|
if size == 0 && ch == utf8.RuneError {
|
|
continue // multibyte character - TODO: recognize non-tab multicolumn characters? ideographic space?
|
|
}
|
|
if !stringutil.IsWhiteSpaceSingleLine(ch) {
|
|
break
|
|
}
|
|
|
|
if ch == '\t' {
|
|
column += options.TabSize + (column % options.TabSize)
|
|
} else {
|
|
column++
|
|
}
|
|
|
|
character++
|
|
}
|
|
return character, column
|
|
}
|
|
|
|
func childStartsOnTheSameLineWithElseInIfStatement(parent *ast.Node, child *ast.Node, childStartLine int, sourceFile *ast.SourceFile) bool {
|
|
if parent.Kind == ast.KindIfStatement && parent.AsIfStatement().ElseStatement == child {
|
|
elseKeyword := astnav.FindPrecedingToken(sourceFile, child.Pos())
|
|
debug.AssertIsDefined(elseKeyword)
|
|
elseKeywordStartLine, _ := getStartLineAndCharacterForNode(elseKeyword, sourceFile)
|
|
return elseKeywordStartLine == childStartLine
|
|
}
|
|
return false
|
|
}
|
|
|
|
func getStartLineAndCharacterForNode(n *ast.Node, sourceFile *ast.SourceFile) (line int, character int) {
|
|
return scanner.GetECMALineAndCharacterOfPosition(sourceFile, scanner.GetTokenPosOfNode(n, sourceFile, false))
|
|
}
|
|
|
|
func GetContainingList(node *ast.Node, sourceFile *ast.SourceFile) *ast.NodeList {
|
|
if node.Parent == nil {
|
|
return nil
|
|
}
|
|
return getListByRange(scanner.GetTokenPosOfNode(node, sourceFile, false), node.End(), node.Parent, sourceFile)
|
|
}
|
|
|
|
func getListByPosition(pos int, node *ast.Node, sourceFile *ast.SourceFile) *ast.NodeList {
|
|
if node == nil {
|
|
return nil
|
|
}
|
|
return getListByRange(pos, pos, node, sourceFile)
|
|
}
|
|
|
|
func getListByRange(start int, end int, node *ast.Node, sourceFile *ast.SourceFile) *ast.NodeList {
|
|
r := core.NewTextRange(start, end)
|
|
switch node.Kind {
|
|
case ast.KindTypeReference:
|
|
return getList(node.AsTypeReferenceNode().TypeArguments, r, node, sourceFile)
|
|
case ast.KindObjectLiteralExpression:
|
|
return getList(node.AsObjectLiteralExpression().Properties, r, node, sourceFile)
|
|
case ast.KindArrayLiteralExpression:
|
|
return getList(node.AsArrayLiteralExpression().Elements, r, node, sourceFile)
|
|
case ast.KindTypeLiteral:
|
|
return getList(node.AsTypeLiteralNode().Members, r, node, sourceFile)
|
|
case ast.KindFunctionDeclaration,
|
|
ast.KindFunctionExpression,
|
|
ast.KindArrowFunction,
|
|
ast.KindMethodDeclaration,
|
|
ast.KindMethodSignature,
|
|
ast.KindCallSignature,
|
|
ast.KindConstructor,
|
|
ast.KindConstructorType,
|
|
ast.KindConstructSignature:
|
|
tpl := getList(node.TypeParameterList(), r, node, sourceFile)
|
|
if tpl != nil {
|
|
return tpl
|
|
}
|
|
return getList(node.ParameterList(), r, node, sourceFile)
|
|
case ast.KindGetAccessor:
|
|
return getList(node.ParameterList(), r, node, sourceFile)
|
|
case ast.KindClassDeclaration,
|
|
ast.KindClassExpression,
|
|
ast.KindInterfaceDeclaration,
|
|
ast.KindTypeAliasDeclaration,
|
|
ast.KindJSDocTemplateTag:
|
|
return getList(node.TypeParameterList(), r, node, sourceFile)
|
|
case ast.KindNewExpression, ast.KindCallExpression:
|
|
l := getList(node.TypeArgumentList(), r, node, sourceFile)
|
|
if l != nil {
|
|
return l
|
|
}
|
|
return getList(node.ArgumentList(), r, node, sourceFile)
|
|
case ast.KindVariableDeclarationList:
|
|
return getList(node.AsVariableDeclarationList().Declarations, r, node, sourceFile)
|
|
case ast.KindNamedImports:
|
|
return getList(node.AsNamedImports().Elements, r, node, sourceFile)
|
|
case ast.KindNamedExports:
|
|
return getList(node.AsNamedExports().Elements, r, node, sourceFile)
|
|
case ast.KindObjectBindingPattern, ast.KindArrayBindingPattern:
|
|
return getList(node.AsBindingPattern().Elements, r, node, sourceFile)
|
|
}
|
|
return nil // TODO: should this be a panic? It isn't in strada.
|
|
}
|
|
|
|
func getList(list *ast.NodeList, r core.TextRange, node *ast.Node, sourceFile *ast.SourceFile) *ast.NodeList {
|
|
if list == nil {
|
|
return nil
|
|
}
|
|
if r.ContainedBy(getVisualListRange(node, list.Loc, sourceFile)) {
|
|
return list
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func getVisualListRange(node *ast.Node, list core.TextRange, sourceFile *ast.SourceFile) core.TextRange {
|
|
// In strada, this relied on the services .getChildren method, which manifested synthetic token nodes
|
|
// _however_, the logic boils down to "find the child with the matching span and adjust its start to the
|
|
// previous (possibly token) child's end and its end to the token start of the following element" - basically
|
|
// expanding the range to encompass all the neighboring non-token trivia
|
|
// Now, we perform that logic with the scanner instead
|
|
prior := astnav.FindPrecedingToken(sourceFile, list.Pos())
|
|
var priorEnd int
|
|
if prior == nil {
|
|
priorEnd = list.Pos()
|
|
} else {
|
|
priorEnd = prior.End()
|
|
}
|
|
next := astnav.FindNextToken(prior, node, sourceFile)
|
|
var nextStart int
|
|
if next == nil {
|
|
nextStart = list.End()
|
|
} else {
|
|
nextStart = next.Pos()
|
|
}
|
|
return core.NewTextRange(priorEnd, nextStart)
|
|
}
|
|
|
|
func getContainingListOrParentStart(parent *ast.Node, child *ast.Node, sourceFile *ast.SourceFile) (line int, character int) {
|
|
containingList := GetContainingList(child, sourceFile)
|
|
var startPos int
|
|
if containingList != nil {
|
|
startPos = containingList.Loc.Pos()
|
|
} else {
|
|
startPos = scanner.GetTokenPosOfNode(parent, sourceFile, false)
|
|
}
|
|
return scanner.GetECMALineAndCharacterOfPosition(sourceFile, startPos)
|
|
}
|
|
|
|
func isControlFlowEndingStatement(kind ast.Kind, parentKind ast.Kind) bool {
|
|
switch kind {
|
|
case ast.KindReturnStatement, ast.KindThrowStatement, ast.KindContinueStatement, ast.KindBreakStatement:
|
|
return parentKind != ast.KindBlock
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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.
|
|
*/
|
|
func ShouldIndentChildNode(settings *FormatCodeSettings, parent *ast.Node, child *ast.Node, sourceFile *ast.SourceFile, isNextChildArg ...bool) bool {
|
|
isNextChild := false
|
|
if len(isNextChildArg) > 0 {
|
|
isNextChild = isNextChildArg[0]
|
|
}
|
|
|
|
return NodeWillIndentChild(settings, parent, child, sourceFile, false) && !(isNextChild && child != nil && isControlFlowEndingStatement(child.Kind, parent.Kind))
|
|
}
|
|
|
|
func NodeWillIndentChild(settings *FormatCodeSettings, parent *ast.Node, child *ast.Node, sourceFile *ast.SourceFile, indentByDefault bool) bool {
|
|
childKind := ast.KindUnknown
|
|
if child != nil {
|
|
childKind = child.Kind
|
|
}
|
|
|
|
switch parent.Kind {
|
|
case ast.KindExpressionStatement,
|
|
ast.KindClassDeclaration,
|
|
ast.KindClassExpression,
|
|
ast.KindInterfaceDeclaration,
|
|
ast.KindEnumDeclaration,
|
|
ast.KindTypeAliasDeclaration,
|
|
ast.KindArrayLiteralExpression,
|
|
ast.KindBlock,
|
|
ast.KindModuleBlock,
|
|
ast.KindObjectLiteralExpression,
|
|
ast.KindTypeLiteral,
|
|
ast.KindMappedType,
|
|
ast.KindTupleType,
|
|
ast.KindParenthesizedExpression,
|
|
ast.KindPropertyAccessExpression,
|
|
ast.KindCallExpression,
|
|
ast.KindNewExpression,
|
|
ast.KindVariableStatement,
|
|
ast.KindExportAssignment,
|
|
ast.KindReturnStatement,
|
|
ast.KindConditionalExpression,
|
|
ast.KindArrayBindingPattern,
|
|
ast.KindObjectBindingPattern,
|
|
ast.KindJsxOpeningElement,
|
|
ast.KindJsxOpeningFragment,
|
|
ast.KindJsxSelfClosingElement,
|
|
ast.KindJsxExpression,
|
|
ast.KindMethodSignature,
|
|
ast.KindCallSignature,
|
|
ast.KindConstructSignature,
|
|
ast.KindParameter,
|
|
ast.KindFunctionType,
|
|
ast.KindConstructorType,
|
|
ast.KindParenthesizedType,
|
|
ast.KindTaggedTemplateExpression,
|
|
ast.KindAwaitExpression,
|
|
ast.KindNamedExports,
|
|
ast.KindNamedImports,
|
|
ast.KindExportSpecifier,
|
|
ast.KindImportSpecifier,
|
|
ast.KindPropertyDeclaration,
|
|
ast.KindCaseClause,
|
|
ast.KindDefaultClause:
|
|
return true
|
|
case ast.KindCaseBlock:
|
|
return settings.IndentSwitchCase.IsTrueOrUnknown()
|
|
case ast.KindVariableDeclaration, ast.KindPropertyAssignment, ast.KindBinaryExpression:
|
|
if settings.IndentMultiLineObjectLiteralBeginningOnBlankLine.IsFalseOrUnknown() && sourceFile != nil && childKind == ast.KindObjectLiteralExpression {
|
|
return rangeIsOnOneLine(child.Loc, sourceFile)
|
|
}
|
|
if parent.Kind == ast.KindBinaryExpression && sourceFile != nil && childKind == ast.KindJsxElement {
|
|
parentStartLine, _ := scanner.GetECMALineAndCharacterOfPosition(sourceFile, scanner.SkipTrivia(sourceFile.Text(), parent.Pos()))
|
|
childStartLine, _ := scanner.GetECMALineAndCharacterOfPosition(sourceFile, scanner.SkipTrivia(sourceFile.Text(), child.Pos()))
|
|
return parentStartLine != childStartLine
|
|
}
|
|
if parent.Kind != ast.KindBinaryExpression {
|
|
return true
|
|
}
|
|
return indentByDefault
|
|
case ast.KindDoStatement,
|
|
ast.KindWhileStatement,
|
|
ast.KindForInStatement,
|
|
ast.KindForOfStatement,
|
|
ast.KindForStatement,
|
|
ast.KindIfStatement,
|
|
ast.KindFunctionDeclaration,
|
|
ast.KindFunctionExpression,
|
|
ast.KindMethodDeclaration,
|
|
ast.KindConstructor,
|
|
ast.KindGetAccessor,
|
|
ast.KindSetAccessor:
|
|
return childKind != ast.KindBlock
|
|
case ast.KindArrowFunction:
|
|
if sourceFile != nil && childKind == ast.KindParenthesizedExpression {
|
|
return rangeIsOnOneLine(child.Loc, sourceFile)
|
|
}
|
|
return childKind != ast.KindBlock
|
|
case ast.KindExportDeclaration:
|
|
return childKind != ast.KindNamedExports
|
|
case ast.KindImportDeclaration:
|
|
return childKind != ast.KindImportClause || (child.AsImportClause().NamedBindings != nil && child.AsImportClause().NamedBindings.Kind != ast.KindNamedImports)
|
|
case ast.KindJsxElement:
|
|
return childKind != ast.KindJsxClosingElement
|
|
case ast.KindJsxFragment:
|
|
return childKind != ast.KindJsxClosingFragment
|
|
case ast.KindIntersectionType, ast.KindUnionType, ast.KindSatisfiesExpression:
|
|
if childKind == ast.KindTypeLiteral || childKind == ast.KindTupleType || childKind == ast.KindMappedType {
|
|
return false
|
|
}
|
|
return indentByDefault
|
|
case ast.KindTryStatement:
|
|
if childKind == ast.KindBlock {
|
|
return false
|
|
}
|
|
return indentByDefault
|
|
}
|
|
|
|
// No explicit rule for given nodes so the result will follow the default value argument
|
|
return indentByDefault
|
|
}
|
|
|
|
// A multiline conditional typically increases the indentation of its whenTrue and whenFalse children:
|
|
//
|
|
// condition
|
|
//
|
|
// ? whenTrue
|
|
// : whenFalse;
|
|
//
|
|
// However, that indentation does not apply if the subexpressions themselves span multiple lines,
|
|
// applying their own indentation:
|
|
//
|
|
// (() => {
|
|
// return complexCalculationForCondition();
|
|
// })() ? {
|
|
//
|
|
// whenTrue: 'multiline object literal'
|
|
// } : (
|
|
//
|
|
// whenFalse('multiline parenthesized expression')
|
|
//
|
|
// );
|
|
//
|
|
// In these cases, we must discard the indentation increase that would otherwise be applied to the
|
|
// whenTrue and whenFalse children to avoid double-indenting their contents. To identify this scenario,
|
|
// we check for the whenTrue branch beginning on the line that the condition ends, and the whenFalse
|
|
// branch beginning on the line that the whenTrue branch ends.
|
|
func childIsUnindentedBranchOfConditionalExpression(parent *ast.Node, child *ast.Node, childStartLine int, sourceFile *ast.SourceFile) bool {
|
|
if parent.Kind == ast.KindConditionalExpression && (child == parent.AsConditionalExpression().WhenTrue || child == parent.AsConditionalExpression().WhenFalse) {
|
|
conditionEndLine, _ := scanner.GetECMALineAndCharacterOfPosition(sourceFile, parent.AsConditionalExpression().Condition.End())
|
|
if child == parent.AsConditionalExpression().WhenTrue {
|
|
return childStartLine == conditionEndLine
|
|
} else {
|
|
// On the whenFalse side, we have to look at the whenTrue side, because if that one was
|
|
// indented, whenFalse must also be indented:
|
|
//
|
|
// const y = true
|
|
// ? 1 : ( L1: whenTrue indented because it's on a new line
|
|
// 0 L2: indented two stops, one because whenTrue was indented
|
|
// ); and one because of the parentheses spanning multiple lines
|
|
trueStartLine, _ := getStartLineAndCharacterForNode(parent.AsConditionalExpression().WhenTrue, sourceFile)
|
|
trueEndLine, _ := scanner.GetECMALineAndCharacterOfPosition(sourceFile, parent.AsConditionalExpression().WhenTrue.End())
|
|
return conditionEndLine == trueStartLine && trueEndLine == childStartLine
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func argumentStartsOnSameLineAsPreviousArgument(parent *ast.Node, child *ast.Node, childStartLine int, sourceFile *ast.SourceFile) bool {
|
|
if ast.IsCallExpression(parent) || ast.IsNewExpression(parent) {
|
|
if len(parent.Arguments()) == 0 {
|
|
return false
|
|
}
|
|
currentIndex := core.FindIndex(parent.Arguments(), func(n *ast.Node) bool { return n == child })
|
|
if currentIndex == -1 {
|
|
// If it's not one of the arguments, don't look past this
|
|
return false
|
|
}
|
|
if currentIndex == 0 {
|
|
return false // Can't look at previous node if first
|
|
}
|
|
|
|
previousNode := parent.Arguments()[currentIndex-1]
|
|
lineOfPreviousNode, _ := scanner.GetECMALineAndCharacterOfPosition(sourceFile, previousNode.End())
|
|
if childStartLine == lineOfPreviousNode {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|