...

Source file src/github.com/bazelbuild/buildtools/build/rewrite.go

Documentation: github.com/bazelbuild/buildtools/build

     1  /*
     2  Copyright 2016 Google LLC
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      https://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  // Rewriting of high-level (not purely syntactic) BUILD constructs.
    18  
    19  package build
    20  
    21  import (
    22  	"path"
    23  	"path/filepath"
    24  	"regexp"
    25  	"sort"
    26  	"strings"
    27  
    28  	"github.com/bazelbuild/buildtools/tables"
    29  )
    30  
    31  // DisableRewrites disables certain rewrites (for debugging).
    32  var DisableRewrites []string
    33  
    34  // disabled reports whether the named rewrite is disabled.
    35  func disabled(name string) bool {
    36  	for _, x := range DisableRewrites {
    37  		if name == x {
    38  			return true
    39  		}
    40  	}
    41  	return false
    42  }
    43  
    44  // AllowSort allows sorting of these lists even with sorting otherwise disabled (for debugging).
    45  var AllowSort []string
    46  
    47  // allowedSort reports whether sorting is allowed in the named context.
    48  func allowedSort(name string) bool {
    49  	for _, x := range AllowSort {
    50  		if name == x {
    51  			return true
    52  		}
    53  	}
    54  	return false
    55  }
    56  
    57  // Rewriter controls the rewrites to be applied.
    58  //
    59  // If non-nil, the rewrites with the specified names will be run. If
    60  // nil, a default set of rewrites will be used that is determined by
    61  // the type (BUILD vs default starlark) of the file being rewritten.
    62  type Rewriter struct {
    63  	RewriteSet                      []string
    64  	IsLabelArg                      map[string]bool
    65  	LabelDenyList                   map[string]bool
    66  	IsSortableListArg               map[string]bool
    67  	SortableDenylist                map[string]bool
    68  	SortableAllowlist               map[string]bool
    69  	NamePriority                    map[string]int
    70  	StripLabelLeadingSlashes        bool
    71  	ShortenAbsoluteLabelsToRelative bool
    72  }
    73  
    74  // Rewrite applies rewrites to a file
    75  func Rewrite(f *File) {
    76  	var rewriter = &Rewriter{
    77  		IsLabelArg:                      tables.IsLabelArg,
    78  		LabelDenyList:                   tables.LabelDenylist,
    79  		IsSortableListArg:               tables.IsSortableListArg,
    80  		SortableDenylist:                tables.SortableDenylist,
    81  		SortableAllowlist:               tables.SortableAllowlist,
    82  		NamePriority:                    tables.NamePriority,
    83  		StripLabelLeadingSlashes:        tables.StripLabelLeadingSlashes,
    84  		ShortenAbsoluteLabelsToRelative: tables.ShortenAbsoluteLabelsToRelative,
    85  	}
    86  	rewriter.Rewrite(f)
    87  }
    88  
    89  // Rewrite applies the rewrites to a file
    90  func (w *Rewriter) Rewrite(f *File) {
    91  	for _, r := range rewrites {
    92  		// f.Type&r.scope is a bitwise comparison. Because starlark files result in a scope that will
    93  		// not be changed by rewrites, we have included another check looking on the right side.
    94  		// If we have an empty rewrite set, we do not want any rewrites to happen.
    95  		if (!disabled(r.name) && (f.Type&r.scope != 0) && w.RewriteSet == nil) || (w.RewriteSet != nil && rewriteSetContains(w, r.name)) {
    96  			r.fn(f, w)
    97  		}
    98  	}
    99  }
   100  
   101  func rewriteSetContains(w *Rewriter, name string) bool {
   102  	for _, value := range w.RewriteSet {
   103  		if value == name {
   104  			return true
   105  		}
   106  	}
   107  	return false
   108  }
   109  
   110  // Each rewrite function can be either applied for BUILD files, other files (such as .bzl),
   111  // or all files.
   112  const (
   113  	scopeDefault = TypeDefault | TypeBzl                  // .bzl and generic Starlark files
   114  	scopeBuild   = TypeBuild | TypeWorkspace | TypeModule // BUILD, WORKSPACE, and MODULE files
   115  	scopeBoth    = scopeDefault | scopeBuild
   116  )
   117  
   118  // rewrites is the list of all Buildifier rewrites, in the order in which they are applied.
   119  // The order here matters: for example, label canonicalization must happen
   120  // before sorting lists of strings.
   121  var rewrites = []struct {
   122  	name  string
   123  	fn    func(*File, *Rewriter)
   124  	scope FileType
   125  }{
   126  	{"removeParens", removeParens, scopeBuild},
   127  	{"callsort", sortCallArgs, scopeBuild},
   128  	{"label", fixLabels, scopeBuild},
   129  	{"listsort", sortStringLists, scopeBoth},
   130  	{"multiplus", fixMultilinePlus, scopeBuild},
   131  	{"loadTop", moveLoadOnTop, scopeBoth},
   132  	{"sameOriginLoad", compressSameOriginLoads, scopeBoth},
   133  	{"sortLoadStatements", sortLoadStatements, scopeBoth},
   134  	{"loadsort", sortAllLoadArgs, scopeBoth},
   135  	{"useRepoPositionalsSort", sortUseRepoPositionals, TypeModule},
   136  	{"formatdocstrings", formatDocstrings, scopeBoth},
   137  	{"reorderarguments", reorderArguments, scopeBoth},
   138  	{"editoctal", editOctals, scopeBoth},
   139  }
   140  
   141  // leaveAlone reports whether any of the nodes on the stack are marked
   142  // with a comment containing "buildifier: leave-alone".
   143  func leaveAlone(stk []Expr, final Expr) bool {
   144  	for _, x := range stk {
   145  		if leaveAlone1(x) {
   146  			return true
   147  		}
   148  	}
   149  	if final != nil && leaveAlone1(final) {
   150  		return true
   151  	}
   152  	return false
   153  }
   154  
   155  // hasComment reports whether x is marked with a comment that
   156  // after being converted to lower case, contains the specified text.
   157  func hasComment(x Expr, text string) bool {
   158  	if x == nil {
   159  		return false
   160  	}
   161  	for _, com := range x.Comment().Before {
   162  		if strings.Contains(strings.ToLower(com.Token), text) {
   163  			return true
   164  		}
   165  	}
   166  	for _, com := range x.Comment().After {
   167  		if strings.Contains(strings.ToLower(com.Token), text) {
   168  			return true
   169  		}
   170  	}
   171  	for _, com := range x.Comment().Suffix {
   172  		if strings.Contains(strings.ToLower(com.Token), text) {
   173  			return true
   174  		}
   175  	}
   176  	return false
   177  }
   178  
   179  // isCommentAnywhere checks whether there's a comment containing the given text
   180  // anywhere in the file.
   181  func isCommentAnywhere(f *File, text string) bool {
   182  	commentExists := false
   183  	WalkInterruptable(f, func(node Expr, stack []Expr) (err error) {
   184  		if hasComment(node, text) {
   185  			commentExists = true
   186  			return &StopTraversalError{}
   187  		}
   188  		return nil
   189  	})
   190  	return commentExists
   191  }
   192  
   193  // leaveAlone1 reports whether x is marked with a comment containing
   194  // "buildifier: leave-alone", case-insensitive.
   195  func leaveAlone1(x Expr) bool {
   196  	return hasComment(x, "buildifier: leave-alone")
   197  }
   198  
   199  // doNotSort reports whether x is marked with a comment containing
   200  // "do not sort", case-insensitive.
   201  func doNotSort(x Expr) bool {
   202  	return hasComment(x, "do not sort")
   203  }
   204  
   205  // keepSorted reports whether x is marked with a comment containing
   206  // "keep sorted", case-insensitive.
   207  func keepSorted(x Expr) bool {
   208  	return hasComment(x, "keep sorted")
   209  }
   210  
   211  // fixLabels rewrites labels into a canonical form.
   212  //
   213  // First, it joins labels written as string addition, turning
   214  // "//x" + ":y" (usually split across multiple lines) into "//x:y".
   215  //
   216  // Second, it removes redundant target qualifiers, turning labels like
   217  // "//third_party/m4:m4" into "//third_party/m4" as well as ones like
   218  // "@foo//:foo" into "@foo".
   219  func fixLabels(f *File, w *Rewriter) {
   220  	joinLabel := func(p *Expr) {
   221  		add, ok := (*p).(*BinaryExpr)
   222  		if !ok || add.Op != "+" {
   223  			return
   224  		}
   225  		str1, ok := add.X.(*StringExpr)
   226  		if !ok || !strings.HasPrefix(str1.Value, "//") || strings.Contains(str1.Value, " ") {
   227  			return
   228  		}
   229  		str2, ok := add.Y.(*StringExpr)
   230  		if !ok || strings.Contains(str2.Value, " ") {
   231  			return
   232  		}
   233  		str1.Value += str2.Value
   234  
   235  		// Deleting nodes add and str2.
   236  		// Merge comments from add, str1, and str2 and save in str1.
   237  		com1 := add.Comment()
   238  		com2 := str1.Comment()
   239  		com3 := str2.Comment()
   240  		com1.Before = append(com1.Before, com2.Before...)
   241  		com1.Before = append(com1.Before, com3.Before...)
   242  		com1.Suffix = append(com1.Suffix, com2.Suffix...)
   243  		com1.Suffix = append(com1.Suffix, com3.Suffix...)
   244  		*str1.Comment() = *com1
   245  
   246  		*p = str1
   247  	}
   248  
   249  	labelPrefix := "//"
   250  	if w.StripLabelLeadingSlashes {
   251  		labelPrefix = ""
   252  	}
   253  	// labelRE matches label strings, e.g. @r//x/y/z:abc
   254  	// where $1 is @r//x/y/z, $2 is @r//, $3 is r, $4 is z, $5 is abc.
   255  	labelRE := regexp.MustCompile(`^(((?:@(\w+))?//|` + labelPrefix + `)(?:.+/)?([^:]*))(?::([^:]+))?$`)
   256  
   257  	shortenLabel := func(v Expr) {
   258  		str, ok := v.(*StringExpr)
   259  		if !ok {
   260  			return
   261  		}
   262  
   263  		if w.StripLabelLeadingSlashes && strings.HasPrefix(str.Value, "//") {
   264  			if filepath.Dir(f.Path) == "." || !strings.HasPrefix(str.Value, "//:") {
   265  				str.Value = str.Value[2:]
   266  			}
   267  		}
   268  		if w.ShortenAbsoluteLabelsToRelative {
   269  			thisPackage := labelPrefix + filepath.Dir(f.Path)
   270  			// filepath.Dir on Windows uses backslashes as separators, while labels always have slashes.
   271  			if filepath.Separator != '/' {
   272  				thisPackage = strings.Replace(thisPackage, string(filepath.Separator), "/", -1)
   273  			}
   274  
   275  			if str.Value == thisPackage {
   276  				str.Value = ":" + path.Base(str.Value)
   277  			} else if strings.HasPrefix(str.Value, thisPackage+":") {
   278  				str.Value = str.Value[len(thisPackage):]
   279  			}
   280  		}
   281  
   282  		m := labelRE.FindStringSubmatch(str.Value)
   283  		if m == nil {
   284  			return
   285  		}
   286  		if m[4] != "" && m[4] == m[5] { // e.g. //foo:foo
   287  			str.Value = m[1]
   288  		} else if m[3] != "" && m[4] == "" && m[3] == m[5] { // e.g. @foo//:foo
   289  			str.Value = "@" + m[3]
   290  		}
   291  	}
   292  
   293  	// Join and shorten labels within a container of labels (which can be a single
   294  	// label, e.g. a single string expression or a concatenation of them).
   295  	// Gracefully finish if the argument is of a different type.
   296  	fixLabelsWithinAContainer := func(e *Expr) {
   297  		if list, ok := (*e).(*ListExpr); ok {
   298  			for i := range list.List {
   299  				if leaveAlone1(list.List[i]) {
   300  					continue
   301  				}
   302  				joinLabel(&list.List[i])
   303  				shortenLabel(list.List[i])
   304  			}
   305  		}
   306  		if set, ok := (*e).(*SetExpr); ok {
   307  			for i := range set.List {
   308  				if leaveAlone1(set.List[i]) {
   309  					continue
   310  				}
   311  				joinLabel(&set.List[i])
   312  				shortenLabel(set.List[i])
   313  			}
   314  		} else {
   315  			joinLabel(e)
   316  			shortenLabel(*e)
   317  		}
   318  	}
   319  
   320  	Walk(f, func(v Expr, stk []Expr) {
   321  		switch v := v.(type) {
   322  		case *CallExpr:
   323  			if leaveAlone(stk, v) {
   324  				return
   325  			}
   326  			for i := range v.List {
   327  				if leaveAlone1(v.List[i]) {
   328  					continue
   329  				}
   330  				as, ok := v.List[i].(*AssignExpr)
   331  				if !ok {
   332  					continue
   333  				}
   334  				key, ok := as.LHS.(*Ident)
   335  				if !ok || !w.IsLabelArg[key.Name] || w.LabelDenyList[callName(v)+"."+key.Name] {
   336  					continue
   337  				}
   338  				if leaveAlone1(as.RHS) {
   339  					continue
   340  				}
   341  
   342  				findAndModifyStrings(&as.RHS, fixLabelsWithinAContainer)
   343  			}
   344  		}
   345  	})
   346  }
   347  
   348  // callName returns the name of the rule being called by call.
   349  // If the call is not to a literal rule name or a dot expression, callName
   350  // returns "".
   351  func callName(call *CallExpr) string {
   352  	return (&Rule{call, ""}).Kind()
   353  }
   354  
   355  // sortCallArgs sorts lists of named arguments to a call.
   356  func sortCallArgs(f *File, w *Rewriter) {
   357  
   358  	Walk(f, func(v Expr, stk []Expr) {
   359  		call, ok := v.(*CallExpr)
   360  		if !ok {
   361  			return
   362  		}
   363  		if leaveAlone(stk, call) {
   364  			return
   365  		}
   366  		rule := callName(call)
   367  		if rule == "" {
   368  			rule = "<complex rule kind>"
   369  		}
   370  
   371  		// Find the tail of the argument list with named arguments.
   372  		start := len(call.List)
   373  		for start > 0 && argName(call.List[start-1]) != "" {
   374  			start--
   375  		}
   376  
   377  		// Record information about each arg into a sortable list.
   378  		var args namedArgs
   379  		for i, x := range call.List[start:] {
   380  			name := argName(x)
   381  			args = append(args, namedArg{ruleNamePriority(w, rule, name), name, i, x})
   382  		}
   383  
   384  		// Sort the list and put the args back in the new order.
   385  		if sort.IsSorted(args) {
   386  			return
   387  		}
   388  		sort.Sort(args)
   389  		for i, x := range args {
   390  			call.List[start+i] = x.expr
   391  		}
   392  	})
   393  }
   394  
   395  // ruleNamePriority maps a rule argument name to its sorting priority.
   396  // It could use the auto-generated per-rule tables but for now it just
   397  // falls back to the original list.
   398  func ruleNamePriority(w *Rewriter, rule, arg string) int {
   399  	ruleArg := rule + "." + arg
   400  	if val, ok := w.NamePriority[ruleArg]; ok {
   401  		return val
   402  	}
   403  	return w.NamePriority[arg]
   404  
   405  	/*
   406  		list := ruleArgOrder[rule]
   407  		if len(list) == 0 {
   408  			return tables.NamePriority[arg]
   409  		}
   410  		for i, x := range list {
   411  			if x == arg {
   412  				return i
   413  			}
   414  		}
   415  		return len(list)
   416  	*/
   417  }
   418  
   419  // If x is of the form key=value, argName returns the string key.
   420  // Otherwise argName returns "".
   421  func argName(x Expr) string {
   422  	if as, ok := x.(*AssignExpr); ok {
   423  		if id, ok := as.LHS.(*Ident); ok {
   424  			return id.Name
   425  		}
   426  	}
   427  	return ""
   428  }
   429  
   430  // A namedArg records information needed for sorting
   431  // a named call argument into its proper position.
   432  type namedArg struct {
   433  	priority int    // kind of name; first sort key
   434  	name     string // name; second sort key
   435  	index    int    // original index; final sort key
   436  	expr     Expr   // name=value argument
   437  }
   438  
   439  // namedArgs is a slice of namedArg that implements sort.Interface
   440  type namedArgs []namedArg
   441  
   442  func (x namedArgs) Len() int      { return len(x) }
   443  func (x namedArgs) Swap(i, j int) { x[i], x[j] = x[j], x[i] }
   444  
   445  func (x namedArgs) Less(i, j int) bool {
   446  	p := x[i]
   447  	q := x[j]
   448  	if p.priority != q.priority {
   449  		return p.priority < q.priority
   450  	}
   451  	if p.name != q.name {
   452  		return p.name < q.name
   453  	}
   454  	return p.index < q.index
   455  }
   456  
   457  // sortStringLists sorts lists of string literals used as specific rule arguments.
   458  func sortStringLists(f *File, w *Rewriter) {
   459  	sortStringList := func(x *Expr) {
   460  		SortStringList(*x)
   461  	}
   462  
   463  	Walk(f, func(e Expr, stk []Expr) {
   464  		switch v := e.(type) {
   465  		case *CallExpr:
   466  			if f.Type == TypeDefault || f.Type == TypeBzl {
   467  				// Rule parameters, not applicable to .bzl or default file types
   468  				return
   469  			}
   470  			if leaveAlone(stk, v) {
   471  				return
   472  			}
   473  			rule := callName(v)
   474  			for _, arg := range v.List {
   475  				if leaveAlone1(arg) {
   476  					continue
   477  				}
   478  				as, ok := arg.(*AssignExpr)
   479  				if !ok || leaveAlone1(as) {
   480  					continue
   481  				}
   482  				key, ok := as.LHS.(*Ident)
   483  				if !ok {
   484  					continue
   485  				}
   486  				context := rule + "." + key.Name
   487  				if w.SortableDenylist[context] {
   488  					continue
   489  				}
   490  				if w.IsSortableListArg[key.Name] ||
   491  					w.SortableAllowlist[context] ||
   492  					(!disabled("unsafesort") && allowedSort(context)) {
   493  					if doNotSort(as) {
   494  						deduplicateStringList(as.RHS)
   495  					} else {
   496  						findAndModifyStrings(&as.RHS, sortStringList)
   497  					}
   498  				}
   499  			}
   500  		case *AssignExpr:
   501  			if disabled("unsafesort") {
   502  				return
   503  			}
   504  			// "keep sorted" comment on x = list forces sorting of list.
   505  			if keepSorted(v) {
   506  				findAndModifyStrings(&v.RHS, sortStringList)
   507  			}
   508  		case *KeyValueExpr:
   509  			if disabled("unsafesort") {
   510  				return
   511  			}
   512  			// "keep sorted" before key: list also forces sorting of list.
   513  			if keepSorted(v) {
   514  				findAndModifyStrings(&v.Value, sortStringList)
   515  			}
   516  		case *ListExpr:
   517  			if disabled("unsafesort") {
   518  				return
   519  			}
   520  			// "keep sorted" comment above first list element also forces sorting of list.
   521  			if len(v.List) > 0 && (keepSorted(v) || keepSorted(v.List[0])) {
   522  				findAndModifyStrings(&e, sortStringList)
   523  			}
   524  		}
   525  	})
   526  }
   527  
   528  // deduplicateStringList removes duplicates from a list with string expressions
   529  // without reordering its elements.
   530  // Any suffix-comments are lost, any before- and after-comments are preserved.
   531  func deduplicateStringList(x Expr) {
   532  	list, ok := x.(*ListExpr)
   533  	if !ok {
   534  		return
   535  	}
   536  
   537  	list.List = deduplicateStringExprs(list.List)
   538  }
   539  
   540  // deduplicateStringExprs removes duplicate string expressions from a slice
   541  // without reordering its elements.
   542  // Any suffix-comments are lost, any before- and after-comments are preserved.
   543  func deduplicateStringExprs(list []Expr) []Expr {
   544  	var comments []Comment
   545  	alreadySeen := make(map[string]bool)
   546  	var deduplicated []Expr
   547  	for _, value := range list {
   548  		str, ok := value.(*StringExpr)
   549  		if !ok {
   550  			deduplicated = append(deduplicated, value)
   551  			continue
   552  		}
   553  		strVal := str.Value
   554  		if _, ok := alreadySeen[strVal]; ok {
   555  			// This is a duplicate of a string above.
   556  			// Collect comments so that they're not lost.
   557  			comments = append(comments, str.Comment().Before...)
   558  			comments = append(comments, str.Comment().After...)
   559  			continue
   560  		}
   561  		alreadySeen[strVal] = true
   562  		if len(comments) > 0 {
   563  			comments = append(comments, value.Comment().Before...)
   564  			value.Comment().Before = comments
   565  			comments = nil
   566  		}
   567  		deduplicated = append(deduplicated, value)
   568  	}
   569  	return deduplicated
   570  }
   571  
   572  // SortStringList sorts x, a list of strings.
   573  // The list is broken by non-strings and by blank lines and comments into chunks.
   574  // Each chunk is sorted in place.
   575  func SortStringList(x Expr) {
   576  	list, ok := x.(*ListExpr)
   577  	if !ok || len(list.List) < 2 {
   578  		return
   579  	}
   580  
   581  	if doNotSort(list.List[0]) {
   582  		list.List = deduplicateStringExprs(list.List)
   583  		return
   584  	}
   585  
   586  	forceSort := keepSorted(list) || keepSorted(list.List[0])
   587  
   588  	// TODO(bazel-team): Decide how to recognize lists that cannot
   589  	// be sorted. Avoiding all lists with comments avoids sorting
   590  	// lists that say explicitly, in some form or another, why they
   591  	// cannot be sorted. For example, many cc_test rules require
   592  	// certain order in their deps attributes.
   593  	if !forceSort {
   594  		if line, _ := hasComments(list); line {
   595  			deduplicateStringList(list)
   596  			return
   597  		}
   598  	}
   599  
   600  	list.List = sortStringExprs(list.List)
   601  }
   602  
   603  // findAndModifyStrings finds and modifies string lists with a callback
   604  // function recursively within  the given expression. It doesn't touch all
   605  // string lists it can find, but only top-level lists, lists that are parts of
   606  // concatenated expressions and lists within select statements.
   607  // It calls the callback on the root node and on all relevant inner lists.
   608  // The callback function should gracefully return if called with not appropriate
   609  // arguments.
   610  func findAndModifyStrings(x *Expr, callback func(*Expr)) {
   611  	callback(x)
   612  	switch x := (*x).(type) {
   613  	case *BinaryExpr:
   614  		if x.Op != "+" {
   615  			return
   616  		}
   617  		findAndModifyStrings(&x.X, callback)
   618  		findAndModifyStrings(&x.Y, callback)
   619  	case *CallExpr:
   620  		if ident, ok := x.X.(*Ident); !ok || ident.Name != "select" {
   621  			return
   622  		}
   623  		if len(x.List) == 0 {
   624  			return
   625  		}
   626  		dict, ok := x.List[0].(*DictExpr)
   627  		if !ok {
   628  			return
   629  		}
   630  		for _, kv := range dict.List {
   631  			findAndModifyStrings(&kv.Value, callback)
   632  		}
   633  	}
   634  }
   635  
   636  func sortStringExprs(list []Expr) []Expr {
   637  	if len(list) < 2 {
   638  		return list
   639  	}
   640  
   641  	// Sort chunks of the list with no intervening blank lines or comments.
   642  	for i := 0; i < len(list); {
   643  		if _, ok := list[i].(*StringExpr); !ok {
   644  			i++
   645  			continue
   646  		}
   647  
   648  		j := i + 1
   649  		for ; j < len(list); j++ {
   650  			if str, ok := list[j].(*StringExpr); !ok || len(str.Before) > 0 {
   651  				break
   652  			}
   653  		}
   654  
   655  		var chunk []stringSortKey
   656  		for index, x := range list[i:j] {
   657  			chunk = append(chunk, makeSortKey(index, x.(*StringExpr)))
   658  		}
   659  		if !sort.IsSorted(byStringExpr(chunk)) || !isUniq(chunk) {
   660  			before := chunk[0].x.Comment().Before
   661  			chunk[0].x.Comment().Before = nil
   662  
   663  			sort.Sort(byStringExpr(chunk))
   664  			chunk = uniq(chunk)
   665  
   666  			chunk[0].x.Comment().Before = before
   667  			for offset, key := range chunk {
   668  				list[i+offset] = key.x
   669  			}
   670  			list = append(list[:(i+len(chunk))], list[j:]...)
   671  		}
   672  
   673  		i = j
   674  	}
   675  
   676  	return list
   677  }
   678  
   679  // uniq removes duplicates from a list, which must already be sorted.
   680  // It edits the list in place.
   681  func uniq(sortedList []stringSortKey) []stringSortKey {
   682  	out := sortedList[:0]
   683  	for _, sk := range sortedList {
   684  		if len(out) == 0 || sk.value != out[len(out)-1].value {
   685  			out = append(out, sk)
   686  		}
   687  	}
   688  	return out
   689  }
   690  
   691  // isUniq reports whether the sorted list only contains unique elements.
   692  func isUniq(list []stringSortKey) bool {
   693  	for i := range list {
   694  		if i+1 < len(list) && list[i].value == list[i+1].value {
   695  			return false
   696  		}
   697  	}
   698  	return true
   699  }
   700  
   701  // If stk describes a call argument like rule(arg=...), callArgName
   702  // returns the name of that argument, formatted as "rule.arg".
   703  func callArgName(stk []Expr) string {
   704  	n := len(stk)
   705  	if n < 2 {
   706  		return ""
   707  	}
   708  	arg := argName(stk[n-1])
   709  	if arg == "" {
   710  		return ""
   711  	}
   712  	call, ok := stk[n-2].(*CallExpr)
   713  	if !ok {
   714  		return ""
   715  	}
   716  	rule, ok := call.X.(*Ident)
   717  	if !ok {
   718  		return ""
   719  	}
   720  	return rule.Name + "." + arg
   721  }
   722  
   723  // A stringSortKey records information about a single string literal to be
   724  // sorted. The strings are first grouped into four phases: most strings,
   725  // strings beginning with ":", strings beginning with "//", and strings
   726  // beginning with "@". The next significant part of the comparison is the list
   727  // of elements in the value, where elements are split at `.' and `:'. Finally
   728  // we compare by value and break ties by original index.
   729  type stringSortKey struct {
   730  	phase    int
   731  	split    []string
   732  	value    string
   733  	original int
   734  	x        Expr
   735  }
   736  
   737  func makeSortKey(index int, x *StringExpr) stringSortKey {
   738  	key := stringSortKey{
   739  		value:    x.Value,
   740  		original: index,
   741  		x:        x,
   742  	}
   743  
   744  	switch {
   745  	case strings.HasPrefix(x.Value, ":"):
   746  		key.phase = 1
   747  	case strings.HasPrefix(x.Value, "//") || (tables.StripLabelLeadingSlashes && !strings.HasPrefix(x.Value, "@")):
   748  		key.phase = 2
   749  	case strings.HasPrefix(x.Value, "@"):
   750  		key.phase = 3
   751  	}
   752  
   753  	key.split = strings.Split(strings.Replace(x.Value, ":", ".", -1), ".")
   754  	return key
   755  }
   756  
   757  // byStringExpr implements sort.Interface for a list of stringSortKey.
   758  type byStringExpr []stringSortKey
   759  
   760  func (x byStringExpr) Len() int      { return len(x) }
   761  func (x byStringExpr) Swap(i, j int) { x[i], x[j] = x[j], x[i] }
   762  
   763  func (x byStringExpr) Less(i, j int) bool {
   764  	xi := x[i]
   765  	xj := x[j]
   766  
   767  	if xi.phase != xj.phase {
   768  		return xi.phase < xj.phase
   769  	}
   770  	for k := 0; k < len(xi.split) && k < len(xj.split); k++ {
   771  		if xi.split[k] != xj.split[k] {
   772  			return xi.split[k] < xj.split[k]
   773  		}
   774  	}
   775  	if len(xi.split) != len(xj.split) {
   776  		return len(xi.split) < len(xj.split)
   777  	}
   778  	if xi.value != xj.value {
   779  		return xi.value < xj.value
   780  	}
   781  	return xi.original < xj.original
   782  }
   783  
   784  // fixMultilinePlus turns
   785  //
   786  //	... +
   787  //	[ ... ]
   788  //
   789  //	... +
   790  //	call(...)
   791  //
   792  // into
   793  //
   794  //	... + [
   795  //		...
   796  //	]
   797  //
   798  //	... + call(
   799  //		...
   800  //	)
   801  //
   802  // which typically works better with our aggressively compact formatting.
   803  func fixMultilinePlus(f *File, _ *Rewriter) {
   804  
   805  	// List manipulation helpers.
   806  	// As a special case, we treat f([...]) as a list, mainly
   807  	// for glob.
   808  
   809  	// isList reports whether x is a list.
   810  	var isList func(x Expr) bool
   811  	isList = func(x Expr) bool {
   812  		switch x := x.(type) {
   813  		case *ListExpr:
   814  			return true
   815  		case *CallExpr:
   816  			if len(x.List) == 1 {
   817  				return isList(x.List[0])
   818  			}
   819  		}
   820  		return false
   821  	}
   822  
   823  	// isMultiLine reports whether x is a multiline list.
   824  	var isMultiLine func(Expr) bool
   825  	isMultiLine = func(x Expr) bool {
   826  		switch x := x.(type) {
   827  		case *ListExpr:
   828  			return x.ForceMultiLine || len(x.List) > 1
   829  		case *CallExpr:
   830  			if x.ForceMultiLine || len(x.List) > 1 && !x.ForceCompact {
   831  				return true
   832  			}
   833  			if len(x.List) == 1 {
   834  				return isMultiLine(x.List[0])
   835  			}
   836  		}
   837  		return false
   838  	}
   839  
   840  	// forceMultiLine tries to force the list x to use a multiline form.
   841  	// It reports whether it was successful.
   842  	var forceMultiLine func(Expr) bool
   843  	forceMultiLine = func(x Expr) bool {
   844  		switch x := x.(type) {
   845  		case *ListExpr:
   846  			// Already multi line?
   847  			if x.ForceMultiLine {
   848  				return true
   849  			}
   850  			// If this is a list containing a list, force the
   851  			// inner list to be multiline instead.
   852  			if len(x.List) == 1 && forceMultiLine(x.List[0]) {
   853  				return true
   854  			}
   855  			x.ForceMultiLine = true
   856  			return true
   857  
   858  		case *CallExpr:
   859  			if len(x.List) == 1 {
   860  				return forceMultiLine(x.List[0])
   861  			}
   862  		}
   863  		return false
   864  	}
   865  
   866  	skip := map[Expr]bool{}
   867  	Walk(f, func(v Expr, stk []Expr) {
   868  		if skip[v] {
   869  			return
   870  		}
   871  		bin, ok := v.(*BinaryExpr)
   872  		if !ok || bin.Op != "+" {
   873  			return
   874  		}
   875  
   876  		// Found a +.
   877  		// w + x + y + z parses as ((w + x) + y) + z,
   878  		// so chase down the left side to make a list of
   879  		// all the things being added together, separated
   880  		// by the BinaryExprs that join them.
   881  		// Mark them as "skip" so that when Walk recurses
   882  		// into the subexpressions, we won't reprocess them.
   883  		var all []Expr
   884  		for {
   885  			all = append(all, bin.Y, bin)
   886  			bin1, ok := bin.X.(*BinaryExpr)
   887  			if !ok || bin1.Op != "+" {
   888  				break
   889  			}
   890  			bin = bin1
   891  			skip[bin] = true
   892  		}
   893  		all = append(all, bin.X)
   894  
   895  		// Because the outermost expression was the
   896  		// rightmost one, the list is backward. Reverse it.
   897  		for i, j := 0, len(all)-1; i < j; i, j = i+1, j-1 {
   898  			all[i], all[j] = all[j], all[i]
   899  		}
   900  
   901  		// The 'all' slice is alternating addends and BinaryExpr +'s:
   902  		//	w, +, x, +, y, +, z
   903  		// If there are no lists involved, don't rewrite anything.
   904  		haveList := false
   905  		for i := 0; i < len(all); i += 2 {
   906  			if isList(all[i]) {
   907  				haveList = true
   908  				break
   909  			}
   910  		}
   911  		if !haveList {
   912  			return
   913  		}
   914  
   915  		// Okay, there are lists.
   916  		// Consider each + next to a line break.
   917  		for i := 1; i < len(all); i += 2 {
   918  			bin := all[i].(*BinaryExpr)
   919  			if !bin.LineBreak {
   920  				continue
   921  			}
   922  
   923  			// We're going to break the line after the +.
   924  			// If it is followed by a list, force that to be
   925  			// multiline instead.
   926  			if forceMultiLine(all[i+1]) {
   927  				bin.LineBreak = false
   928  				continue
   929  			}
   930  
   931  			// If the previous list was multiline already,
   932  			// don't bother with the line break after
   933  			// the +.
   934  			if isMultiLine(all[i-1]) {
   935  				bin.LineBreak = false
   936  				continue
   937  			}
   938  		}
   939  	})
   940  }
   941  
   942  // isFunctionCall checks whether expr is a call of a function with a given name
   943  func isFunctionCall(expr Expr, name string) bool {
   944  	call, ok := expr.(*CallExpr)
   945  	if !ok {
   946  		return false
   947  	}
   948  	if ident, ok := call.X.(*Ident); ok && ident.Name == name {
   949  		return true
   950  	}
   951  	return false
   952  }
   953  
   954  // moveLoadOnTop moves all load statements to the top of the file
   955  func moveLoadOnTop(f *File, _ *Rewriter) {
   956  	if f.Type == TypeWorkspace {
   957  		// Moving load statements in Workspace files can break the semantics
   958  		return
   959  	}
   960  	if isCommentAnywhere(f, "disable=load-on-top") {
   961  		// For backward compatibility. This rewrite used to be a suppressible warning,
   962  		// in some cases it's hard to maintain the position of load statements (e.g.
   963  		// when the file is automatically generated or has automatic transformations
   964  		// applied to it). The rewrite checks for the comment anywhere in the file
   965  		// because it's hard to determine which statement is out of order.
   966  		return
   967  	}
   968  
   969  	// Find the misplaced load statements
   970  	misplacedLoads := make(map[int]*LoadStmt)
   971  	firstStmtIndex := -1 // index of the first seen non-load statement
   972  	for i := 0; i < len(f.Stmt); i++ {
   973  		stmt := f.Stmt[i]
   974  		_, isString := stmt.(*StringExpr) // typically a docstring
   975  		_, isComment := stmt.(*CommentBlock)
   976  		isWorkspace := isFunctionCall(stmt, "workspace")
   977  		if isString || isComment || isWorkspace || stmt == nil {
   978  			continue
   979  		}
   980  		load, ok := stmt.(*LoadStmt)
   981  		if !ok {
   982  			if firstStmtIndex == -1 {
   983  				firstStmtIndex = i
   984  			}
   985  			continue
   986  		}
   987  		if firstStmtIndex == -1 {
   988  			continue
   989  		}
   990  		misplacedLoads[i] = load
   991  	}
   992  
   993  	// Calculate a fix
   994  	if firstStmtIndex == -1 {
   995  		firstStmtIndex = 0
   996  	}
   997  	offset := len(misplacedLoads)
   998  	if offset == 0 {
   999  		return
  1000  	}
  1001  	stmtCopy := append([]Expr{}, f.Stmt...)
  1002  	for i := range f.Stmt {
  1003  		if i < firstStmtIndex {
  1004  			// Docstring or a comment in the beginning, skip
  1005  			continue
  1006  		} else if _, ok := misplacedLoads[i]; ok {
  1007  			// A misplaced load statement, should be moved up to the `firstStmtIndex` position
  1008  			f.Stmt[firstStmtIndex] = stmtCopy[i]
  1009  			firstStmtIndex++
  1010  			offset--
  1011  			if offset == 0 {
  1012  				// No more statements should be moved
  1013  				break
  1014  			}
  1015  		} else {
  1016  			// An actual statement (not a docstring or a comment in the beginning), should be moved
  1017  			// `offset` positions down.
  1018  			f.Stmt[i+offset] = stmtCopy[i]
  1019  		}
  1020  	}
  1021  }
  1022  
  1023  // compressSameOriginLoads merges loads from the same source into one load statement
  1024  func compressSameOriginLoads(f *File, _ *Rewriter) {
  1025  	// Load statements grouped by their source files
  1026  	loads := make(map[string]*LoadStmt)
  1027  
  1028  	for stmtIndex := 0; stmtIndex < len(f.Stmt); stmtIndex++ {
  1029  		load, ok := f.Stmt[stmtIndex].(*LoadStmt)
  1030  		if !ok {
  1031  			continue
  1032  		}
  1033  
  1034  		previousLoad, ok := loads[load.Module.Value]
  1035  		if !ok {
  1036  			loads[load.Module.Value] = load
  1037  			continue
  1038  		}
  1039  		if hasComment(previousLoad, "disable=same-origin-load") ||
  1040  			hasComment(load, "disable=same-origin-load") {
  1041  			continue
  1042  		}
  1043  
  1044  		// Move loaded symbols to the existing load statement
  1045  		previousLoad.From = append(previousLoad.From, load.From...)
  1046  		previousLoad.To = append(previousLoad.To, load.To...)
  1047  		// Remove the current statement
  1048  		f.Stmt[stmtIndex] = nil
  1049  	}
  1050  }
  1051  
  1052  // comparePaths compares two strings as if they were paths (the path delimiter,
  1053  // '/', should be treated as smallest symbol).
  1054  func comparePaths(path1, path2 string) bool {
  1055  	if path1 == path2 {
  1056  		return false
  1057  	}
  1058  
  1059  	chunks1 := strings.Split(path1, "/")
  1060  	chunks2 := strings.Split(path2, "/")
  1061  
  1062  	for i, chunk1 := range chunks1 {
  1063  		if i >= len(chunks2) {
  1064  			return false
  1065  		}
  1066  		chunk2 := chunks2[i]
  1067  		// Compare case-insensitively
  1068  		chunk1Lower := strings.ToLower(chunk1)
  1069  		chunk2Lower := strings.ToLower(chunk2)
  1070  		if chunk1Lower != chunk2Lower {
  1071  			return chunk1Lower < chunk2Lower
  1072  		}
  1073  	}
  1074  
  1075  	// No case-insensitive difference detected. Likely the difference is just in
  1076  	// the case of some symbols, compare case-sensitively for the determinism.
  1077  	return path1 <= path2
  1078  }
  1079  
  1080  // compareLoadLabels compares two module names
  1081  // If one label has explicit repository path (starts with @), it goes first
  1082  // If the packages are different, labels are sorted by package name (empty package goes first)
  1083  // If the packages are the same, labels are sorted by their name
  1084  func compareLoadLabels(load1Label, load2Label string) bool {
  1085  	// handle absolute labels with explicit repositories separately to
  1086  	// make sure they precede absolute and relative labels without repos
  1087  	isExplicitRepo1 := strings.HasPrefix(load1Label, "@")
  1088  	isExplicitRepo2 := strings.HasPrefix(load2Label, "@")
  1089  
  1090  	if isExplicitRepo1 != isExplicitRepo2 {
  1091  		// Exactly one label has an explicit repository name, it should be the first one.
  1092  		return isExplicitRepo1
  1093  	}
  1094  
  1095  	// Either both labels have explicit repository names or both don't, compare their packages
  1096  	// and break ties using file names if necessary
  1097  	module1Parts := strings.SplitN(load1Label, ":", 2)
  1098  	package1, filename1 := "", module1Parts[0]
  1099  	if len(module1Parts) == 2 {
  1100  		package1, filename1 = module1Parts[0], module1Parts[1]
  1101  	}
  1102  	module2Parts := strings.SplitN(load2Label, ":", 2)
  1103  	package2, filename2 := "", module2Parts[0]
  1104  	if len(module2Parts) == 2 {
  1105  		package2, filename2 = module2Parts[0], module2Parts[1]
  1106  	}
  1107  
  1108  	// in case both packages are the same, use file names to break ties
  1109  	if package1 == package2 {
  1110  		return comparePaths(filename1, filename2)
  1111  	}
  1112  
  1113  	// in case one of the packages is empty, the empty one goes first
  1114  	if len(package1) == 0 || len(package2) == 0 {
  1115  		return len(package1) > 0
  1116  	}
  1117  
  1118  	// both packages are non-empty and not equal, so compare them
  1119  	return comparePaths(package1, package2)
  1120  }
  1121  
  1122  // sortLoadStatements reorders sorts loads lexicographically by the source file,
  1123  // but absolute loads have priority over local loads.
  1124  func sortLoadStatements(f *File, _ *Rewriter) {
  1125  	if isCommentAnywhere(f, "disable=out-of-order-load") {
  1126  		// For backward compatibility. This rewrite used to be a suppressible warning,
  1127  		// in some cases it's hard to maintain the position of load statements (e.g.
  1128  		// when the file is automatically generated or has automatic transformations
  1129  		// applied to it). The rewrite checks for the comment anywhere in the file
  1130  		// because it's hard to determine which statement is out of order.
  1131  		return
  1132  	}
  1133  
  1134  	// Consequent chunks of load statements (i.e. without statements of other types between them)
  1135  	var loadsChunks [][]*LoadStmt
  1136  	newChunk := true // Create a new chunk for the next seen load statement
  1137  
  1138  	for _, stmt := range f.Stmt {
  1139  		if stmt == nil {
  1140  			// nil statements are no-op and shouldn't break consecutive chains of
  1141  			// load statements
  1142  			continue
  1143  		}
  1144  		load, ok := stmt.(*LoadStmt)
  1145  		if !ok {
  1146  			newChunk = true
  1147  			continue
  1148  		}
  1149  		if newChunk {
  1150  			// There's a non-load statement between this load and the previous load
  1151  			loadsChunks = append(loadsChunks, []*LoadStmt{})
  1152  			newChunk = false
  1153  		}
  1154  		loadsChunks[len(loadsChunks)-1] = append(loadsChunks[len(loadsChunks)-1], load)
  1155  	}
  1156  
  1157  	// Sort and flatten the chunks
  1158  	var sortedLoads []*LoadStmt
  1159  	for _, chunk := range loadsChunks {
  1160  		sortedChunk := append([]*LoadStmt{}, chunk...)
  1161  
  1162  		sort.SliceStable(sortedChunk, func(i, j int) bool {
  1163  			load1Label := (sortedChunk)[i].Module.Value
  1164  			load2Label := (sortedChunk)[j].Module.Value
  1165  			return compareLoadLabels(load1Label, load2Label)
  1166  		})
  1167  		sortedLoads = append(sortedLoads, sortedChunk...)
  1168  	}
  1169  
  1170  	// Calculate the replacements
  1171  	loadIndex := 0
  1172  	for stmtIndex := range f.Stmt {
  1173  		if _, ok := f.Stmt[stmtIndex].(*LoadStmt); !ok {
  1174  			continue
  1175  		}
  1176  		f.Stmt[stmtIndex] = sortedLoads[loadIndex]
  1177  		loadIndex++
  1178  	}
  1179  }
  1180  
  1181  // sortAllLoadArgs sorts all load arguments in the file
  1182  func sortAllLoadArgs(f *File, _ *Rewriter) {
  1183  	Walk(f, func(v Expr, stk []Expr) {
  1184  		if load, ok := v.(*LoadStmt); ok {
  1185  			SortLoadArgs(load)
  1186  		}
  1187  	})
  1188  }
  1189  
  1190  func sortUseRepoPositionals(f *File, _ *Rewriter) {
  1191  	Walk(f, func(v Expr, stk []Expr) {
  1192  		if call, ok := v.(*CallExpr); ok {
  1193  			// The first argument of a valid use_repo call is always a module extension proxy, so we
  1194  			// do not need to sort calls with less than three arguments.
  1195  			if ident, ok := call.X.(*Ident); !ok || ident.Name != "use_repo" || len(call.List) < 3 {
  1196  				return
  1197  			}
  1198  			// Respect the "do not sort" comment on both the first argument and the first repository
  1199  			// name.
  1200  			if doNotSort(call) || doNotSort(call.List[0]) || doNotSort(call.List[1]) {
  1201  				call.List = deduplicateStringExprs(call.List)
  1202  			} else {
  1203  				// Keyword arguments do not have to be sorted here as this has already been done by
  1204  				// the generic callsort rewriter pass.
  1205  				call.List = sortStringExprs(call.List)
  1206  			}
  1207  		}
  1208  	})
  1209  }
  1210  
  1211  // hasComments reports whether any comments are associated with
  1212  // the list or its elements.
  1213  func hasComments(list *ListExpr) (line, suffix bool) {
  1214  	com := list.Comment()
  1215  	if len(com.Before) > 0 || len(com.After) > 0 || len(list.End.Before) > 0 {
  1216  		line = true
  1217  	}
  1218  	if len(com.Suffix) > 0 {
  1219  		suffix = true
  1220  	}
  1221  	for _, elem := range list.List {
  1222  		com := elem.Comment()
  1223  		if len(com.Before) > 0 {
  1224  			line = true
  1225  		}
  1226  		if len(com.Suffix) > 0 {
  1227  			suffix = true
  1228  		}
  1229  	}
  1230  	return
  1231  }
  1232  
  1233  // A wrapper for a LoadStmt's From and To slices for consistent sorting of their contents.
  1234  // It's assumed that the following slices have the same length. The contents are sorted by
  1235  // the `To` attribute, but all items with equal "From" and "To" parts are placed before the items
  1236  // with different parts.
  1237  type loadArgs struct {
  1238  	From     []*Ident
  1239  	To       []*Ident
  1240  	modified bool
  1241  }
  1242  
  1243  func (args *loadArgs) Len() int {
  1244  	return len(args.From)
  1245  }
  1246  
  1247  func (args *loadArgs) Swap(i, j int) {
  1248  	args.From[i], args.From[j] = args.From[j], args.From[i]
  1249  	args.To[i], args.To[j] = args.To[j], args.To[i]
  1250  	args.modified = true
  1251  }
  1252  
  1253  func (args *loadArgs) Less(i, j int) bool {
  1254  	// Arguments with equal "from" and "to" parts are prioritized
  1255  	equalI := args.From[i].Name == args.To[i].Name
  1256  	equalJ := args.From[j].Name == args.To[j].Name
  1257  	if equalI != equalJ {
  1258  		// If equalI and !equalJ, return true, otherwise false.
  1259  		// Equivalently, return equalI.
  1260  		return equalI
  1261  	}
  1262  	return args.To[i].Name < args.To[j].Name
  1263  }
  1264  
  1265  // deduplicate assumes that the args are sorted and prioritize arguments that
  1266  // are defined later (e.g. `a = "x", a = "y"` is shortened to `a = "y"`).
  1267  func (args *loadArgs) deduplicate() {
  1268  	var from, to []*Ident
  1269  	for i := range args.To {
  1270  		if len(to) > 0 && to[len(to)-1].Name == args.To[i].Name {
  1271  			// Overwrite
  1272  			from[len(from)-1] = args.From[i]
  1273  			to[len(to)-1] = args.To[i]
  1274  			args.modified = true
  1275  		} else {
  1276  			// Just append
  1277  			from = append(from, args.From[i])
  1278  			to = append(to, args.To[i])
  1279  		}
  1280  	}
  1281  	args.From = from
  1282  	args.To = to
  1283  }
  1284  
  1285  // SortLoadArgs sorts a load statement arguments (lexicographically, but positional first)
  1286  func SortLoadArgs(load *LoadStmt) bool {
  1287  	args := loadArgs{From: load.From, To: load.To}
  1288  	sort.Sort(&args)
  1289  	args.deduplicate()
  1290  	load.From = args.From
  1291  	load.To = args.To
  1292  	return args.modified
  1293  }
  1294  
  1295  // formatDocstrings fixes the indentation and trailing whitespace of docstrings
  1296  func formatDocstrings(f *File, _ *Rewriter) {
  1297  	Walk(f, func(v Expr, stk []Expr) {
  1298  		def, ok := v.(*DefStmt)
  1299  		if !ok || len(def.Body) == 0 {
  1300  			return
  1301  		}
  1302  		docstring, ok := def.Body[0].(*StringExpr)
  1303  		if !ok || !docstring.TripleQuote {
  1304  			return
  1305  		}
  1306  
  1307  		oldIndentation := docstring.Start.LineRune - 1 // LineRune starts with 1
  1308  		newIndentation := nestedIndentation * len(stk)
  1309  
  1310  		// Operate on Token, not Value, because their line breaks can be different if a line ends with
  1311  		// a backslash.
  1312  		updatedToken := formatString(docstring.Token, oldIndentation, newIndentation)
  1313  		if updatedToken != docstring.Token {
  1314  			docstring.Token = updatedToken
  1315  			// Update the value to keep it consistent with Token
  1316  			docstring.Value, _, _ = Unquote(updatedToken)
  1317  		}
  1318  	})
  1319  }
  1320  
  1321  // formatString modifies a string value of a docstring to match the new indentation level and
  1322  // to remove trailing whitespace from its lines.
  1323  func formatString(value string, oldIndentation, newIndentation int) string {
  1324  	difference := newIndentation - oldIndentation
  1325  	lines := strings.Split(value, "\n")
  1326  	for i, line := range lines {
  1327  		if i == 0 {
  1328  			// The first line shouldn't be touched because it starts right after ''' or """
  1329  			continue
  1330  		}
  1331  		if difference > 0 {
  1332  			line = strings.Repeat(" ", difference) + line
  1333  		} else {
  1334  			for i, rune := range line {
  1335  				if i == -difference || rune != ' ' {
  1336  					line = line[i:]
  1337  					break
  1338  				}
  1339  			}
  1340  		}
  1341  		if i != len(lines)-1 {
  1342  			// Remove trailing space from the line unless it's the last line that's responsible
  1343  			// for the indentation of the closing `"""`
  1344  			line = strings.TrimRight(line, " ")
  1345  		}
  1346  		lines[i] = line
  1347  	}
  1348  	return strings.Join(lines, "\n")
  1349  }
  1350  
  1351  // argumentType returns an integer by which funcall arguments can be sorted:
  1352  // 1 for positional, 2 for named, 3 for *args, 4 for **kwargs
  1353  func argumentType(expr Expr) int {
  1354  	switch expr := expr.(type) {
  1355  	case *UnaryExpr:
  1356  		switch expr.Op {
  1357  		case "**":
  1358  			return 4
  1359  		case "*":
  1360  			return 3
  1361  		}
  1362  	case *AssignExpr:
  1363  		return 2
  1364  	}
  1365  	return 1
  1366  }
  1367  
  1368  // reorderArguments fixes the order of arguments of a function call
  1369  // (positional, named, *args, **kwargs)
  1370  func reorderArguments(f *File, _ *Rewriter) {
  1371  	Walk(f, func(expr Expr, stack []Expr) {
  1372  		call, ok := expr.(*CallExpr)
  1373  		if !ok {
  1374  			return
  1375  		}
  1376  		compare := func(i, j int) bool {
  1377  			// Keep nil nodes at their place. They are no-op for the formatter but can
  1378  			// be useful for the linter which expects them to not move.
  1379  			if call.List[i] == nil || call.List[j] == nil {
  1380  				return false
  1381  			}
  1382  			return argumentType(call.List[i]) < argumentType(call.List[j])
  1383  		}
  1384  		if !sort.SliceIsSorted(call.List, compare) {
  1385  			sort.SliceStable(call.List, compare)
  1386  		}
  1387  	})
  1388  }
  1389  
  1390  // editOctals inserts 'o' into octal numbers to make it more obvious they are octal
  1391  // 0123 -> 0o123
  1392  func editOctals(f *File, _ *Rewriter) {
  1393  	Walk(f, func(expr Expr, stack []Expr) {
  1394  		l, ok := expr.(*LiteralExpr)
  1395  		if !ok {
  1396  			return
  1397  		}
  1398  		if len(l.Token) > 1 && l.Token[0] == '0' && l.Token[1] >= '0' && l.Token[1] <= '9' {
  1399  			l.Token = "0o" + l.Token[1:]
  1400  		}
  1401  	})
  1402  }
  1403  
  1404  // removeParens removes trivial parens
  1405  func removeParens(f *File, _ *Rewriter) {
  1406  	var simplify func(expr Expr, stack []Expr) Expr
  1407  	simplify = func(expr Expr, stack []Expr) Expr {
  1408  		// Look for parenthesized expressions, ignoring those with
  1409  		// comments and those that are intentionally multiline.
  1410  		pa, ok := expr.(*ParenExpr)
  1411  		if !ok || pa.ForceMultiLine {
  1412  			return expr
  1413  		}
  1414  		if len(pa.Comment().Before) > 0 || len(pa.Comment().After) > 0 || len(pa.Comment().Suffix) > 0 {
  1415  			return expr
  1416  		}
  1417  
  1418  		switch x := pa.X.(type) {
  1419  		case *Comprehension, *DictExpr, *Ident, *ListExpr, *LiteralExpr, *ParenExpr, *SetExpr, *StringExpr:
  1420  			// These expressions don't need parens, remove them (recursively).
  1421  			return Edit(x, simplify)
  1422  		case *CallExpr:
  1423  			// Parens might be needed if the callable is multiline.
  1424  			start, end := x.X.Span()
  1425  			if start.Line == end.Line {
  1426  				return Edit(x, simplify)
  1427  			}
  1428  		}
  1429  		return expr
  1430  	}
  1431  
  1432  	Edit(f, simplify)
  1433  }
  1434  

View as plain text