File: buildattr.go

package info (click to toggle)
golang-github-cue-lang-cue 0.12.0.-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 19,072 kB
  • sloc: sh: 57; makefile: 17
file content (117 lines) | stat: -rw-r--r-- 2,964 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
// Package buildattr implements support for interpreting the @if
// build attributes in CUE files.
package buildattr

import (
	"cuelang.org/go/cue/ast"
	"cuelang.org/go/cue/errors"
	"cuelang.org/go/cue/parser"
	"cuelang.org/go/cue/token"
)

// ShouldIgnoreFile reports whether a File contains an @ignore() file-level
// attribute and hence should be ignored.
func ShouldIgnoreFile(f *ast.File) bool {
	ignore, _, _ := getBuildAttr(f)
	return ignore
}

// ShouldBuildFile reports whether a File should be included based on its
// attributes. It uses tagIsSet to determine whether a given attribute
// key should be treated as set.
//
// It also returns the build attribute if one was found.
func ShouldBuildFile(f *ast.File, tagIsSet func(key string) bool) (bool, *ast.Attribute, errors.Error) {
	ignore, a, err := getBuildAttr(f)
	if ignore || err != nil {
		return false, a, err
	}
	if a == nil {
		return true, nil, nil
	}

	_, body := a.Split()

	expr, parseErr := parser.ParseExpr("", body)
	if parseErr != nil {
		return false, a, errors.Promote(parseErr, "")
	}

	include, err := shouldInclude(expr, tagIsSet)
	if err != nil {
		return false, a, err
	}
	return include, a, nil
}

func getBuildAttr(f *ast.File) (ignore bool, a *ast.Attribute, err errors.Error) {
	for _, d := range f.Decls {
		switch x := d.(type) {
		case *ast.Attribute:
			switch key, _ := x.Split(); key {
			case "ignore":
				return true, x, nil
			case "if":
				if a != nil {
					err := errors.Newf(d.Pos(), "multiple @if attributes")
					err = errors.Append(err,
						errors.Newf(a.Pos(), "previous declaration here"))
					return false, a, err
				}
				a = x
			}
		case *ast.Package:
			return false, a, nil
		case *ast.CommentGroup:
		default:
			// If it's anything else, then we know we won't see a package
			// clause so avoid scanning more than we need to (this
			// could be a large file with no package clause)
			return false, a, nil
		}
	}
	return false, a, nil
}

func shouldInclude(expr ast.Expr, tagIsSet func(key string) bool) (bool, errors.Error) {
	switch x := expr.(type) {
	case *ast.Ident:
		return tagIsSet(x.Name), nil

	case *ast.ParenExpr:
		return shouldInclude(x.X, tagIsSet)

	case *ast.BinaryExpr:
		switch x.Op {
		case token.LAND, token.LOR:
			a, err := shouldInclude(x.X, tagIsSet)
			if err != nil {
				return false, err
			}
			b, err := shouldInclude(x.Y, tagIsSet)
			if err != nil {
				return false, err
			}
			if x.Op == token.LAND {
				return a && b, nil
			}
			return a || b, nil

		default:
			return false, errors.Newf(token.NoPos, "invalid operator %v in build attribute", x.Op)
		}

	case *ast.UnaryExpr:
		if x.Op != token.NOT {
			return false, errors.Newf(token.NoPos, "invalid operator %v in build attribute", x.Op)
		}
		v, err := shouldInclude(x.X, tagIsSet)
		if err != nil {
			return false, err
		}
		return !v, nil

	default:
		return false, errors.Newf(token.NoPos, "invalid type %T in build attribute", expr)
	}
}