11package analyzer
22
33import (
4+ "fmt"
45 "go/ast"
56 "go/types"
67
@@ -19,58 +20,140 @@ var Analyzer = &analysis.Analyzer{
1920func run (pass * analysis.Pass ) (interface {}, error ) {
2021 inspector := pass .ResultOf [inspect .Analyzer ].(* inspector.Inspector )
2122
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+ }
2327
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+ }
2634
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+ }
3043
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+ }
3848 }
3949
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 )
4353 }
44- }
4554
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 " )
6271 }
6372
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+ }
67109 }
68110 }
111+
112+ // Continue with the children
113+ return true
69114 })
70115
71116 return nil , nil
72117}
73118
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+
74157func isWorkflow (funcDecl * ast.FuncDecl ) bool {
75158 params := funcDecl .Type .Params .List
76159
0 commit comments