818 lines
20 KiB
Go
818 lines
20 KiB
Go
/*
|
|
Copyright 2016 Google Inc. All Rights Reserved.
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
you may not use this file except in compliance with the License.
|
|
You may obtain a copy of the License at
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
See the License for the specific language governing permissions and
|
|
limitations under the License.
|
|
*/
|
|
// Rewriting of high-level (not purely syntactic) BUILD constructs.
|
|
|
|
package build
|
|
|
|
import (
|
|
"path"
|
|
"regexp"
|
|
"sort"
|
|
"strings"
|
|
|
|
"github.com/bazelbuild/buildtools/tables"
|
|
)
|
|
|
|
// For debugging: flag to disable certain rewrites.
|
|
var DisableRewrites []string
|
|
|
|
// disabled reports whether the named rewrite is disabled.
|
|
func disabled(name string) bool {
|
|
for _, x := range DisableRewrites {
|
|
if name == x {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// For debugging: allow sorting of these lists even with sorting otherwise disabled.
|
|
var AllowSort []string
|
|
|
|
// allowedSort reports whether sorting is allowed in the named context.
|
|
func allowedSort(name string) bool {
|
|
for _, x := range AllowSort {
|
|
if name == x {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// Rewrite applies the high-level Buildifier rewrites to f, modifying it in place.
|
|
// If info is non-nil, Rewrite updates it with information about the rewrite.
|
|
func Rewrite(f *File, info *RewriteInfo) {
|
|
// Allocate an info so that helpers can assume it's there.
|
|
if info == nil {
|
|
info = new(RewriteInfo)
|
|
}
|
|
|
|
for _, r := range rewrites {
|
|
if !disabled(r.name) {
|
|
r.fn(f, info)
|
|
}
|
|
}
|
|
}
|
|
|
|
// RewriteInfo collects information about what Rewrite did.
|
|
type RewriteInfo struct {
|
|
EditLabel int // number of label strings edited
|
|
NameCall int // number of calls with argument names added
|
|
SortCall int // number of call argument lists sorted
|
|
SortStringList int // number of string lists sorted
|
|
UnsafeSort int // number of unsafe string lists sorted
|
|
Log []string // log entries - may change
|
|
}
|
|
|
|
func (info *RewriteInfo) String() string {
|
|
s := ""
|
|
if info.EditLabel > 0 {
|
|
s += " label"
|
|
}
|
|
if info.NameCall > 0 {
|
|
s += " callname"
|
|
}
|
|
if info.SortCall > 0 {
|
|
s += " callsort"
|
|
}
|
|
if info.SortStringList > 0 {
|
|
s += " listsort"
|
|
}
|
|
if info.UnsafeSort > 0 {
|
|
s += " unsafesort"
|
|
}
|
|
if s != "" {
|
|
s = s[1:]
|
|
}
|
|
return s
|
|
}
|
|
|
|
// rewrites is the list of all Buildifier rewrites, in the order in which they are applied.
|
|
// The order here matters: for example, label canonicalization must happen
|
|
// before sorting lists of strings.
|
|
var rewrites = []struct {
|
|
name string
|
|
fn func(*File, *RewriteInfo)
|
|
}{
|
|
{"callsort", sortCallArgs},
|
|
{"label", fixLabels},
|
|
{"listsort", sortStringLists},
|
|
{"multiplus", fixMultilinePlus},
|
|
}
|
|
|
|
// leaveAlone reports whether any of the nodes on the stack are marked
|
|
// with a comment containing "buildifier: leave-alone".
|
|
func leaveAlone(stk []Expr, final Expr) bool {
|
|
for _, x := range stk {
|
|
if leaveAlone1(x) {
|
|
return true
|
|
}
|
|
}
|
|
if final != nil && leaveAlone1(final) {
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
// hasComment reports whether x is marked with a comment that
|
|
// after being converted to lower case, contains the specified text.
|
|
func hasComment(x Expr, text string) bool {
|
|
for _, com := range x.Comment().Before {
|
|
if strings.Contains(strings.ToLower(com.Token), text) {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// leaveAlone1 reports whether x is marked with a comment containing
|
|
// "buildifier: leave-alone", case-insensitive.
|
|
func leaveAlone1(x Expr) bool {
|
|
return hasComment(x, "buildifier: leave-alone")
|
|
}
|
|
|
|
// doNotSort reports whether x is marked with a comment containing
|
|
// "do not sort", case-insensitive.
|
|
func doNotSort(x Expr) bool {
|
|
return hasComment(x, "do not sort")
|
|
}
|
|
|
|
// keepSorted reports whether x is marked with a comment containing
|
|
// "keep sorted", case-insensitive.
|
|
func keepSorted(x Expr) bool {
|
|
return hasComment(x, "keep sorted")
|
|
}
|
|
|
|
// fixLabels rewrites labels into a canonical form.
|
|
//
|
|
// First, it joins labels written as string addition, turning
|
|
// "//x" + ":y" (usually split across multiple lines) into "//x:y".
|
|
//
|
|
// Second, it removes redundant target qualifiers, turning labels like
|
|
// "//third_party/m4:m4" into "//third_party/m4" as well as ones like
|
|
// "@foo//:foo" into "@foo".
|
|
//
|
|
func fixLabels(f *File, info *RewriteInfo) {
|
|
joinLabel := func(p *Expr) {
|
|
add, ok := (*p).(*BinaryExpr)
|
|
if !ok || add.Op != "+" {
|
|
return
|
|
}
|
|
str1, ok := add.X.(*StringExpr)
|
|
if !ok || !strings.HasPrefix(str1.Value, "//") || strings.Contains(str1.Value, " ") {
|
|
return
|
|
}
|
|
str2, ok := add.Y.(*StringExpr)
|
|
if !ok || strings.Contains(str2.Value, " ") {
|
|
return
|
|
}
|
|
info.EditLabel++
|
|
str1.Value += str2.Value
|
|
|
|
// Deleting nodes add and str2.
|
|
// Merge comments from add, str1, and str2 and save in str1.
|
|
com1 := add.Comment()
|
|
com2 := str1.Comment()
|
|
com3 := str2.Comment()
|
|
com1.Before = append(com1.Before, com2.Before...)
|
|
com1.Before = append(com1.Before, com3.Before...)
|
|
com1.Suffix = append(com1.Suffix, com2.Suffix...)
|
|
com1.Suffix = append(com1.Suffix, com3.Suffix...)
|
|
*str1.Comment() = *com1
|
|
|
|
*p = str1
|
|
}
|
|
|
|
labelPrefix := "//"
|
|
if tables.StripLabelLeadingSlashes {
|
|
labelPrefix = ""
|
|
}
|
|
// labelRE matches label strings, e.g. @r//x/y/z:abc
|
|
// where $1 is @r//x/y/z, $2 is @r//, $3 is r, $4 is z, $5 is abc.
|
|
labelRE := regexp.MustCompile(`^(((?:@(\w+))?//|` + labelPrefix + `)(?:.+/)?([^:]*))(?::([^:]+))?$`)
|
|
|
|
shortenLabel := func(v Expr) {
|
|
str, ok := v.(*StringExpr)
|
|
if !ok {
|
|
return
|
|
}
|
|
editPerformed := false
|
|
|
|
if tables.StripLabelLeadingSlashes && strings.HasPrefix(str.Value, "//") {
|
|
if path.Dir(f.Path) == "." || !strings.HasPrefix(str.Value, "//:") {
|
|
editPerformed = true
|
|
str.Value = str.Value[2:]
|
|
}
|
|
}
|
|
|
|
if tables.ShortenAbsoluteLabelsToRelative {
|
|
thisPackage := labelPrefix + path.Dir(f.Path)
|
|
if str.Value == thisPackage {
|
|
editPerformed = true
|
|
str.Value = ":" + path.Base(str.Value)
|
|
} else if strings.HasPrefix(str.Value, thisPackage+":") {
|
|
editPerformed = true
|
|
str.Value = str.Value[len(thisPackage):]
|
|
}
|
|
}
|
|
|
|
m := labelRE.FindStringSubmatch(str.Value)
|
|
if m == nil {
|
|
return
|
|
}
|
|
if m[4] != "" && m[4] == m[5] { // e.g. //foo:foo
|
|
editPerformed = true
|
|
str.Value = m[1]
|
|
} else if m[3] != "" && m[4] == "" && m[3] == m[5] { // e.g. @foo//:foo
|
|
editPerformed = true
|
|
str.Value = "@" + m[3]
|
|
}
|
|
if editPerformed {
|
|
info.EditLabel++
|
|
}
|
|
}
|
|
|
|
Walk(f, func(v Expr, stk []Expr) {
|
|
switch v := v.(type) {
|
|
case *CallExpr:
|
|
if leaveAlone(stk, v) {
|
|
return
|
|
}
|
|
for i := range v.List {
|
|
if leaveAlone1(v.List[i]) {
|
|
continue
|
|
}
|
|
as, ok := v.List[i].(*BinaryExpr)
|
|
if !ok || as.Op != "=" {
|
|
continue
|
|
}
|
|
key, ok := as.X.(*LiteralExpr)
|
|
if !ok || !tables.IsLabelArg[key.Token] || tables.LabelBlacklist[callName(v)+"."+key.Token] {
|
|
continue
|
|
}
|
|
if leaveAlone1(as.Y) {
|
|
continue
|
|
}
|
|
if list, ok := as.Y.(*ListExpr); ok {
|
|
for i := range list.List {
|
|
if leaveAlone1(list.List[i]) {
|
|
continue
|
|
}
|
|
joinLabel(&list.List[i])
|
|
shortenLabel(list.List[i])
|
|
}
|
|
}
|
|
if set, ok := as.Y.(*SetExpr); ok {
|
|
for i := range set.List {
|
|
if leaveAlone1(set.List[i]) {
|
|
continue
|
|
}
|
|
joinLabel(&set.List[i])
|
|
shortenLabel(set.List[i])
|
|
}
|
|
} else {
|
|
joinLabel(&as.Y)
|
|
shortenLabel(as.Y)
|
|
}
|
|
}
|
|
}
|
|
})
|
|
}
|
|
|
|
// callName returns the name of the rule being called by call.
|
|
// If the call is not to a literal rule name, callName returns "".
|
|
func callName(call *CallExpr) string {
|
|
rule, ok := call.X.(*LiteralExpr)
|
|
if !ok {
|
|
return ""
|
|
}
|
|
return rule.Token
|
|
}
|
|
|
|
// sortCallArgs sorts lists of named arguments to a call.
|
|
func sortCallArgs(f *File, info *RewriteInfo) {
|
|
Walk(f, func(v Expr, stk []Expr) {
|
|
call, ok := v.(*CallExpr)
|
|
if !ok {
|
|
return
|
|
}
|
|
if leaveAlone(stk, call) {
|
|
return
|
|
}
|
|
rule := callName(call)
|
|
if rule == "" {
|
|
return
|
|
}
|
|
|
|
// Find the tail of the argument list with named arguments.
|
|
start := len(call.List)
|
|
for start > 0 && argName(call.List[start-1]) != "" {
|
|
start--
|
|
}
|
|
|
|
// Record information about each arg into a sortable list.
|
|
var args namedArgs
|
|
for i, x := range call.List[start:] {
|
|
name := argName(x)
|
|
args = append(args, namedArg{ruleNamePriority(rule, name), name, i, x})
|
|
}
|
|
|
|
// Sort the list and put the args back in the new order.
|
|
if sort.IsSorted(args) {
|
|
return
|
|
}
|
|
info.SortCall++
|
|
sort.Sort(args)
|
|
for i, x := range args {
|
|
call.List[start+i] = x.expr
|
|
}
|
|
})
|
|
}
|
|
|
|
// ruleNamePriority maps a rule argument name to its sorting priority.
|
|
// It could use the auto-generated per-rule tables but for now it just
|
|
// falls back to the original list.
|
|
func ruleNamePriority(rule, arg string) int {
|
|
ruleArg := rule + "." + arg
|
|
if val, ok := tables.NamePriority[ruleArg]; ok {
|
|
return val
|
|
}
|
|
return tables.NamePriority[arg]
|
|
/*
|
|
list := ruleArgOrder[rule]
|
|
if len(list) == 0 {
|
|
return tables.NamePriority[arg]
|
|
}
|
|
for i, x := range list {
|
|
if x == arg {
|
|
return i
|
|
}
|
|
}
|
|
return len(list)
|
|
*/
|
|
}
|
|
|
|
// If x is of the form key=value, argName returns the string key.
|
|
// Otherwise argName returns "".
|
|
func argName(x Expr) string {
|
|
if as, ok := x.(*BinaryExpr); ok && as.Op == "=" {
|
|
if id, ok := as.X.(*LiteralExpr); ok {
|
|
return id.Token
|
|
}
|
|
}
|
|
return ""
|
|
}
|
|
|
|
// A namedArg records information needed for sorting
|
|
// a named call argument into its proper position.
|
|
type namedArg struct {
|
|
priority int // kind of name; first sort key
|
|
name string // name; second sort key
|
|
index int // original index; final sort key
|
|
expr Expr // name=value argument
|
|
}
|
|
|
|
// namedArgs is a slice of namedArg that implements sort.Interface
|
|
type namedArgs []namedArg
|
|
|
|
func (x namedArgs) Len() int { return len(x) }
|
|
func (x namedArgs) Swap(i, j int) { x[i], x[j] = x[j], x[i] }
|
|
|
|
func (x namedArgs) Less(i, j int) bool {
|
|
p := x[i]
|
|
q := x[j]
|
|
if p.priority != q.priority {
|
|
return p.priority < q.priority
|
|
}
|
|
if p.name != q.name {
|
|
return p.name < q.name
|
|
}
|
|
return p.index < q.index
|
|
}
|
|
|
|
// sortStringLists sorts lists of string literals used as specific rule arguments.
|
|
func sortStringLists(f *File, info *RewriteInfo) {
|
|
Walk(f, func(v Expr, stk []Expr) {
|
|
switch v := v.(type) {
|
|
case *CallExpr:
|
|
if leaveAlone(stk, v) {
|
|
return
|
|
}
|
|
rule := callName(v)
|
|
for _, arg := range v.List {
|
|
if leaveAlone1(arg) {
|
|
continue
|
|
}
|
|
as, ok := arg.(*BinaryExpr)
|
|
if !ok || as.Op != "=" || leaveAlone1(as) || doNotSort(as) {
|
|
continue
|
|
}
|
|
key, ok := as.X.(*LiteralExpr)
|
|
if !ok {
|
|
continue
|
|
}
|
|
context := rule + "." + key.Token
|
|
if !tables.IsSortableListArg[key.Token] || tables.SortableBlacklist[context] {
|
|
continue
|
|
}
|
|
if disabled("unsafesort") && !tables.SortableWhitelist[context] && !allowedSort(context) {
|
|
continue
|
|
}
|
|
sortStringList(as.Y, info, context)
|
|
}
|
|
case *BinaryExpr:
|
|
if disabled("unsafesort") {
|
|
return
|
|
}
|
|
// "keep sorted" comment on x = list forces sorting of list.
|
|
as := v
|
|
if as.Op == "=" && keepSorted(as) {
|
|
sortStringList(as.Y, info, "?")
|
|
}
|
|
case *KeyValueExpr:
|
|
if disabled("unsafesort") {
|
|
return
|
|
}
|
|
// "keep sorted" before key: list also forces sorting of list.
|
|
if keepSorted(v) {
|
|
sortStringList(v.Value, info, "?")
|
|
}
|
|
case *ListExpr:
|
|
if disabled("unsafesort") {
|
|
return
|
|
}
|
|
// "keep sorted" comment above first list element also forces sorting of list.
|
|
if len(v.List) > 0 && keepSorted(v.List[0]) {
|
|
sortStringList(v, info, "?")
|
|
}
|
|
}
|
|
})
|
|
}
|
|
|
|
// SortStringList sorts x, a list of strings.
|
|
func SortStringList(x Expr) {
|
|
sortStringList(x, nil, "")
|
|
}
|
|
|
|
// sortStringList sorts x, a list of strings.
|
|
// The list is broken by non-strings and by blank lines and comments into chunks.
|
|
// Each chunk is sorted in place.
|
|
func sortStringList(x Expr, info *RewriteInfo, context string) {
|
|
list, ok := x.(*ListExpr)
|
|
if !ok || len(list.List) < 2 || doNotSort(list.List[0]) {
|
|
return
|
|
}
|
|
|
|
forceSort := keepSorted(list.List[0])
|
|
|
|
// TODO(bazel-team): Decide how to recognize lists that cannot
|
|
// be sorted. Avoiding all lists with comments avoids sorting
|
|
// lists that say explicitly, in some form or another, why they
|
|
// cannot be sorted. For example, many cc_test rules require
|
|
// certain order in their deps attributes.
|
|
if !forceSort {
|
|
if line, _ := hasComments(list); line {
|
|
return
|
|
}
|
|
}
|
|
|
|
// Sort chunks of the list with no intervening blank lines or comments.
|
|
for i := 0; i < len(list.List); {
|
|
if _, ok := list.List[i].(*StringExpr); !ok {
|
|
i++
|
|
continue
|
|
}
|
|
|
|
j := i + 1
|
|
for ; j < len(list.List); j++ {
|
|
if str, ok := list.List[j].(*StringExpr); !ok || len(str.Before) > 0 {
|
|
break
|
|
}
|
|
}
|
|
|
|
var chunk []stringSortKey
|
|
for index, x := range list.List[i:j] {
|
|
chunk = append(chunk, makeSortKey(index, x.(*StringExpr)))
|
|
}
|
|
if !sort.IsSorted(byStringExpr(chunk)) || !isUniq(chunk) {
|
|
if info != nil {
|
|
info.SortStringList++
|
|
if !tables.SortableWhitelist[context] {
|
|
info.UnsafeSort++
|
|
info.Log = append(info.Log, "sort:"+context)
|
|
}
|
|
}
|
|
before := chunk[0].x.Comment().Before
|
|
chunk[0].x.Comment().Before = nil
|
|
|
|
sort.Sort(byStringExpr(chunk))
|
|
chunk = uniq(chunk)
|
|
|
|
chunk[0].x.Comment().Before = before
|
|
for offset, key := range chunk {
|
|
list.List[i+offset] = key.x
|
|
}
|
|
list.List = append(list.List[:(i+len(chunk))], list.List[j:]...)
|
|
}
|
|
|
|
i = j
|
|
}
|
|
}
|
|
|
|
// uniq removes duplicates from a list, which must already be sorted.
|
|
// It edits the list in place.
|
|
func uniq(sortedList []stringSortKey) []stringSortKey {
|
|
out := sortedList[:0]
|
|
for _, sk := range sortedList {
|
|
if len(out) == 0 || sk.value != out[len(out)-1].value {
|
|
out = append(out, sk)
|
|
}
|
|
}
|
|
return out
|
|
}
|
|
|
|
// isUniq reports whether the sorted list only contains unique elements.
|
|
func isUniq(list []stringSortKey) bool {
|
|
for i := range list {
|
|
if i+1 < len(list) && list[i].value == list[i+1].value {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
// If stk describes a call argument like rule(arg=...), callArgName
|
|
// returns the name of that argument, formatted as "rule.arg".
|
|
func callArgName(stk []Expr) string {
|
|
n := len(stk)
|
|
if n < 2 {
|
|
return ""
|
|
}
|
|
arg := argName(stk[n-1])
|
|
if arg == "" {
|
|
return ""
|
|
}
|
|
call, ok := stk[n-2].(*CallExpr)
|
|
if !ok {
|
|
return ""
|
|
}
|
|
rule, ok := call.X.(*LiteralExpr)
|
|
if !ok {
|
|
return ""
|
|
}
|
|
return rule.Token + "." + arg
|
|
}
|
|
|
|
// A stringSortKey records information about a single string literal to be
|
|
// sorted. The strings are first grouped into four phases: most strings,
|
|
// strings beginning with ":", strings beginning with "//", and strings
|
|
// beginning with "@". The next significant part of the comparison is the list
|
|
// of elements in the value, where elements are split at `.' and `:'. Finally
|
|
// we compare by value and break ties by original index.
|
|
type stringSortKey struct {
|
|
phase int
|
|
split []string
|
|
value string
|
|
original int
|
|
x Expr
|
|
}
|
|
|
|
func makeSortKey(index int, x *StringExpr) stringSortKey {
|
|
key := stringSortKey{
|
|
value: x.Value,
|
|
original: index,
|
|
x: x,
|
|
}
|
|
|
|
switch {
|
|
case strings.HasPrefix(x.Value, ":"):
|
|
key.phase = 1
|
|
case strings.HasPrefix(x.Value, "//") || (tables.StripLabelLeadingSlashes && !strings.HasPrefix(x.Value, "@")):
|
|
key.phase = 2
|
|
case strings.HasPrefix(x.Value, "@"):
|
|
key.phase = 3
|
|
}
|
|
|
|
key.split = strings.Split(strings.Replace(x.Value, ":", ".", -1), ".")
|
|
return key
|
|
}
|
|
|
|
// byStringExpr implements sort.Interface for a list of stringSortKey.
|
|
type byStringExpr []stringSortKey
|
|
|
|
func (x byStringExpr) Len() int { return len(x) }
|
|
func (x byStringExpr) Swap(i, j int) { x[i], x[j] = x[j], x[i] }
|
|
|
|
func (x byStringExpr) Less(i, j int) bool {
|
|
xi := x[i]
|
|
xj := x[j]
|
|
|
|
if xi.phase != xj.phase {
|
|
return xi.phase < xj.phase
|
|
}
|
|
for k := 0; k < len(xi.split) && k < len(xj.split); k++ {
|
|
if xi.split[k] != xj.split[k] {
|
|
return xi.split[k] < xj.split[k]
|
|
}
|
|
}
|
|
if len(xi.split) != len(xj.split) {
|
|
return len(xi.split) < len(xj.split)
|
|
}
|
|
if xi.value != xj.value {
|
|
return xi.value < xj.value
|
|
}
|
|
return xi.original < xj.original
|
|
}
|
|
|
|
// fixMultilinePlus turns
|
|
//
|
|
// ... +
|
|
// [ ... ]
|
|
//
|
|
// ... +
|
|
// call(...)
|
|
//
|
|
// into
|
|
// ... + [
|
|
// ...
|
|
// ]
|
|
//
|
|
// ... + call(
|
|
// ...
|
|
// )
|
|
//
|
|
// which typically works better with our aggressively compact formatting.
|
|
func fixMultilinePlus(f *File, info *RewriteInfo) {
|
|
|
|
// List manipulation helpers.
|
|
// As a special case, we treat f([...]) as a list, mainly
|
|
// for glob.
|
|
|
|
// isList reports whether x is a list.
|
|
var isList func(x Expr) bool
|
|
isList = func(x Expr) bool {
|
|
switch x := x.(type) {
|
|
case *ListExpr:
|
|
return true
|
|
case *CallExpr:
|
|
if len(x.List) == 1 {
|
|
return isList(x.List[0])
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// isMultiLine reports whether x is a multiline list.
|
|
var isMultiLine func(Expr) bool
|
|
isMultiLine = func(x Expr) bool {
|
|
switch x := x.(type) {
|
|
case *ListExpr:
|
|
return x.ForceMultiLine || len(x.List) > 1
|
|
case *CallExpr:
|
|
if x.ForceMultiLine || len(x.List) > 1 && !x.ForceCompact {
|
|
return true
|
|
}
|
|
if len(x.List) == 1 {
|
|
return isMultiLine(x.List[0])
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// forceMultiLine tries to force the list x to use a multiline form.
|
|
// It reports whether it was successful.
|
|
var forceMultiLine func(Expr) bool
|
|
forceMultiLine = func(x Expr) bool {
|
|
switch x := x.(type) {
|
|
case *ListExpr:
|
|
// Already multi line?
|
|
if x.ForceMultiLine {
|
|
return true
|
|
}
|
|
// If this is a list containing a list, force the
|
|
// inner list to be multiline instead.
|
|
if len(x.List) == 1 && forceMultiLine(x.List[0]) {
|
|
return true
|
|
}
|
|
x.ForceMultiLine = true
|
|
return true
|
|
|
|
case *CallExpr:
|
|
if len(x.List) == 1 {
|
|
return forceMultiLine(x.List[0])
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
skip := map[Expr]bool{}
|
|
Walk(f, func(v Expr, stk []Expr) {
|
|
if skip[v] {
|
|
return
|
|
}
|
|
bin, ok := v.(*BinaryExpr)
|
|
if !ok || bin.Op != "+" {
|
|
return
|
|
}
|
|
|
|
// Found a +.
|
|
// w + x + y + z parses as ((w + x) + y) + z,
|
|
// so chase down the left side to make a list of
|
|
// all the things being added together, separated
|
|
// by the BinaryExprs that join them.
|
|
// Mark them as "skip" so that when Walk recurses
|
|
// into the subexpressions, we won't reprocess them.
|
|
var all []Expr
|
|
for {
|
|
all = append(all, bin.Y, bin)
|
|
bin1, ok := bin.X.(*BinaryExpr)
|
|
if !ok || bin1.Op != "+" {
|
|
break
|
|
}
|
|
bin = bin1
|
|
skip[bin] = true
|
|
}
|
|
all = append(all, bin.X)
|
|
|
|
// Because the outermost expression was the
|
|
// rightmost one, the list is backward. Reverse it.
|
|
for i, j := 0, len(all)-1; i < j; i, j = i+1, j-1 {
|
|
all[i], all[j] = all[j], all[i]
|
|
}
|
|
|
|
// The 'all' slice is alternating addends and BinaryExpr +'s:
|
|
// w, +, x, +, y, +, z
|
|
// If there are no lists involved, don't rewrite anything.
|
|
haveList := false
|
|
for i := 0; i < len(all); i += 2 {
|
|
if isList(all[i]) {
|
|
haveList = true
|
|
break
|
|
}
|
|
}
|
|
if !haveList {
|
|
return
|
|
}
|
|
|
|
// Okay, there are lists.
|
|
// Consider each + next to a line break.
|
|
for i := 1; i < len(all); i += 2 {
|
|
bin := all[i].(*BinaryExpr)
|
|
if !bin.LineBreak {
|
|
continue
|
|
}
|
|
|
|
// We're going to break the line after the +.
|
|
// If it is followed by a list, force that to be
|
|
// multiline instead.
|
|
if forceMultiLine(all[i+1]) {
|
|
bin.LineBreak = false
|
|
continue
|
|
}
|
|
|
|
// If the previous list was multiline already,
|
|
// don't bother with the line break after
|
|
// the +.
|
|
if isMultiLine(all[i-1]) {
|
|
bin.LineBreak = false
|
|
continue
|
|
}
|
|
}
|
|
})
|
|
}
|
|
|
|
// hasComments reports whether any comments are associated with
|
|
// the list or its elements.
|
|
func hasComments(list *ListExpr) (line, suffix bool) {
|
|
com := list.Comment()
|
|
if len(com.Before) > 0 || len(com.After) > 0 || len(list.End.Before) > 0 {
|
|
line = true
|
|
}
|
|
if len(com.Suffix) > 0 {
|
|
suffix = true
|
|
}
|
|
for _, elem := range list.List {
|
|
com := elem.Comment()
|
|
if len(com.Before) > 0 {
|
|
line = true
|
|
}
|
|
if len(com.Suffix) > 0 {
|
|
suffix = true
|
|
}
|
|
}
|
|
return
|
|
}
|