github.com/bazelbuild/bazel-gazelle@v0.36.1-0.20240520142334-61b277ba6fed/rule/expr.go (about)

     1  /* Copyright 2018 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  	"fmt"
    20  	"log"
    21  	"strings"
    22  
    23  	"github.com/bazelbuild/bazel-gazelle/label"
    24  	bzl "github.com/bazelbuild/buildtools/build"
    25  )
    26  
    27  // MapExprStrings applies a function to string sub-expressions within e.
    28  // An expression containing the results with the same structure as e is
    29  // returned.
    30  func MapExprStrings(e bzl.Expr, f func(string) string) bzl.Expr {
    31  	if e == nil {
    32  		return nil
    33  	}
    34  	switch expr := e.(type) {
    35  	case *bzl.StringExpr:
    36  		s := f(expr.Value)
    37  		if s == "" {
    38  			return nil
    39  		}
    40  		ret := *expr
    41  		ret.Value = s
    42  		return &ret
    43  
    44  	case *bzl.ListExpr:
    45  		var list []bzl.Expr
    46  		for _, elem := range expr.List {
    47  			elem = MapExprStrings(elem, f)
    48  			if elem != nil {
    49  				list = append(list, elem)
    50  			}
    51  		}
    52  		if len(list) == 0 && len(expr.List) > 0 {
    53  			return nil
    54  		}
    55  		ret := *expr
    56  		ret.List = list
    57  		return &ret
    58  
    59  	case *bzl.DictExpr:
    60  		var cases []*bzl.KeyValueExpr
    61  		isEmpty := true
    62  		for _, kv := range expr.List {
    63  			value := MapExprStrings(kv.Value, f)
    64  			if value != nil {
    65  				cases = append(cases, &bzl.KeyValueExpr{Key: kv.Key, Value: value})
    66  				if key, ok := kv.Key.(*bzl.StringExpr); !ok || key.Value != "//conditions:default" {
    67  					isEmpty = false
    68  				}
    69  			}
    70  		}
    71  		if isEmpty {
    72  			return nil
    73  		}
    74  		ret := *expr
    75  		ret.List = cases
    76  		return &ret
    77  
    78  	case *bzl.CallExpr:
    79  		if x, ok := expr.X.(*bzl.Ident); !ok || x.Name != "select" || len(expr.List) != 1 {
    80  			log.Panicf("unexpected call expression in generated imports: %#v", e)
    81  		}
    82  		arg := MapExprStrings(expr.List[0], f)
    83  		if arg == nil {
    84  			return nil
    85  		}
    86  		call := *expr
    87  		call.List[0] = arg
    88  		return &call
    89  
    90  	case *bzl.BinaryExpr:
    91  		x := MapExprStrings(expr.X, f)
    92  		y := MapExprStrings(expr.Y, f)
    93  		if x == nil {
    94  			return y
    95  		}
    96  		if y == nil {
    97  			return x
    98  		}
    99  		binop := *expr
   100  		binop.X = x
   101  		binop.Y = y
   102  		return &binop
   103  
   104  	default:
   105  		return nil
   106  	}
   107  }
   108  
   109  // FlattenExpr takes an expression that may have been generated from
   110  // PlatformStrings and returns its values in a flat, sorted, de-duplicated
   111  // list. Comments are accumulated and de-duplicated across duplicate
   112  // expressions. If the expression could not have been generted by
   113  // PlatformStrings, the expression will be returned unmodified.
   114  func FlattenExpr(e bzl.Expr) bzl.Expr {
   115  	ps, err := extractPlatformStringsExprs(e)
   116  	if err != nil {
   117  		return e
   118  	}
   119  
   120  	ls := makeListSquasher()
   121  	addElem := func(e bzl.Expr) bool {
   122  		s, ok := e.(*bzl.StringExpr)
   123  		if !ok {
   124  			return false
   125  		}
   126  		ls.add(s)
   127  		return true
   128  	}
   129  	addList := func(e bzl.Expr) bool {
   130  		l, ok := e.(*bzl.ListExpr)
   131  		if !ok {
   132  			return false
   133  		}
   134  		for _, elem := range l.List {
   135  			if !addElem(elem) {
   136  				return false
   137  			}
   138  		}
   139  		return true
   140  	}
   141  	addDict := func(d *bzl.DictExpr) bool {
   142  		for _, kv := range d.List {
   143  			if !addList(kv.Value) {
   144  				return false
   145  			}
   146  		}
   147  		return true
   148  	}
   149  
   150  	if ps.generic != nil {
   151  		if !addList(ps.generic) {
   152  			return e
   153  		}
   154  	}
   155  	for _, d := range []*bzl.DictExpr{ps.os, ps.arch, ps.platform} {
   156  		if d == nil {
   157  			continue
   158  		}
   159  		if !addDict(d) {
   160  			return e
   161  		}
   162  	}
   163  
   164  	return ls.list()
   165  }
   166  
   167  func isScalar(e bzl.Expr) bool {
   168  	switch e.(type) {
   169  	case *bzl.StringExpr, *bzl.LiteralExpr, *bzl.Ident:
   170  		return true
   171  	default:
   172  		return false
   173  	}
   174  }
   175  
   176  func dictEntryKeyValue(e bzl.Expr) (string, *bzl.ListExpr, error) {
   177  	kv, ok := e.(*bzl.KeyValueExpr)
   178  	if !ok {
   179  		return "", nil, fmt.Errorf("dict entry was not a key-value pair: %#v", e)
   180  	}
   181  	k, ok := kv.Key.(*bzl.StringExpr)
   182  	if !ok {
   183  		return "", nil, fmt.Errorf("dict key was not string: %#v", kv.Key)
   184  	}
   185  	v, ok := kv.Value.(*bzl.ListExpr)
   186  	if !ok {
   187  		return "", nil, fmt.Errorf("dict value was not list: %#v", kv.Value)
   188  	}
   189  	return k.Value, v, nil
   190  }
   191  
   192  func stringValue(e bzl.Expr) string {
   193  	s, ok := e.(*bzl.StringExpr)
   194  	if !ok {
   195  		return ""
   196  	}
   197  	return s.Value
   198  }
   199  
   200  // platformStringsExprs is a set of sub-expressions that match the structure
   201  // of package.PlatformStrings. ExprFromValue produces expressions that
   202  // follow this structure for srcs, deps, and other attributes, so this matches
   203  // all non-scalar expressions generated by Gazelle.
   204  //
   205  // The matched expression has the form:
   206  //
   207  // [] + select({}) + select({}) + select({})
   208  //
   209  // The four collections may appear in any order, and some or all of them may
   210  // be omitted (all fields are nil for a nil expression).
   211  type platformStringsExprs struct {
   212  	generic            *bzl.ListExpr
   213  	os, arch, platform *bzl.DictExpr
   214  }
   215  
   216  // extractPlatformStringsExprs matches an expression and attempts to extract
   217  // sub-expressions in platformStringsExprs. The sub-expressions can then be
   218  // merged with corresponding sub-expressions. Any field in the returned
   219  // structure may be nil. An error is returned if the given expression does
   220  // not follow the pattern described by platformStringsExprs.
   221  func extractPlatformStringsExprs(expr bzl.Expr) (platformStringsExprs, error) {
   222  	var ps platformStringsExprs
   223  	if expr == nil {
   224  		return ps, nil
   225  	}
   226  
   227  	// Break the expression into a sequence of expressions combined with +.
   228  	var parts []bzl.Expr
   229  	for {
   230  		binop, ok := expr.(*bzl.BinaryExpr)
   231  		if !ok {
   232  			parts = append(parts, expr)
   233  			break
   234  		}
   235  		parts = append(parts, binop.Y)
   236  		expr = binop.X
   237  	}
   238  
   239  	// Process each part. They may be in any order.
   240  	for _, part := range parts {
   241  		switch part := part.(type) {
   242  		case *bzl.ListExpr:
   243  			if ps.generic != nil {
   244  				return platformStringsExprs{}, fmt.Errorf("expression could not be matched: multiple list expressions")
   245  			}
   246  			ps.generic = part
   247  
   248  		case *bzl.CallExpr:
   249  			x, ok := part.X.(*bzl.Ident)
   250  			if !ok || x.Name != "select" || len(part.List) != 1 {
   251  				return platformStringsExprs{}, fmt.Errorf("expression could not be matched: callee other than select or wrong number of args")
   252  			}
   253  			arg, ok := part.List[0].(*bzl.DictExpr)
   254  			if !ok {
   255  				return platformStringsExprs{}, fmt.Errorf("expression could not be matched: select argument not dict")
   256  			}
   257  			var dict **bzl.DictExpr
   258  			for _, kv := range arg.List {
   259  				k, ok := kv.Key.(*bzl.StringExpr)
   260  				if !ok {
   261  					return platformStringsExprs{}, fmt.Errorf("expression could not be matched: dict keys are not all strings")
   262  				}
   263  				if k.Value == "//conditions:default" {
   264  					continue
   265  				}
   266  				key, err := label.Parse(k.Value)
   267  				if err != nil {
   268  					return platformStringsExprs{}, fmt.Errorf("expression could not be matched: dict key is not label: %q", k.Value)
   269  				}
   270  				if KnownOSSet[key.Name] {
   271  					dict = &ps.os
   272  					break
   273  				}
   274  				if KnownArchSet[key.Name] {
   275  					dict = &ps.arch
   276  					break
   277  				}
   278  				osArch := strings.Split(key.Name, "_")
   279  				if len(osArch) != 2 || !KnownOSSet[osArch[0]] || !KnownArchSet[osArch[1]] {
   280  					return platformStringsExprs{}, fmt.Errorf("expression could not be matched: dict key contains unknown platform: %q", k.Value)
   281  				}
   282  				dict = &ps.platform
   283  				break
   284  			}
   285  			if dict == nil {
   286  				// We could not identify the dict because it's empty or only contains
   287  				// //conditions:default. We'll call it the platform dict to avoid
   288  				// dropping it.
   289  				dict = &ps.platform
   290  			}
   291  			if *dict != nil {
   292  				return platformStringsExprs{}, fmt.Errorf("expression could not be matched: multiple selects that are either os-specific, arch-specific, or platform-specific")
   293  			}
   294  			*dict = arg
   295  		}
   296  	}
   297  	return ps, nil
   298  }
   299  
   300  // makePlatformStringsExpr constructs a single expression from the
   301  // sub-expressions in ps.
   302  func makePlatformStringsExpr(ps platformStringsExprs) bzl.Expr {
   303  	makeSelect := func(dict *bzl.DictExpr) bzl.Expr {
   304  		return &bzl.CallExpr{
   305  			X:    &bzl.Ident{Name: "select"},
   306  			List: []bzl.Expr{dict},
   307  		}
   308  	}
   309  	forceMultiline := func(e bzl.Expr) {
   310  		switch e := e.(type) {
   311  		case *bzl.ListExpr:
   312  			e.ForceMultiLine = true
   313  		case *bzl.CallExpr:
   314  			e.List[0].(*bzl.DictExpr).ForceMultiLine = true
   315  		}
   316  	}
   317  
   318  	var parts []bzl.Expr
   319  	if ps.generic != nil {
   320  		parts = append(parts, ps.generic)
   321  	}
   322  	if ps.os != nil {
   323  		parts = append(parts, makeSelect(ps.os))
   324  	}
   325  	if ps.arch != nil {
   326  		parts = append(parts, makeSelect(ps.arch))
   327  	}
   328  	if ps.platform != nil {
   329  		parts = append(parts, makeSelect(ps.platform))
   330  	}
   331  
   332  	if len(parts) == 0 {
   333  		return nil
   334  	}
   335  	if len(parts) == 1 {
   336  		return parts[0]
   337  	}
   338  	expr := parts[0]
   339  	forceMultiline(expr)
   340  	for _, part := range parts[1:] {
   341  		forceMultiline(part)
   342  		expr = &bzl.BinaryExpr{
   343  			Op: "+",
   344  			X:  expr,
   345  			Y:  part,
   346  		}
   347  	}
   348  	return expr
   349  }