1
1
package analyzer
2
2
3
3
import (
4
+ "fmt"
4
5
"go/ast"
5
6
"go/types"
6
7
@@ -19,58 +20,140 @@ var Analyzer = &analysis.Analyzer{
19
20
func run (pass * analysis.Pass ) (interface {}, error ) {
20
21
inspector := pass .ResultOf [inspect .Analyzer ].(* inspector.Inspector )
21
22
22
- nodeFilter := []ast.Node {(* ast .FuncDecl )(nil )}
23
+ inspector .Nodes (nil , func (node ast.Node , push bool ) bool {
24
+ if ! push {
25
+ return false
26
+ }
23
27
24
- inspector .Preorder (nodeFilter , func (node ast.Node ) {
25
- funcDecl := node .(* ast.FuncDecl )
28
+ switch n := node .(type ) {
29
+ case * ast.FuncDecl :
30
+ // Only check functions that look like workflows
31
+ if ! isWorkflow (n ) {
32
+ return false
33
+ }
26
34
27
- if ! isWorkflow (funcDecl ) {
28
- return
29
- }
35
+ // Check return types
36
+ if n .Type .Results == nil || len (n .Type .Results .List ) == 0 {
37
+ pass .Reportf (n .Pos (), "workflow `%v` doesn't return anything. needs to return at least `error`" , n .Name .Name )
38
+ } else {
39
+ if len (n .Type .Results .List ) > 2 {
40
+ pass .Reportf (n .Pos (), "workflow `%v` returns more than two values" , n .Name .Name )
41
+ return true
42
+ }
30
43
31
- // Check return types
32
- if funcDecl .Type .Results == nil || len (funcDecl .Type .Results .List ) == 0 {
33
- pass .Reportf (funcDecl .Pos (), "workflow %q doesn't return anything. needs to return at least `error`" , funcDecl .Name .Name )
34
- } else {
35
- if len (funcDecl .Type .Results .List ) > 2 {
36
- pass .Reportf (funcDecl .Pos (), "workflow %q returns more than two values" , funcDecl .Name .Name )
37
- return
44
+ lastResult := n .Type .Results .List [len (n .Type .Results .List )- 1 ]
45
+ if types .ExprString (lastResult .Type ) != "error" {
46
+ pass .Reportf (n .Pos (), "workflow `%v` doesn't return `error` as last return value" , n .Name .Name )
47
+ }
38
48
}
39
49
40
- lastResult := funcDecl . Type . Results . List [ len ( funcDecl .Type . Results . List ) - 1 ]
41
- if types . ExprString ( lastResult . Type ) != "error" {
42
- pass . Reportf ( funcDecl . Pos (), "workflow %q doesn't return `error` as last return value" , funcDecl . Name . Name )
50
+ funcScope := pass . TypesInfo . Scopes [ n .Type ]
51
+ if funcScope != nil {
52
+ checkVarsInScope ( pass , funcScope )
43
53
}
44
- }
45
54
46
- // Check for various errors in the workflow body
47
- for _ , stmt := range funcDecl . Body . List {
48
- switch stmt := stmt .( type ) {
49
- // Check for map iterations
50
- case * ast. RangeStmt :
51
- {
52
- t := pass . TypesInfo . TypeOf ( stmt . X )
53
- if t == nil {
54
- continue
55
- }
56
-
57
- if _ , ok := t .( * types.Map ); ! ok {
58
- continue
59
- }
60
-
61
- pass .Reportf (stmt .Pos (), "iterating over a map is not deterministic and not allowed in workflows" )
55
+ // Continue with the function's children
56
+ return true
57
+
58
+ case * ast. RangeStmt :
59
+ {
60
+ t := pass . TypesInfo . TypeOf ( n . X )
61
+ if t == nil {
62
+ break
63
+ }
64
+
65
+ switch t .( type ) {
66
+ case * types.Map :
67
+ pass . Reportf ( n . Pos (), "iterating over a `map` is not deterministic and not allowed in workflows" )
68
+
69
+ case * types. Chan :
70
+ pass .Reportf (n .Pos (), "using native channels is not allowed in workflows, use `workflow.Channel` instead " )
62
71
}
63
72
64
- // Check for `go` statements
65
- case * ast.GoStmt :
66
- pass .Reportf (stmt .Pos (), "use workflow.Go instead of `go` in workflows" )
73
+ // checkStatements(pass, n.Body.List)
74
+ }
75
+
76
+ case * ast.SelectStmt :
77
+ pass .Reportf (n .Pos (), "`select` statements are not allowed in workflows, use `workflow.Select` instead" )
78
+
79
+ case * ast.GoStmt :
80
+ pass .Reportf (n .Pos (), "use `workflow.Go` instead of `go` in workflows" )
81
+
82
+ case * ast.CallExpr :
83
+ var pkg * ast.Ident
84
+ var id * ast.Ident
85
+ switch fun := n .Fun .(type ) {
86
+ case * ast.SelectorExpr :
87
+ pkg , _ = fun .X .(* ast.Ident )
88
+ id = fun .Sel
89
+ }
90
+
91
+ if pkg == nil || id == nil {
92
+ break
93
+ }
94
+
95
+ pkgInfo := pass .TypesInfo .Uses [pkg ]
96
+ pkgName , _ := pkgInfo .(* types.PkgName )
97
+ if pkgName == nil {
98
+ break
99
+ }
100
+
101
+ switch pkgName .Imported ().Path () {
102
+ case "time" :
103
+ switch id .Name {
104
+ case "Now" :
105
+ pass .Reportf (n .Pos (), "`time.Now` is not allowed in workflows, use `workflow.Now` instead" )
106
+ case "Sleep" :
107
+ pass .Reportf (n .Pos (), "`time.Sleep` is not allowed in workflows, use `workflow.Sleep` instead" )
108
+ }
67
109
}
68
110
}
111
+
112
+ // Continue with the children
113
+ return true
69
114
})
70
115
71
116
return nil , nil
72
117
}
73
118
119
+ func checkVarsInScope (pass * analysis.Pass , scope * types.Scope ) {
120
+ for _ , name := range scope .Names () {
121
+ obj := scope .Lookup (name )
122
+ switch t := obj .Type ().(type ) {
123
+ case * types.Chan :
124
+ pass .Reportf (obj .Pos (), "using native channels is not allowed in workflows, use `workflow.Channel` instead" )
125
+
126
+ case * types.Named :
127
+ checkNamed (pass , obj , t )
128
+
129
+ case * types.Pointer :
130
+ if named , ok := t .Elem ().(* types.Named ); ok {
131
+ checkNamed (pass , obj , named )
132
+ }
133
+ }
134
+ }
135
+
136
+ for i := 0 ; i < scope .NumChildren (); i ++ {
137
+ checkVarsInScope (pass , scope .Child (i ))
138
+ }
139
+ }
140
+
141
+ func checkNamed (pass * analysis.Pass , ref types.Object , named * types.Named ) {
142
+ if obj := named .Obj (); obj != nil {
143
+ if pkg := obj .Pkg (); pkg != nil {
144
+ fmt .Println (pkg .Path (), obj .Name (), obj .Id ())
145
+
146
+ switch pkg .Path () {
147
+ case "sync" :
148
+ if obj .Name () == "WaitGroup" {
149
+ pass .Reportf (ref .Pos (), "using `sync.WaitGroup` is not allowed in workflows, use `workflow.WaitGroup` instead" )
150
+ }
151
+ }
152
+ }
153
+ }
154
+
155
+ }
156
+
74
157
func isWorkflow (funcDecl * ast.FuncDecl ) bool {
75
158
params := funcDecl .Type .Params .List
76
159
0 commit comments