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 }