github.com/bazelbuild/bazel-gazelle@v0.36.1-0.20240520142334-61b277ba6fed/rule/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 rule
    17  
    18  import (
    19  	"sort"
    20  	"strings"
    21  
    22  	bzl "github.com/bazelbuild/buildtools/build"
    23  )
    24  
    25  // sortExprLabels sorts lists of strings using the same order as buildifier.
    26  // Buildifier also sorts string lists, but not those involved with "select"
    27  // expressions. This function is intended to be used with bzl.Walk.
    28  func sortExprLabels(e bzl.Expr, _ []bzl.Expr) {
    29  	list, ok := e.(*bzl.ListExpr)
    30  	if !ok || len(list.List) == 0 {
    31  		return
    32  	}
    33  
    34  	keys := make([]stringSortKey, len(list.List))
    35  	for i, elem := range list.List {
    36  		s, ok := elem.(*bzl.StringExpr)
    37  		if !ok {
    38  			return // don't sort lists unless all elements are strings
    39  		}
    40  		keys[i] = makeSortKey(i, s)
    41  	}
    42  
    43  	before := keys[0].x.Comment().Before
    44  	keys[0].x.Comment().Before = nil
    45  	sort.Sort(byStringExpr(keys))
    46  	keys[0].x.Comment().Before = append(before, keys[0].x.Comment().Before...)
    47  	for i, k := range keys {
    48  		list.List[i] = k.x
    49  	}
    50  }
    51  
    52  // Code below this point is adapted from
    53  // github.com/bazelbuild/buildtools/build/rewrite.go
    54  
    55  // A stringSortKey records information about a single string literal to be
    56  // sorted. The strings are first grouped into four phases: most strings,
    57  // strings beginning with ":", strings beginning with "//", and strings
    58  // beginning with "@". The next significant part of the comparison is the list
    59  // of elements in the value, where elements are split at `.' and `:'. Finally
    60  // we compare by value and break ties by original index.
    61  type stringSortKey struct {
    62  	phase    int
    63  	split    []string
    64  	value    string
    65  	original int
    66  	x        bzl.Expr
    67  }
    68  
    69  func makeSortKey(index int, x *bzl.StringExpr) stringSortKey {
    70  	key := stringSortKey{
    71  		value:    x.Value,
    72  		original: index,
    73  		x:        x,
    74  	}
    75  
    76  	switch {
    77  	case strings.HasPrefix(x.Value, ":"):
    78  		key.phase = 1
    79  	case strings.HasPrefix(x.Value, "//"):
    80  		key.phase = 2
    81  	case strings.HasPrefix(x.Value, "@"):
    82  		key.phase = 3
    83  	}
    84  
    85  	key.split = strings.Split(strings.Replace(x.Value, ":", ".", -1), ".")
    86  	return key
    87  }
    88  
    89  // byStringExpr implements sort.Interface for a list of stringSortKey.
    90  type byStringExpr []stringSortKey
    91  
    92  func (x byStringExpr) Len() int      { return len(x) }
    93  func (x byStringExpr) Swap(i, j int) { x[i], x[j] = x[j], x[i] }
    94  
    95  func (x byStringExpr) Less(i, j int) bool {
    96  	xi := x[i]
    97  	xj := x[j]
    98  
    99  	if xi.phase != xj.phase {
   100  		return xi.phase < xj.phase
   101  	}
   102  	for k := 0; k < len(xi.split) && k < len(xj.split); k++ {
   103  		if xi.split[k] != xj.split[k] {
   104  			return xi.split[k] < xj.split[k]
   105  		}
   106  	}
   107  	if len(xi.split) != len(xj.split) {
   108  		return len(xi.split) < len(xj.split)
   109  	}
   110  	if xi.value != xj.value {
   111  		return xi.value < xj.value
   112  	}
   113  	return xi.original < xj.original
   114  }