github.com/afking/bazel-gazelle@v0.0.0-20180301150245-c02bc0f529e8/internal/rules/sort_labels.go (about)

     1  /* Copyright 2017 The Bazel Authors. All rights reserved.
     2  
     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  
     7     http://www.apache.org/licenses/LICENSE-2.0
     8  
     9  Unless required by applicable law or agreed to in writing, software
    10  distributed under the License is distributed on an "AS IS" BASIS,
    11  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  See the License for the specific language governing permissions and
    13  limitations under the License.
    14  */
    15  
    16  package rules
    17  
    18  import (
    19  	"sort"
    20  	"strings"
    21  
    22  	bf "github.com/bazelbuild/buildtools/build"
    23  )
    24  
    25  var (
    26  	goRuleKinds = map[string]bool{
    27  		"cgo_library": true,
    28  		"go_binary":   true,
    29  		"go_library":  true,
    30  		"go_test":     true,
    31  	}
    32  	sortedAttrs = []string{"srcs", "deps"}
    33  )
    34  
    35  // SortLabels sorts lists of strings in "srcs" and "deps" attributes of
    36  // Go rules using the same order as buildifier. Buildifier also sorts string
    37  // lists, but not those involved with "select" expressions.
    38  // TODO(jayconrod): remove this when bazelbuild/buildtools#122 is fixed.
    39  func SortLabels(f *bf.File) {
    40  	for _, s := range f.Stmt {
    41  		c, ok := s.(*bf.CallExpr)
    42  		if !ok {
    43  			continue
    44  		}
    45  		r := bf.Rule{Call: c}
    46  		if !goRuleKinds[r.Kind()] {
    47  			continue
    48  		}
    49  		for _, key := range []string{"srcs", "deps"} {
    50  			attr := r.AttrDefn(key)
    51  			if attr == nil {
    52  				continue
    53  			}
    54  			bf.Walk(attr.Y, sortExprLabels)
    55  		}
    56  	}
    57  }
    58  
    59  func sortExprLabels(e bf.Expr, _ []bf.Expr) {
    60  	list, ok := e.(*bf.ListExpr)
    61  	if !ok || len(list.List) == 0 {
    62  		return
    63  	}
    64  
    65  	keys := make([]stringSortKey, len(list.List))
    66  	for i, elem := range list.List {
    67  		s, ok := elem.(*bf.StringExpr)
    68  		if !ok {
    69  			return // don't sort lists unless all elements are strings
    70  		}
    71  		keys[i] = makeSortKey(i, s)
    72  	}
    73  
    74  	before := keys[0].x.Comment().Before
    75  	keys[0].x.Comment().Before = nil
    76  	sort.Sort(byStringExpr(keys))
    77  	keys[0].x.Comment().Before = append(before, keys[0].x.Comment().Before...)
    78  	for i, k := range keys {
    79  		list.List[i] = k.x
    80  	}
    81  }
    82  
    83  // Code below this point is adapted from
    84  // github.com/bazelbuild/buildtools/build/rewrite.go
    85  
    86  // A stringSortKey records information about a single string literal to be
    87  // sorted. The strings are first grouped into four phases: most strings,
    88  // strings beginning with ":", strings beginning with "//", and strings
    89  // beginning with "@". The next significant part of the comparison is the list
    90  // of elements in the value, where elements are split at `.' and `:'. Finally
    91  // we compare by value and break ties by original index.
    92  type stringSortKey struct {
    93  	phase    int
    94  	split    []string
    95  	value    string
    96  	original int
    97  	x        bf.Expr
    98  }
    99  
   100  func makeSortKey(index int, x *bf.StringExpr) stringSortKey {
   101  	key := stringSortKey{
   102  		value:    x.Value,
   103  		original: index,
   104  		x:        x,
   105  	}
   106  
   107  	switch {
   108  	case strings.HasPrefix(x.Value, ":"):
   109  		key.phase = 1
   110  	case strings.HasPrefix(x.Value, "//"):
   111  		key.phase = 2
   112  	case strings.HasPrefix(x.Value, "@"):
   113  		key.phase = 3
   114  	}
   115  
   116  	key.split = strings.Split(strings.Replace(x.Value, ":", ".", -1), ".")
   117  	return key
   118  }
   119  
   120  // byStringExpr implements sort.Interface for a list of stringSortKey.
   121  type byStringExpr []stringSortKey
   122  
   123  func (x byStringExpr) Len() int      { return len(x) }
   124  func (x byStringExpr) Swap(i, j int) { x[i], x[j] = x[j], x[i] }
   125  
   126  func (x byStringExpr) Less(i, j int) bool {
   127  	xi := x[i]
   128  	xj := x[j]
   129  
   130  	if xi.phase != xj.phase {
   131  		return xi.phase < xj.phase
   132  	}
   133  	for k := 0; k < len(xi.split) && k < len(xj.split); k++ {
   134  		if xi.split[k] != xj.split[k] {
   135  			return xi.split[k] < xj.split[k]
   136  		}
   137  	}
   138  	if len(xi.split) != len(xj.split) {
   139  		return len(xi.split) < len(xj.split)
   140  	}
   141  	if xi.value != xj.value {
   142  		return xi.value < xj.value
   143  	}
   144  	return xi.original < xj.original
   145  }