github.com/cheshirekow/buildtools@v0.0.0-20200224190056-5d637702fe81/edit/edit.go (about)

     1  /*
     2  Copyright 2016 Google Inc. All Rights Reserved.
     3  Licensed under the Apache License, Version 2.0 (the "License");
     4  you may not use this file except in compliance with the License.
     5  You may obtain a copy of the License at
     6      http://www.apache.org/licenses/LICENSE-2.0
     7  Unless required by applicable law or agreed to in writing, software
     8  distributed under the License is distributed on an "AS IS" BASIS,
     9   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    10   See the License for the specific language governing permissions and
    11   limitations under the License.
    12  */
    13  
    14  // Package edit provides high-level auxiliary functions for AST manipulation
    15  // on BUILD files.
    16  package edit
    17  
    18  import (
    19  	"fmt"
    20  	"os"
    21  	"path"
    22  	"path/filepath"
    23  	"regexp"
    24  	"sort"
    25  	"strconv"
    26  	"strings"
    27  
    28  	"github.com/cheshirekow/buildtools/build"
    29  	"github.com/cheshirekow/buildtools/tables"
    30  	"github.com/cheshirekow/buildtools/wspace"
    31  )
    32  
    33  var (
    34  	// ShortenLabelsFlag if true converts added labels to short form , e.g. //foo:bar => :bar
    35  	ShortenLabelsFlag = true
    36  	// DeleteWithComments if true a list attribute will be be deleted in ListDelete, even if there is a comment attached to it
    37  	DeleteWithComments = true
    38  )
    39  
    40  // ParseLabel parses a Blaze label (eg. //devtools/buildozer:rule), and returns
    41  // the repo name ("" for the main repo), package (with leading slashes trimmed)
    42  // and rule name (e.g. ["", "devtools/buildozer", "rule"]).
    43  func ParseLabel(target string) (string, string, string) {
    44  	repo := ""
    45  	if strings.HasPrefix(target, "@") {
    46  		target = strings.TrimLeft(target, "@")
    47  		parts := strings.SplitN(target, "/", 2)
    48  		if len(parts) == 1 {
    49  			// "@foo" -> "foo", "", "foo" (ie @foo//:foo)
    50  			return target, "", target
    51  		}
    52  		repo = parts[0]
    53  		target = "/" + parts[1]
    54  	}
    55  	// TODO(bazel-team): check if the next line can now be deleted
    56  	target = strings.TrimRight(target, ":") // labels can end with ':'
    57  	parts := strings.SplitN(target, ":", 2)
    58  	parts[0] = strings.TrimPrefix(parts[0], "//")
    59  	if len(parts) == 1 {
    60  		if strings.HasPrefix(target, "//") || tables.StripLabelLeadingSlashes {
    61  			// "//absolute/pkg" -> "absolute/pkg", "pkg"
    62  			return repo, parts[0], path.Base(parts[0])
    63  		}
    64  		// "relative/label" -> "", "relative/label"
    65  		return repo, "", parts[0]
    66  	}
    67  	return repo, parts[0], parts[1]
    68  }
    69  
    70  // ShortenLabel rewrites labels to use the canonical form (the form
    71  // recommended by build-style).  This behavior can be disabled using the
    72  // --noshorten_labels flag for projects that consistently use long-form labels.
    73  // "//foo/bar:bar" => "//foo/bar", or ":bar" when possible.
    74  func ShortenLabel(label string, pkg string) string {
    75  	if !ShortenLabelsFlag {
    76  		return label
    77  	}
    78  	if !strings.Contains(label, "//") {
    79  		// It doesn't look like a long label, so we preserve it.
    80  		return label
    81  	}
    82  	repo, labelPkg, rule := ParseLabel(label)
    83  	if repo == "" && labelPkg == pkg { // local label
    84  		return ":" + rule
    85  	}
    86  	slash := strings.LastIndex(labelPkg, "/")
    87  	if (slash >= 0 && labelPkg[slash+1:] == rule) || labelPkg == rule {
    88  		if repo == "" {
    89  			return "//" + labelPkg
    90  		}
    91  		return "@" + repo + "//" + labelPkg
    92  	}
    93  	if strings.HasPrefix(label, "@") && repo == rule && labelPkg == "" {
    94  		return "@" + repo
    95  	}
    96  	return label
    97  }
    98  
    99  // LabelsEqual returns true if label1 and label2 are equal. The function
   100  // takes care of the optional ":" prefix and differences between long-form
   101  // labels and local labels.
   102  func LabelsEqual(label1, label2, pkg string) bool {
   103  	str1 := strings.TrimPrefix(ShortenLabel(label1, pkg), ":")
   104  	str2 := strings.TrimPrefix(ShortenLabel(label2, pkg), ":")
   105  	return str1 == str2
   106  }
   107  
   108  // isFile returns true if the path refers to a regular file after following
   109  // symlinks.
   110  func isFile(path string) bool {
   111  	path, err := filepath.EvalSymlinks(path)
   112  	if err != nil {
   113  		return false
   114  	}
   115  	info, err := os.Stat(path)
   116  	if err != nil {
   117  		return false
   118  	}
   119  	return info.Mode().IsRegular()
   120  }
   121  
   122  // InterpretLabelForWorkspaceLocation returns the name of the BUILD file to
   123  // edit, the full package name, and the rule. It takes a workspace-rooted
   124  // directory to use.
   125  func InterpretLabelForWorkspaceLocation(root string, target string) (buildFile string, pkg string, rule string) {
   126  	repo, pkg, rule := ParseLabel(target)
   127  	rootDir, relativePath := wspace.FindWorkspaceRoot(root)
   128  	if repo != "" {
   129  		files, err := wspace.FindRepoBuildFiles(rootDir)
   130  		if err == nil {
   131  			if buildFile, ok := files[repo]; ok {
   132  				return buildFile, pkg, rule
   133  			}
   134  		}
   135  		// TODO(rodrigoq): report error for other repos
   136  	}
   137  
   138  	if strings.HasPrefix(target, "//") {
   139  		buildFile = filepath.Join(rootDir, pkg, "BUILD")
   140  		if !isFile(buildFile) && isFile(buildFile+".bazel") {
   141  			// try it with the .bazel extension
   142  			buildFile += ".bazel"
   143  		}
   144  		return
   145  	}
   146  	if isFile(pkg) {
   147  		// allow operation on other files like WORKSPACE
   148  		buildFile = pkg
   149  		pkg = filepath.Join(relativePath, filepath.Dir(pkg))
   150  		return
   151  	}
   152  	if pkg != "" {
   153  		buildFile = filepath.Join(pkg, "/BUILD")
   154  	} else {
   155  		buildFile = "BUILD"
   156  	}
   157  	if !isFile(buildFile) && isFile(buildFile+".bazel") {
   158  		// try it with the .bazel extension
   159  		buildFile += ".bazel"
   160  	}
   161  	pkg = filepath.Join(relativePath, pkg)
   162  	return
   163  }
   164  
   165  // InterpretLabel returns the name of the BUILD file to edit, the full
   166  // package name, and the rule. It uses the pwd for resolving workspace file paths.
   167  func InterpretLabel(target string) (buildFile string, pkg string, rule string) {
   168  	return InterpretLabelForWorkspaceLocation("", target)
   169  }
   170  
   171  // ExprToRule returns a Rule from an Expr.
   172  // The boolean is false iff the Expr is not a function call, or does not have
   173  // the expected kind.
   174  func ExprToRule(expr build.Expr, kind string) (*build.Rule, bool) {
   175  	call, ok := expr.(*build.CallExpr)
   176  	if !ok {
   177  		return nil, false
   178  	}
   179  	k, ok := call.X.(*build.Ident)
   180  	if !ok || k.Name != kind {
   181  		return nil, false
   182  	}
   183  	return &build.Rule{call, ""}, true
   184  }
   185  
   186  // ExistingPackageDeclaration returns the package declaration, or nil if there is none.
   187  func ExistingPackageDeclaration(f *build.File) *build.Rule {
   188  	for _, stmt := range f.Stmt {
   189  		if rule, ok := ExprToRule(stmt, "package"); ok {
   190  			return rule
   191  		}
   192  	}
   193  	return nil
   194  }
   195  
   196  // PackageDeclaration returns the package declaration. If it doesn't
   197  // exist, it is created at the top of the BUILD file, after optional
   198  // docstring, comments, and load statements.
   199  func PackageDeclaration(f *build.File) *build.Rule {
   200  	if pkg := ExistingPackageDeclaration(f); pkg != nil {
   201  		return pkg
   202  	}
   203  	all := []build.Expr{}
   204  	added := false
   205  	call := &build.CallExpr{X: &build.Ident{Name: "package"}}
   206  	for _, stmt := range f.Stmt {
   207  		switch stmt.(type) {
   208  		case *build.CommentBlock, *build.LoadStmt, *build.StringExpr:
   209  			// Skip docstring, comments, and load statements to
   210  			// find a place to insert the package declaration.
   211  		default:
   212  			if !added {
   213  				all = append(all, call)
   214  				added = true
   215  			}
   216  		}
   217  		all = append(all, stmt)
   218  	}
   219  	if !added { // In case the file is empty.
   220  		all = append(all, call)
   221  	}
   222  	f.Stmt = all
   223  	return &build.Rule{call, ""}
   224  }
   225  
   226  // RemoveEmptyPackage removes empty package declarations from the file, i.e.:
   227  //    package()
   228  // This might appear because of a buildozer transformation (e.g. when removing a package
   229  // attribute). Removing it is required for the file to be valid.
   230  func RemoveEmptyPackage(f *build.File) *build.File {
   231  	var all []build.Expr
   232  	for _, stmt := range f.Stmt {
   233  		if call, ok := stmt.(*build.CallExpr); ok {
   234  			functionName, ok := call.X.(*build.Ident)
   235  			if ok && functionName.Name == "package" && len(call.List) == 0 {
   236  				continue
   237  			}
   238  		}
   239  		all = append(all, stmt)
   240  	}
   241  	return &build.File{Path: f.Path, Comments: f.Comments, Stmt: all, Type: build.TypeBuild}
   242  }
   243  
   244  // InsertAfter inserts an expression after index i.
   245  func InsertAfter(i int, stmt []build.Expr, expr build.Expr) []build.Expr {
   246  	i = i + 1 // index after the element at i
   247  	result := make([]build.Expr, len(stmt)+1)
   248  	copy(result[0:i], stmt[0:i])
   249  	result[i] = expr
   250  	copy(result[i+1:], stmt[i:])
   251  	return result
   252  }
   253  
   254  // IndexOfLast finds the index of the last expression of a specific kind.
   255  func IndexOfLast(stmt []build.Expr, Kind string) int {
   256  	lastIndex := -1
   257  	for i, s := range stmt {
   258  		sAsCallExpr, ok := s.(*build.CallExpr)
   259  		if !ok {
   260  			continue
   261  		}
   262  		literal, ok := sAsCallExpr.X.(*build.Ident)
   263  		if ok && literal.Name == Kind {
   264  			lastIndex = i
   265  		}
   266  	}
   267  	return lastIndex
   268  }
   269  
   270  // InsertAfterLastOfSameKind inserts an expression after the last expression of the same kind.
   271  func InsertAfterLastOfSameKind(stmt []build.Expr, expr *build.CallExpr) []build.Expr {
   272  	index := IndexOfLast(stmt, expr.X.(*build.Ident).Name)
   273  	if index == -1 {
   274  		return InsertAtEnd(stmt, expr)
   275  	}
   276  	return InsertAfter(index, stmt, expr)
   277  }
   278  
   279  // InsertAtEnd inserts an expression at the end of a list, before trailing comments.
   280  func InsertAtEnd(stmt []build.Expr, expr build.Expr) []build.Expr {
   281  	var i int
   282  	for i = len(stmt) - 1; i >= 0; i-- {
   283  		_, ok := stmt[i].(*build.CommentBlock)
   284  		if !ok {
   285  			break
   286  		}
   287  	}
   288  	return InsertAfter(i, stmt, expr)
   289  }
   290  
   291  // FindRuleByName returns the rule in the file that has the given name.
   292  // If the name is "__pkg__", it returns the global package declaration.
   293  func FindRuleByName(f *build.File, name string) *build.Rule {
   294  	if name == "__pkg__" {
   295  		return PackageDeclaration(f)
   296  	}
   297  	_, rule := IndexOfRuleByName(f, name)
   298  	return rule
   299  }
   300  
   301  // IndexOfRuleByName returns the index (in f.Stmt) of the CallExpr which defines a rule named `name`, or -1 if it doesn't exist.
   302  func IndexOfRuleByName(f *build.File, name string) (int, *build.Rule) {
   303  	linenum := -1
   304  	if strings.HasPrefix(name, "%") {
   305  		// "%<LINENUM>" will match the rule which begins at LINENUM.
   306  		// This is for convenience, "%" is not a valid character in bazel targets.
   307  		if result, err := strconv.Atoi(name[1:]); err == nil {
   308  			linenum = result
   309  		}
   310  	}
   311  
   312  	for i, stmt := range f.Stmt {
   313  		call, ok := stmt.(*build.CallExpr)
   314  		if !ok {
   315  			continue
   316  		}
   317  		r := f.Rule(call)
   318  		start, _ := call.X.Span()
   319  		if r.Name() == name || start.Line == linenum {
   320  			return i, r
   321  		}
   322  	}
   323  	return -1, nil
   324  }
   325  
   326  // FindExportedFile returns the first exports_files call which contains the
   327  // file 'name', or nil if not found
   328  func FindExportedFile(f *build.File, name string) *build.Rule {
   329  	for _, r := range f.Rules("exports_files") {
   330  		if len(r.Call.List) == 0 {
   331  			continue
   332  		}
   333  		pkg := "" // Files are not affected by the package name
   334  		if ListFind(r.Call.List[0], name, pkg) != nil {
   335  			return r
   336  		}
   337  	}
   338  	return nil
   339  }
   340  
   341  // DeleteRule returns the AST without the specified rule
   342  func DeleteRule(f *build.File, rule *build.Rule) *build.File {
   343  	var all []build.Expr
   344  	for _, stmt := range f.Stmt {
   345  		if stmt == rule.Call {
   346  			continue
   347  		}
   348  		all = append(all, stmt)
   349  	}
   350  	return &build.File{Path: f.Path, Comments: f.Comments, Stmt: all, Type: build.TypeBuild}
   351  }
   352  
   353  // DeleteRuleByName returns the AST without the rules that have the
   354  // given name.
   355  func DeleteRuleByName(f *build.File, name string) *build.File {
   356  	var all []build.Expr
   357  	for _, stmt := range f.Stmt {
   358  		call, ok := stmt.(*build.CallExpr)
   359  		if !ok {
   360  			all = append(all, stmt)
   361  			continue
   362  		}
   363  		r := f.Rule(call)
   364  		if r.Name() != name {
   365  			all = append(all, stmt)
   366  		}
   367  	}
   368  	return &build.File{Path: f.Path, Comments: f.Comments, Stmt: all, Type: build.TypeBuild}
   369  }
   370  
   371  // DeleteRuleByKind removes the rules of the specified kind from the AST.
   372  // Returns an updated copy of f.
   373  func DeleteRuleByKind(f *build.File, kind string) *build.File {
   374  	var all []build.Expr
   375  	for _, stmt := range f.Stmt {
   376  		call, ok := stmt.(*build.CallExpr)
   377  		if !ok {
   378  			all = append(all, stmt)
   379  			continue
   380  		}
   381  		k, ok := call.X.(*build.Ident)
   382  		if !ok || k.Name != kind {
   383  			all = append(all, stmt)
   384  		}
   385  	}
   386  	return &build.File{Path: f.Path, Comments: f.Comments, Stmt: all, Type: build.TypeBuild}
   387  }
   388  
   389  // AllLists returns all the lists concatenated in an expression.
   390  // For example, in: glob(["*.go"]) + [":rule"]
   391  // the function will return [[":rule"]].
   392  func AllLists(e build.Expr) []*build.ListExpr {
   393  	switch e := e.(type) {
   394  	case *build.ListExpr:
   395  		return []*build.ListExpr{e}
   396  	case *build.BinaryExpr:
   397  		if e.Op == "+" {
   398  			return append(AllLists(e.X), AllLists(e.Y)...)
   399  		}
   400  	}
   401  	return nil
   402  }
   403  
   404  // AllSelects returns all the selects concatenated in an expression.
   405  func AllSelects(e build.Expr) []*build.CallExpr {
   406  	switch e := e.(type) {
   407  	case *build.BinaryExpr:
   408  		if e.Op == "+" {
   409  			return append(AllSelects(e.X), AllSelects(e.Y)...)
   410  		}
   411  	case *build.CallExpr:
   412  		if x, ok := e.X.(*build.Ident); ok && x.Name == "select" {
   413  			return []*build.CallExpr{e}
   414  		}
   415  	}
   416  	return nil
   417  }
   418  
   419  // FirstList works in the same way as AllLists, except that it
   420  // returns only one list, or nil.
   421  func FirstList(e build.Expr) *build.ListExpr {
   422  	switch e := e.(type) {
   423  	case *build.ListExpr:
   424  		return e
   425  	case *build.BinaryExpr:
   426  		if e.Op == "+" {
   427  			li := FirstList(e.X)
   428  			if li == nil {
   429  				return FirstList(e.Y)
   430  			}
   431  			return li
   432  		}
   433  	}
   434  	return nil
   435  }
   436  
   437  // AllStrings returns all the string literals concatenated in an expression.
   438  // For example, in: "foo" + x + "bar"
   439  // the function will return ["foo", "bar"].
   440  func AllStrings(e build.Expr) []*build.StringExpr {
   441  	switch e := e.(type) {
   442  	case *build.StringExpr:
   443  		return []*build.StringExpr{e}
   444  	case *build.BinaryExpr:
   445  		if e.Op == "+" {
   446  			return append(AllStrings(e.X), AllStrings(e.Y)...)
   447  		}
   448  	}
   449  	return nil
   450  }
   451  
   452  // ListFind looks for a string in the list expression (which may be a
   453  // concatenation of lists). It returns the element if it is found. nil
   454  // otherwise.
   455  func ListFind(e build.Expr, item string, pkg string) *build.StringExpr {
   456  	item = ShortenLabel(item, pkg)
   457  	for _, li := range AllLists(e) {
   458  		for _, elem := range li.List {
   459  			str, ok := elem.(*build.StringExpr)
   460  			if ok && LabelsEqual(str.Value, item, pkg) {
   461  				return str
   462  			}
   463  		}
   464  	}
   465  	return nil
   466  }
   467  
   468  // hasComments returns whether the StringExpr literal has a comment attached to it.
   469  func hasComments(literal *build.StringExpr) bool {
   470  	return len(literal.Before) > 0 || len(literal.Suffix) > 0
   471  }
   472  
   473  // ContainsComments returns whether the expr has a comment that includes str.
   474  func ContainsComments(expr build.Expr, str string) bool {
   475  	str = strings.ToLower(str)
   476  	com := expr.Comment()
   477  	comments := append(com.Before, com.Suffix...)
   478  	comments = append(comments, com.After...)
   479  	for _, c := range comments {
   480  		if strings.Contains(strings.ToLower(c.Token), str) {
   481  			return true
   482  		}
   483  	}
   484  	return false
   485  }
   486  
   487  // RemoveEmptySelectsAndConcatLists iterates the tree in order to turn
   488  // empty selects into empty lists and adjacent lists are concatenated
   489  func RemoveEmptySelectsAndConcatLists(e build.Expr) build.Expr {
   490  	switch e := e.(type) {
   491  	case *build.BinaryExpr:
   492  		if e.Op == "+" {
   493  			e.X = RemoveEmptySelectsAndConcatLists(e.X)
   494  			e.Y = RemoveEmptySelectsAndConcatLists(e.Y)
   495  
   496  			x, xIsList := e.X.(*build.ListExpr)
   497  			y, yIsList := e.Y.(*build.ListExpr)
   498  
   499  			if xIsList && yIsList {
   500  				return &build.ListExpr{List: append(x.List, y.List...)}
   501  			}
   502  
   503  			if xIsList && len(x.List) == 0 {
   504  				return e.Y
   505  			}
   506  
   507  			if yIsList && len(y.List) == 0 {
   508  				return e.X
   509  			}
   510  		}
   511  	case *build.CallExpr:
   512  		if x, ok := e.X.(*build.Ident); ok && x.Name == "select" {
   513  			if len(e.List) == 0 {
   514  				return &build.ListExpr{List: []build.Expr{}}
   515  			}
   516  
   517  			if dict, ok := e.List[0].(*build.DictExpr); ok {
   518  				for _, keyVal := range dict.List {
   519  					if keyVal, ok := keyVal.(*build.KeyValueExpr); ok {
   520  						val, ok := keyVal.Value.(*build.ListExpr)
   521  						if !ok || len(val.List) > 0 {
   522  							return e
   523  						}
   524  					} else {
   525  						return e
   526  					}
   527  				}
   528  
   529  				return &build.ListExpr{List: []build.Expr{}}
   530  			}
   531  		}
   532  	}
   533  
   534  	return e
   535  }
   536  
   537  // ComputeIntersection returns the intersection of the two lists given as parameters;
   538  // if the containing elements are not build.StringExpr, the result will be nil.
   539  func ComputeIntersection(list1, list2 []build.Expr) []build.Expr {
   540  	if list1 == nil || list2 == nil {
   541  		return nil
   542  	}
   543  
   544  	if len(list2) == 0 {
   545  		return []build.Expr{}
   546  	}
   547  
   548  	i := 0
   549  	for j, common := range list1 {
   550  		if common, ok := common.(*build.StringExpr); ok {
   551  			found := false
   552  			for _, elem := range list2 {
   553  				if str, ok := elem.(*build.StringExpr); ok {
   554  					if str.Value == common.Value {
   555  						found = true
   556  						break
   557  					}
   558  				} else {
   559  					return nil
   560  				}
   561  			}
   562  
   563  			if found {
   564  				list1[i] = list1[j]
   565  				i++
   566  			}
   567  		} else {
   568  			return nil
   569  		}
   570  	}
   571  	return list1[:i]
   572  }
   573  
   574  // SelectListsIntersection returns the intersection of the lists of strings inside
   575  // the dictionary argument of the select expression given as a parameter
   576  func SelectListsIntersection(sel *build.CallExpr, pkg string) (intersection []build.Expr) {
   577  	if len(sel.List) == 0 || len(sel.List) > 1 {
   578  		return nil
   579  	}
   580  
   581  	dict, ok := sel.List[0].(*build.DictExpr)
   582  	if !ok || len(dict.List) == 0 {
   583  		return nil
   584  	}
   585  
   586  	if keyVal, ok := dict.List[0].(*build.KeyValueExpr); ok {
   587  		if val, ok := keyVal.Value.(*build.ListExpr); ok {
   588  			intersection = make([]build.Expr, len(val.List))
   589  			copy(intersection, val.List)
   590  		}
   591  	}
   592  
   593  	for _, keyVal := range dict.List[1:] {
   594  		if keyVal, ok := keyVal.(*build.KeyValueExpr); ok {
   595  			if val, ok := keyVal.Value.(*build.ListExpr); ok {
   596  				intersection = ComputeIntersection(intersection, val.List)
   597  				if len(intersection) == 0 {
   598  					return intersection
   599  				}
   600  			} else {
   601  				return nil
   602  			}
   603  		} else {
   604  			return nil
   605  		}
   606  	}
   607  
   608  	return intersection
   609  }
   610  
   611  // ResolveAttr extracts common elements of the lists inside select dictionaries
   612  // and adds them at attribute level rather than select level, as well as turns
   613  // empty selects into empty lists and concatenates adjacent lists
   614  func ResolveAttr(r *build.Rule, attr, pkg string) {
   615  	var toExtract []build.Expr
   616  
   617  	e := r.Attr(attr)
   618  	if e == nil {
   619  		return
   620  	}
   621  
   622  	for _, sel := range AllSelects(e) {
   623  		intersection := SelectListsIntersection(sel, pkg)
   624  		if intersection != nil {
   625  			toExtract = append(toExtract, intersection...)
   626  		}
   627  	}
   628  
   629  	for _, common := range toExtract {
   630  		e = AddValueToList(e, pkg, common, false) // this will also remove them from selects
   631  	}
   632  
   633  	r.SetAttr(attr, RemoveEmptySelectsAndConcatLists(e))
   634  }
   635  
   636  // SelectDelete removes the item from all the lists which are values
   637  // in the dictionary of every select
   638  func SelectDelete(e build.Expr, item, pkg string, deleted **build.StringExpr) {
   639  	for _, sel := range AllSelects(e) {
   640  		if len(sel.List) == 0 {
   641  			continue
   642  		}
   643  
   644  		if dict, ok := sel.List[0].(*build.DictExpr); ok {
   645  			for _, keyVal := range dict.List {
   646  				if keyVal, ok := keyVal.(*build.KeyValueExpr); ok {
   647  					if val, ok := keyVal.Value.(*build.ListExpr); ok {
   648  						RemoveFromList(val, item, pkg, deleted)
   649  					}
   650  				}
   651  			}
   652  		}
   653  	}
   654  }
   655  
   656  // RemoveFromList removes one element from a ListExpr and stores
   657  // the deleted StringExpr at the address pointed by the last parameter
   658  func RemoveFromList(li *build.ListExpr, item, pkg string, deleted **build.StringExpr) {
   659  	var all []build.Expr
   660  	for _, elem := range li.List {
   661  		if str, ok := elem.(*build.StringExpr); ok {
   662  			if LabelsEqual(str.Value, item, pkg) && (DeleteWithComments || !hasComments(str)) {
   663  				if deleted != nil {
   664  					*deleted = str
   665  				}
   666  
   667  				continue
   668  			}
   669  		}
   670  		all = append(all, elem)
   671  	}
   672  	li.List = all
   673  }
   674  
   675  // ListDelete deletes the item from a list expression in e and returns
   676  // the StringExpr deleted, or nil otherwise.
   677  func ListDelete(e build.Expr, item, pkg string) (deleted *build.StringExpr) {
   678  	if unquoted, _, err := build.Unquote(item); err == nil {
   679  		item = unquoted
   680  	}
   681  	deleted = nil
   682  	item = ShortenLabel(item, pkg)
   683  	for _, li := range AllLists(e) {
   684  		RemoveFromList(li, item, pkg, &deleted)
   685  	}
   686  
   687  	SelectDelete(e, item, pkg, &deleted)
   688  
   689  	return deleted
   690  }
   691  
   692  // ListAttributeDelete deletes string item from list attribute attr, deletes attr if empty,
   693  // and returns the StringExpr deleted, or nil otherwise.
   694  func ListAttributeDelete(rule *build.Rule, attr, item, pkg string) *build.StringExpr {
   695  	deleted := ListDelete(rule.Attr(attr), item, pkg)
   696  	if deleted != nil {
   697  		if listExpr, ok := rule.Attr(attr).(*build.ListExpr); ok && len(listExpr.List) == 0 {
   698  			rule.DelAttr(attr)
   699  		}
   700  	}
   701  	return deleted
   702  }
   703  
   704  // ListReplace replaces old with value in all lists in e and returns a Boolean
   705  // to indicate whether the replacement was successful.
   706  func ListReplace(e build.Expr, old, value, pkg string) bool {
   707  	replaced := false
   708  	old = ShortenLabel(old, pkg)
   709  	for _, li := range AllLists(e) {
   710  		for k, elem := range li.List {
   711  			str, ok := elem.(*build.StringExpr)
   712  			if !ok || !LabelsEqual(str.Value, old, pkg) {
   713  				continue
   714  			}
   715  			li.List[k] = &build.StringExpr{Value: ShortenLabel(value, pkg), Comments: *elem.Comment()}
   716  			replaced = true
   717  		}
   718  	}
   719  	return replaced
   720  }
   721  
   722  // ListSubstitute replaces strings matching a regular expression in all lists
   723  // in e and returns a Boolean to indicate whether the replacement was
   724  // successful.
   725  func ListSubstitute(e build.Expr, oldRegexp *regexp.Regexp, newTemplate string) bool {
   726  	substituted := false
   727  	for _, li := range AllLists(e) {
   728  		for k, elem := range li.List {
   729  			str, ok := elem.(*build.StringExpr)
   730  			if !ok {
   731  				continue
   732  			}
   733  			newValue, ok := stringSubstitute(str.Value, oldRegexp, newTemplate)
   734  			if ok {
   735  				li.List[k] = &build.StringExpr{Value: newValue, Comments: *elem.Comment()}
   736  				substituted = true
   737  			}
   738  		}
   739  	}
   740  	return substituted
   741  }
   742  
   743  func stringSubstitute(oldValue string, oldRegexp *regexp.Regexp, newTemplate string) (string, bool) {
   744  	match := oldRegexp.FindStringSubmatchIndex(oldValue)
   745  	if match == nil {
   746  		return oldValue, false
   747  	}
   748  	newValue := string(oldRegexp.ExpandString(nil, newTemplate, oldValue, match))
   749  	if match[0] > 0 {
   750  		newValue = oldValue[:match[0]] + newValue
   751  	}
   752  	if match[1] < len(oldValue) {
   753  		newValue = newValue + oldValue[match[1]:]
   754  	}
   755  	return newValue, true
   756  }
   757  
   758  // isExprLessThan compares two Expr statements. Currently, only labels are supported.
   759  func isExprLessThan(x1, x2 build.Expr) bool {
   760  	str1, ok1 := x1.(*build.StringExpr)
   761  	str2, ok2 := x2.(*build.StringExpr)
   762  	if ok1 != ok2 {
   763  		return ok2
   764  	}
   765  	if ok1 && ok2 {
   766  		// Labels starting with // are put at the end.
   767  		pre1 := strings.HasPrefix(str1.Value, "//")
   768  		pre2 := strings.HasPrefix(str2.Value, "//")
   769  		if pre1 != pre2 {
   770  			return pre2
   771  		}
   772  		return str1.Value < str2.Value
   773  	}
   774  	return false
   775  }
   776  
   777  func sortedInsert(list []build.Expr, item build.Expr) []build.Expr {
   778  	i := 0
   779  	for ; i < len(list); i++ {
   780  		if isExprLessThan(item, list[i]) {
   781  			break
   782  		}
   783  	}
   784  	res := make([]build.Expr, 0, len(list)+1)
   785  	res = append(res, list[:i]...)
   786  	res = append(res, item)
   787  	res = append(res, list[i:]...)
   788  	return res
   789  }
   790  
   791  // attributeMustNotBeSorted returns true if the list in the attribute cannot be
   792  // sorted. For some attributes, it makes sense to try to do a sorted insert
   793  // (e.g. deps), even when buildifier will not sort it for conservative reasons.
   794  // For a few attributes, sorting will never make sense.
   795  func attributeMustNotBeSorted(rule, attr string) bool {
   796  	// TODO(bazel-team): Come up with a more complete list.
   797  	return attr == "args"
   798  }
   799  
   800  // getVariable returns the binary expression that assignes a variable to expr, if expr is
   801  // an identifier of a variable that vars contains a mapping for.
   802  func getVariable(expr build.Expr, vars *map[string]*build.AssignExpr) (varAssignment *build.AssignExpr) {
   803  	if vars == nil {
   804  		return nil
   805  	}
   806  
   807  	if literal, ok := expr.(*build.Ident); ok {
   808  		if varAssignment = (*vars)[literal.Name]; varAssignment != nil {
   809  			return varAssignment
   810  		}
   811  	}
   812  	return nil
   813  }
   814  
   815  // AddValueToList adds a value to a list. If the expression is
   816  // not a list, a list with a single element is appended to the original
   817  // expression.
   818  func AddValueToList(oldList build.Expr, pkg string, item build.Expr, sorted bool) build.Expr {
   819  	if oldList == nil {
   820  		return &build.ListExpr{List: []build.Expr{item}}
   821  	}
   822  
   823  	str, ok := item.(*build.StringExpr)
   824  	if ok {
   825  		if ListFind(oldList, str.Value, pkg) != nil {
   826  			// The value is already in the list.
   827  			return oldList
   828  		}
   829  		SelectDelete(oldList, str.Value, pkg, nil)
   830  	}
   831  
   832  	li := FirstList(oldList)
   833  	if li != nil {
   834  		if sorted {
   835  			li.List = sortedInsert(li.List, item)
   836  		} else {
   837  			li.List = append(li.List, item)
   838  		}
   839  		return oldList
   840  	}
   841  	list := &build.ListExpr{List: []build.Expr{item}}
   842  	concat := &build.BinaryExpr{Op: "+", X: oldList, Y: list}
   843  	return concat
   844  }
   845  
   846  // AddValueToListAttribute adds the given item to the list attribute identified by name and pkg.
   847  func AddValueToListAttribute(r *build.Rule, name string, pkg string, item build.Expr, vars *map[string]*build.AssignExpr) {
   848  	old := r.Attr(name)
   849  	sorted := !attributeMustNotBeSorted(r.Kind(), name)
   850  	if varAssignment := getVariable(old, vars); varAssignment != nil {
   851  		varAssignment.RHS = AddValueToList(varAssignment.RHS, pkg, item, sorted)
   852  	} else {
   853  		r.SetAttr(name, AddValueToList(old, pkg, item, sorted))
   854  	}
   855  }
   856  
   857  // MoveAllListAttributeValues moves all values from list attribute oldAttr to newAttr,
   858  // and deletes oldAttr.
   859  func MoveAllListAttributeValues(rule *build.Rule, oldAttr, newAttr, pkg string, vars *map[string]*build.AssignExpr) error {
   860  	if rule.Attr(oldAttr) == nil {
   861  		return fmt.Errorf("no attribute %s found in %s", oldAttr, rule.Name())
   862  	}
   863  	if rule.Attr(newAttr) == nil {
   864  		RenameAttribute(rule, oldAttr, newAttr)
   865  		return nil
   866  	}
   867  	if listExpr, ok := rule.Attr(oldAttr).(*build.ListExpr); ok {
   868  		for _, val := range listExpr.List {
   869  			AddValueToListAttribute(rule, newAttr, pkg, val, vars)
   870  		}
   871  		rule.DelAttr(oldAttr)
   872  		return nil
   873  	}
   874  	return fmt.Errorf("%s already exists and %s is not a simple list", newAttr, oldAttr)
   875  }
   876  
   877  // DictionarySet looks for the key in the dictionary expression. If value is not nil,
   878  // it replaces the current value with it. In all cases, it returns the current value.
   879  func DictionarySet(dict *build.DictExpr, key string, value build.Expr) build.Expr {
   880  	for _, e := range dict.List {
   881  		kv, _ := e.(*build.KeyValueExpr)
   882  		if k, ok := kv.Key.(*build.StringExpr); ok && k.Value == key {
   883  			if value != nil {
   884  				kv.Value = value
   885  			}
   886  			return kv.Value
   887  		}
   888  	}
   889  	if value != nil {
   890  		kv := &build.KeyValueExpr{Key: &build.StringExpr{Value: key}, Value: value}
   891  		dict.List = append(dict.List, kv)
   892  	}
   893  	return nil
   894  }
   895  
   896  // DictionaryGet looks for the key in the dictionary expression, and returns the
   897  // current value. If it is unset, it returns nil.
   898  func DictionaryGet(dict *build.DictExpr, key string) build.Expr {
   899  	for _, e := range dict.List {
   900  		kv, ok := e.(*build.KeyValueExpr)
   901  		if !ok {
   902  			continue
   903  		}
   904  		if k, ok := kv.Key.(*build.StringExpr); ok && k.Value == key {
   905  			return kv.Value
   906  		}
   907  	}
   908  	return nil
   909  }
   910  
   911  // DictionaryDelete looks for the key in the dictionary expression. If the key exists,
   912  // it removes the key-value pair and returns it. Otherwise it returns nil.
   913  func DictionaryDelete(dict *build.DictExpr, key string) (deleted build.Expr) {
   914  	if unquoted, _, err := build.Unquote(key); err == nil {
   915  		key = unquoted
   916  	}
   917  	deleted = nil
   918  	var all []build.Expr
   919  	for _, e := range dict.List {
   920  		kv, _ := e.(*build.KeyValueExpr)
   921  		if k, ok := kv.Key.(*build.StringExpr); ok {
   922  			if k.Value == key {
   923  				deleted = kv
   924  			} else {
   925  				all = append(all, e)
   926  			}
   927  		}
   928  	}
   929  	dict.List = all
   930  	return deleted
   931  }
   932  
   933  // RenameAttribute renames an attribute in a rule.
   934  func RenameAttribute(r *build.Rule, oldName, newName string) error {
   935  	if r.Attr(newName) != nil {
   936  		return fmt.Errorf("attribute %s already exists in rule %s", newName, r.Name())
   937  	}
   938  	for _, kv := range r.Call.List {
   939  		as, ok := kv.(*build.AssignExpr)
   940  		if !ok {
   941  			continue
   942  		}
   943  		k, ok := as.LHS.(*build.Ident)
   944  		if !ok || k.Name != oldName {
   945  			continue
   946  		}
   947  		k.Name = newName
   948  		return nil
   949  	}
   950  	return fmt.Errorf("no attribute %s found in rule %s", oldName, r.Name())
   951  }
   952  
   953  // EditFunction is a wrapper around build.Edit. The callback is called only on
   954  // functions 'name'.
   955  func EditFunction(v build.Expr, name string, f func(x *build.CallExpr, stk []build.Expr) build.Expr) build.Expr {
   956  	return build.Edit(v, func(expr build.Expr, stk []build.Expr) build.Expr {
   957  		call, ok := expr.(*build.CallExpr)
   958  		if !ok {
   959  			return nil
   960  		}
   961  		fct, ok := call.X.(*build.Ident)
   962  		if !ok || fct.Name != name {
   963  			return nil
   964  		}
   965  		return f(call, stk)
   966  	})
   967  }
   968  
   969  // UsedSymbols returns the set of symbols used in the BUILD file (variables, function names).
   970  func UsedSymbols(stmt build.Expr) map[string]bool {
   971  	symbols := make(map[string]bool)
   972  	build.Walk(stmt, func(expr build.Expr, stack []build.Expr) {
   973  		// Don't traverse inside load statements
   974  		if len(stack) > 0 {
   975  			if _, ok := stack[len(stack)-1].(*build.LoadStmt); ok {
   976  				return
   977  			}
   978  		}
   979  
   980  		literal, ok := expr.(*build.Ident)
   981  		if !ok {
   982  			return
   983  		}
   984  		// Check if we are on the left-side of an assignment
   985  		for _, e := range stack {
   986  			if as, ok := e.(*build.AssignExpr); ok {
   987  				if as.LHS == expr {
   988  					return
   989  				}
   990  			}
   991  		}
   992  		symbols[literal.Name] = true
   993  	})
   994  	return symbols
   995  }
   996  
   997  // NewLoad creates a new LoadStmt node
   998  func NewLoad(location string, from, to []string) *build.LoadStmt {
   999  	load := &build.LoadStmt{
  1000  		Module: &build.StringExpr{
  1001  			Value: location,
  1002  		},
  1003  		ForceCompact: true,
  1004  	}
  1005  	for i := range from {
  1006  		load.From = append(load.From, &build.Ident{Name: from[i]})
  1007  		load.To = append(load.To, &build.Ident{Name: to[i]})
  1008  	}
  1009  	return load
  1010  }
  1011  
  1012  // AppendToLoad appends symbols to an existing load statement
  1013  // Returns true if the statement was acually edited (if the required symbols haven't been
  1014  // loaded yet)
  1015  func AppendToLoad(load *build.LoadStmt, from, to []string) bool {
  1016  	symbolsToLoad := make(map[string]string)
  1017  	for i, s := range to {
  1018  		symbolsToLoad[s] = from[i]
  1019  	}
  1020  	for _, ident := range load.To {
  1021  		delete(symbolsToLoad, ident.Name) // Already loaded.
  1022  	}
  1023  
  1024  	if len(symbolsToLoad) == 0 {
  1025  		return false
  1026  	}
  1027  
  1028  	// Append the remaining loads to the load statement.
  1029  	sortedSymbols := []string{}
  1030  	for s := range symbolsToLoad {
  1031  		sortedSymbols = append(sortedSymbols, s)
  1032  	}
  1033  	sort.Strings(sortedSymbols)
  1034  	for _, s := range sortedSymbols {
  1035  		load.From = append(load.From, &build.Ident{Name: symbolsToLoad[s]})
  1036  		load.To = append(load.To, &build.Ident{Name: s})
  1037  	}
  1038  	return true
  1039  }
  1040  
  1041  // appendLoad tries to find an existing load location and append symbols to it.
  1042  func appendLoad(stmts []build.Expr, location string, from, to []string) bool {
  1043  	symbolsToLoad := make(map[string]string)
  1044  	for i, s := range to {
  1045  		symbolsToLoad[s] = from[i]
  1046  	}
  1047  	var lastLoad *build.LoadStmt
  1048  	for _, s := range stmts {
  1049  		load, ok := s.(*build.LoadStmt)
  1050  		if !ok {
  1051  			continue
  1052  		}
  1053  		if load.Module.Value != location {
  1054  			continue // Loads a different file.
  1055  		}
  1056  		for _, ident := range load.To {
  1057  			delete(symbolsToLoad, ident.Name) // Already loaded.
  1058  		}
  1059  		// Remember the last insert location, but potentially remove more symbols
  1060  		// that are already loaded in other subsequent calls.
  1061  		lastLoad = load
  1062  	}
  1063  	if lastLoad == nil {
  1064  		return false
  1065  	}
  1066  
  1067  	// Append the remaining loads to the last load location.
  1068  	from = []string{}
  1069  	to = []string{}
  1070  	for t, f := range symbolsToLoad {
  1071  		from = append(from, f)
  1072  		to = append(to, t)
  1073  	}
  1074  	AppendToLoad(lastLoad, from, to)
  1075  	return true
  1076  }
  1077  
  1078  // InsertLoad inserts a load statement at the top of the list of statements.
  1079  // The load statement is constructed using a string location and two slices of from- and to-symbols.
  1080  // The function panics if the slices aren't of the same lentgh. Symbols that are already loaded
  1081  // from the given filepath are ignored. If stmts already contains a load for the
  1082  // location in arguments, appends the symbols to load to it.
  1083  func InsertLoad(stmts []build.Expr, location string, from, to []string) []build.Expr {
  1084  	if len(from) != len(to) {
  1085  		panic(fmt.Errorf("length mismatch: %v (from) and %v (to)", len(from), len(to)))
  1086  	}
  1087  
  1088  	if appendLoad(stmts, location, from, to) {
  1089  		return stmts
  1090  	}
  1091  
  1092  	load := NewLoad(location, from, to)
  1093  
  1094  	var all []build.Expr
  1095  	added := false
  1096  	for i, stmt := range stmts {
  1097  		_, isComment := stmt.(*build.CommentBlock)
  1098  		_, isString := stmt.(*build.StringExpr)
  1099  		isDocString := isString && i == 0
  1100  		if isComment || isDocString || added {
  1101  			all = append(all, stmt)
  1102  			continue
  1103  		}
  1104  		all = append(all, load)
  1105  		all = append(all, stmt)
  1106  		added = true
  1107  	}
  1108  	if !added { // Empty file or just comments.
  1109  		all = append(all, load)
  1110  	}
  1111  	return all
  1112  }
  1113  
  1114  // ReplaceLoad removes load statements for passed to-symbols and replaces them with a new
  1115  // load at the top of the list of statements. The new load statement is constructed using
  1116  // a string location and two slices of from- and to-symbols. If stmts already contains a
  1117  // load for the location in arguments, appends the symbols to load to it.
  1118  // The function panics if the slices aren't of the same lentgh.
  1119  func ReplaceLoad(stmts []build.Expr, location string, from, to []string) []build.Expr {
  1120  	if len(from) != len(to) {
  1121  		panic(fmt.Errorf("length mismatch: %v (from) and %v (to)", len(from), len(to)))
  1122  	}
  1123  
  1124  	toSymbols := make(map[string]bool, len(to))
  1125  	for _, name := range to {
  1126  		toSymbols[name] = true
  1127  	}
  1128  
  1129  	// 1. Remove loads that will be replaced.
  1130  	var all []build.Expr
  1131  	for _, stmt := range stmts {
  1132  		load, ok := stmt.(*build.LoadStmt)
  1133  		if !ok {
  1134  			all = append(all, stmt)
  1135  			continue
  1136  		}
  1137  
  1138  		for i, to := range load.To {
  1139  			if toSymbols[to.Name] {
  1140  				if i < len(load.From)-1 {
  1141  					load.From = append(load.From[:i], load.From[i+1:]...)
  1142  					load.To = append(load.To[:i], load.To[i+1:]...)
  1143  				} else {
  1144  					load.From = load.From[:i]
  1145  					load.To = load.To[:i]
  1146  				}
  1147  			}
  1148  		}
  1149  
  1150  		if len(load.To) > 0 {
  1151  			all = append(all, load)
  1152  		}
  1153  	}
  1154  
  1155  	// 2. Insert new loads.
  1156  	return InsertLoad(all, location, from, to)
  1157  }