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