in build/print.go [379:763]
func (p *printer) expr(v Expr, outerPrec int) {
// Emit line-comments preceding this expression.
// If we are in the middle of an expression but not inside ( ) [ ] { }
// then we cannot just break the line: we'd have to end it with a \.
// However, even then we can't emit line comments since that would
// end the expression. This is only a concern if we have rewritten
// the parse tree. If comments were okay before this expression in
// the original input they're still okay now, in the absence of rewrites.
//
// TODO(bazel-team): Check whether it is valid to emit comments right now,
// and if not, insert them earlier in the output instead, at the most
// recent \n not following a \ line.
p.newlineIfNeeded()
if before := v.Comment().Before; len(before) > 0 {
// Want to print a line comment.
// Line comments must be at the current margin.
p.trim()
if p.indent() > 0 {
// There's other text on the line. Start a new line.
p.printf("\n")
}
// Re-indent to margin.
p.printf("%*s", p.margin, "")
for _, com := range before {
p.printf("%s", strings.TrimSpace(com.Token))
p.newline()
}
}
// Do we introduce parentheses?
// The result depends on the kind of expression.
// Each expression type that might need parentheses
// calls addParen with its own precedence.
// If parentheses are necessary, addParen prints the
// opening parenthesis and sets parenthesized so that
// the code after the switch can print the closing one.
parenthesized := false
addParen := func(prec int) {
if prec < outerPrec {
p.printf("(")
p.depth++
parenthesized = true
}
}
switch v := v.(type) {
default:
panic(fmt.Errorf("printer: unexpected type %T", v))
case *CommentBlock:
// CommentBlock has no body
case *LiteralExpr:
p.printf("%s", v.Token)
case *Ident:
p.printf("%s", v.Name)
case *TypedIdent:
p.expr(v.Ident, precLow)
p.printf(": ")
p.expr(v.Type, precLow)
case *BranchStmt:
p.printf("%s", v.Token)
case *StringExpr:
// If the Token is a correct quoting of Value and has double quotes, use it,
// also use it if it has single quotes and the value itself contains a double quote symbol
// or if it's a raw string literal (starts with "r").
// This preserves the specific escaping choices that BUILD authors have made.
s, triple, err := Unquote(v.Token)
if err == nil && s == v.Value && triple == v.TripleQuote {
if strings.HasPrefix(v.Token, `r`) {
// Raw string literal
token := v.Token
if strings.HasSuffix(v.Token, `'`) && !strings.ContainsRune(v.Value, '"') {
// Single quotes but no double quotes inside the string, replace with double quotes
if strings.HasSuffix(token, `'''`) {
token = `r"""` + token[4:len(token)-3] + `"""`
} else if strings.HasSuffix(token, `'`) {
token = `r"` + token[2:len(token)-1] + `"`
}
}
p.printf("%s", token)
break
}
// Non-raw string literal
if strings.HasPrefix(v.Token, `"`) || strings.ContainsRune(v.Value, '"') {
// Either double quoted or there are double-quotes inside the string
if IsCorrectEscaping(v.Token) {
p.printf("%s", v.Token)
break
}
}
}
p.printf("%s", quote(v.Value, v.TripleQuote))
case *DotExpr:
addParen(precSuffix)
p.expr(v.X, precSuffix)
_, xEnd := v.X.Span()
isMultiline := isDifferentLines(&v.NamePos, &xEnd)
if isMultiline {
p.margin += listIndentation
p.breakline()
}
p.printf(".%s", v.Name)
if isMultiline {
p.margin -= listIndentation
}
case *IndexExpr:
addParen(precSuffix)
p.expr(v.X, precSuffix)
p.printf("[")
p.expr(v.Y, precLow)
p.printf("]")
case *KeyValueExpr:
p.expr(v.Key, precLow)
p.printf(": ")
p.expr(v.Value, precLow)
case *SliceExpr:
addParen(precSuffix)
p.expr(v.X, precSuffix)
p.printf("[")
if v.From != nil {
p.expr(v.From, precLow)
}
p.printf(":")
if v.To != nil {
p.expr(v.To, precLow)
}
if v.SecondColon.Byte != 0 {
p.printf(":")
if v.Step != nil {
p.expr(v.Step, precLow)
}
}
p.printf("]")
case *UnaryExpr:
addParen(precUnary)
if v.Op == "not" {
p.printf("not ") // Requires a space after it.
} else {
p.printf("%s", v.Op)
}
// Use the next precedence level (precSuffix), so that nested unary expressions are parenthesized,
// for example: `not (-(+(~foo)))` instead of `not -+~foo`
if v.X != nil {
p.expr(v.X, precSuffix)
}
case *LambdaExpr:
addParen(precColon)
p.printf("lambda")
for i, param := range v.Params {
if i > 0 {
p.printf(",")
}
p.printf(" ")
p.expr(param, precLow)
}
p.printf(": ")
p.expr(v.Body[0], precLow) // lambdas should have exactly one statement
case *BinaryExpr:
// Precedence: use the precedence of the operator.
// Since all binary expressions FormatWithoutRewriting left-to-right,
// it is okay for the left side to reuse the same operator
// without parentheses, so we use prec for v.X.
// For the same reason, the right side cannot reuse the same
// operator, or else a parse tree for a + (b + c), where the ( ) are
// not present in the source, will format as a + b + c, which
// means (a + b) + c. Treat the right expression as appearing
// in a context one precedence level higher: use prec+1 for v.Y.
//
// Line breaks: if we are to break the line immediately after
// the operator, introduce a margin at the current column,
// so that the second operand lines up with the first one and
// also so that neither operand can use space to the left.
// If the operator is an =, indent the right side another 4 spaces.
prec := opPrec[v.Op]
addParen(prec)
m := p.margin
if v.LineBreak {
p.margin = p.indent()
}
p.expr(v.X, prec)
p.printf(" %s", v.Op)
if v.LineBreak {
p.breakline()
} else {
p.printf(" ")
}
p.expr(v.Y, prec+1)
p.margin = m
case *AssignExpr:
addParen(precAssign)
m := p.margin
if v.LineBreak {
p.margin = p.indent() + listIndentation
}
p.expr(v.LHS, precAssign)
p.printf(" %s", v.Op)
if v.LineBreak {
p.breakline()
} else {
p.printf(" ")
}
p.expr(v.RHS, precAssign+1)
p.margin = m
case *ParenExpr:
p.seq("()", &v.Start, &[]Expr{v.X}, &v.End, modeParen, false, v.ForceMultiLine)
case *CallExpr:
forceCompact := v.ForceCompact
if p.fileType == TypeModule && isBazelDep(v) {
start, end := v.Span()
forceCompact = start.Line == end.Line
}
addParen(precSuffix)
p.expr(v.X, precSuffix)
p.seq("()", &v.ListStart, &v.List, &v.End, modeCall, forceCompact, v.ForceMultiLine)
case *LoadStmt:
addParen(precSuffix)
p.printf("load")
args := []Expr{v.Module}
for i := range v.From {
from := v.From[i]
to := v.To[i]
var arg Expr
if from.Name == to.Name {
// Suffix comments are attached to the `to` token,
// Before comments are attached to the `from` token,
// they need to be combined.
arg = from.asString()
arg.Comment().Before = to.Comment().Before
} else {
arg = &AssignExpr{
LHS: to,
Op: "=",
RHS: from.asString(),
}
}
args = append(args, arg)
}
p.seq("()", &v.Load, &args, &v.Rparen, modeLoad, v.ForceCompact, false)
case *ListExpr:
p.seq("[]", &v.Start, &v.List, &v.End, modeList, false, v.ForceMultiLine)
case *SetExpr:
p.seq("{}", &v.Start, &v.List, &v.End, modeList, false, v.ForceMultiLine)
case *TupleExpr:
mode := modeTuple
if v.NoBrackets {
mode = modeSeq
}
p.seq("()", &v.Start, &v.List, &v.End, mode, v.ForceCompact, v.ForceMultiLine)
case *DictExpr:
var list []Expr
for _, x := range v.List {
list = append(list, x)
}
p.seq("{}", &v.Start, &list, &v.End, modeDict, false, v.ForceMultiLine)
case *Comprehension:
p.listFor(v)
case *ConditionalExpr:
addParen(precSuffix)
p.expr(v.Then, precIfElse)
p.printf(" if ")
p.expr(v.Test, precIfElse)
p.printf(" else ")
p.expr(v.Else, precIfElse)
case *ReturnStmt:
p.printf("return")
if v.Result != nil {
p.printf(" ")
p.expr(v.Result, precLow)
}
case *DefStmt:
p.printf("def ")
p.printf(v.Name)
p.seq("()", &v.StartPos, &v.Params, nil, modeDef, v.ForceCompact, v.ForceMultiLine)
if v.Type != nil {
p.printf(" -> ")
p.expr(v.Type, precLow)
}
p.printf(":")
p.nestedStatements(v.Body)
case *ForStmt:
p.printf("for ")
p.expr(v.Vars, precLow)
p.printf(" in ")
p.expr(v.X, precLow)
p.printf(":")
p.nestedStatements(v.Body)
case *IfStmt:
block := v
isFirst := true
needsEmptyLine := false
for {
p.newlineIfNeeded()
if !isFirst {
if needsEmptyLine {
p.newline()
}
p.printf("el")
}
p.printf("if ")
p.expr(block.Cond, precLow)
p.printf(":")
p.nestedStatements(block.True)
isFirst = false
_, end := block.True[len(block.True)-1].Span()
needsEmptyLine = block.ElsePos.Pos.Line-end.Line > 1
// If the else-block contains just one statement which is an IfStmt, flatten it as a part
// of if-elif chain.
// Don't do it if the "else" statement has a suffix comment or if the next "if" statement
// has a before-comment.
if len(block.False) != 1 {
break
}
next, ok := block.False[0].(*IfStmt)
if !ok {
break
}
if len(block.ElsePos.Comment().Suffix) == 0 && len(next.Comment().Before) == 0 {
block = next
continue
}
break
}
if len(block.False) > 0 {
p.newlineIfNeeded()
if needsEmptyLine {
p.newline()
}
p.printf("else:")
p.comment = append(p.comment, block.ElsePos.Comment().Suffix...)
p.nestedStatements(block.False)
}
case *ForClause:
p.printf("for ")
p.expr(v.Vars, precLow)
p.printf(" in ")
p.expr(v.X, precLow)
case *IfClause:
p.printf("if ")
p.expr(v.Cond, precLow)
}
// Add closing parenthesis if needed.
if parenthesized {
p.depth--
p.printf(")")
}
// Queue end-of-line comments for printing when we
// reach the end of the line.
p.comment = append(p.comment, v.Comment().Suffix...)
}