cuelang.org/go@v0.10.1/internal/buildattr/buildattr.go (about)

     1  // Package buildattr implements support for interpreting the @if
     2  // build attributes in CUE files.
     3  package buildattr
     4  
     5  import (
     6  	"cuelang.org/go/cue/ast"
     7  	"cuelang.org/go/cue/errors"
     8  	"cuelang.org/go/cue/parser"
     9  	"cuelang.org/go/cue/token"
    10  )
    11  
    12  // ShouldIgnoreFile reports whether a File contains an @ignore() file-level
    13  // attribute and hence should be ignored.
    14  func ShouldIgnoreFile(f *ast.File) bool {
    15  	ignore, _, _ := getBuildAttr(f)
    16  	return ignore
    17  }
    18  
    19  // ShouldBuildFile reports whether a File should be included based on its
    20  // attributes. It uses tagIsSet to determine whether a given attribute
    21  // key should be treated as set.
    22  //
    23  // It also returns the build attribute if one was found.
    24  func ShouldBuildFile(f *ast.File, tagIsSet func(key string) bool) (bool, *ast.Attribute, errors.Error) {
    25  	ignore, a, err := getBuildAttr(f)
    26  	if ignore || err != nil {
    27  		return false, a, err
    28  	}
    29  	if a == nil {
    30  		return true, nil, nil
    31  	}
    32  
    33  	_, body := a.Split()
    34  
    35  	expr, parseErr := parser.ParseExpr("", body)
    36  	if parseErr != nil {
    37  		return false, a, errors.Promote(parseErr, "")
    38  	}
    39  
    40  	include, err := shouldInclude(expr, tagIsSet)
    41  	if err != nil {
    42  		return false, a, err
    43  	}
    44  	return include, a, nil
    45  }
    46  
    47  func getBuildAttr(f *ast.File) (ignore bool, a *ast.Attribute, err errors.Error) {
    48  	for _, d := range f.Decls {
    49  		switch x := d.(type) {
    50  		case *ast.Attribute:
    51  			switch key, _ := x.Split(); key {
    52  			case "ignore":
    53  				return true, x, nil
    54  			case "if":
    55  				if a != nil {
    56  					err := errors.Newf(d.Pos(), "multiple @if attributes")
    57  					err = errors.Append(err,
    58  						errors.Newf(a.Pos(), "previous declaration here"))
    59  					return false, a, err
    60  				}
    61  				a = x
    62  			}
    63  		case *ast.Package:
    64  			return false, a, nil
    65  		case *ast.CommentGroup:
    66  		default:
    67  			// If it's anything else, then we know we won't see a package
    68  			// clause so avoid scanning more than we need to (this
    69  			// could be a large file with no package clause)
    70  			return false, a, nil
    71  		}
    72  	}
    73  	return false, a, nil
    74  }
    75  
    76  func shouldInclude(expr ast.Expr, tagIsSet func(key string) bool) (bool, errors.Error) {
    77  	switch x := expr.(type) {
    78  	case *ast.Ident:
    79  		return tagIsSet(x.Name), nil
    80  
    81  	case *ast.ParenExpr:
    82  		return shouldInclude(x.X, tagIsSet)
    83  
    84  	case *ast.BinaryExpr:
    85  		switch x.Op {
    86  		case token.LAND, token.LOR:
    87  			a, err := shouldInclude(x.X, tagIsSet)
    88  			if err != nil {
    89  				return false, err
    90  			}
    91  			b, err := shouldInclude(x.Y, tagIsSet)
    92  			if err != nil {
    93  				return false, err
    94  			}
    95  			if x.Op == token.LAND {
    96  				return a && b, nil
    97  			}
    98  			return a || b, nil
    99  
   100  		default:
   101  			return false, errors.Newf(token.NoPos, "invalid operator %v in build attribute", x.Op)
   102  		}
   103  
   104  	case *ast.UnaryExpr:
   105  		if x.Op != token.NOT {
   106  			return false, errors.Newf(token.NoPos, "invalid operator %v in build attribute", x.Op)
   107  		}
   108  		v, err := shouldInclude(x.X, tagIsSet)
   109  		if err != nil {
   110  			return false, err
   111  		}
   112  		return !v, nil
   113  
   114  	default:
   115  		return false, errors.Newf(token.NoPos, "invalid type %T in build attribute", expr)
   116  	}
   117  }