-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathrefreturn.go
154 lines (130 loc) · 3.38 KB
/
refreturn.go
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
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
package main
import (
"fmt"
"go/ast"
"go/parser"
"go/token"
"os"
"path/filepath"
"strings"
"sync"
)
const (
numWorkers int = 4
fileExtension string = ".go"
)
// Run executes refreturn. It is in charge of spawning all worker
// routines, sending the files to be processed through a channel
// and gracefully stopping all workers.
//
// At the moment, the results are not queued but printed directly
// by the workers instead. This may change in the future.
func Run(dir string) error {
var wg sync.WaitGroup
jobQueue := make(chan string)
for i := 0; i < numWorkers; i++ {
wg.Add(1)
go func() {
worker := &Worker{}
_ = worker.readFromQueue(jobQueue, &wg)
}()
}
if err := sendFiles(dir, jobQueue); err != nil {
return err
}
close(jobQueue)
wg.Wait()
return nil
}
// sendFiles sends all files matching the configured file extension
// through the jobQueue channel.
func sendFiles(dir string, jobQueue chan<- string) error {
return filepath.Walk(dir, func(path string, _ os.FileInfo, err error) error {
if err != nil {
return err
}
if strings.HasSuffix(path, fileExtension) {
jobQueue <- path
}
return nil
})
}
type Worker struct{}
// readFromQueue pops off items from the job queue sequentially.
// Each queue item will be passed to findAllocationsInFile.
func (w *Worker) readFromQueue(jobs <-chan string, wg *sync.WaitGroup) error {
for path := range jobs {
if err := w.findAllocationsInFile(path); err != nil {
return err
}
}
wg.Done()
return nil
}
// findAllocationsInFile parses a source code file and walks through
// its syntax tree. In doing so, a custom node visitor will check if
// a node is a function that returns a pointer. All matching nodes
// will be sent to a dedicated channel.
func (w *Worker) findAllocationsInFile(path string) error {
fileSet := token.NewFileSet()
file, err := parser.ParseFile(fileSet, path, nil, parser.AllErrors)
if err != nil {
return nil
}
visitor := Visitor{
matches: make(chan Node),
}
go func() {
ast.Walk(visitor, file)
close(visitor.matches)
}()
// Iterate over all matches and print the file information for
// each match. This should be outsourced to an own component.
for match := range visitor.matches {
pos := fileSet.PositionFor(match.Position, false)
fn := match.Identifier.Name
fmt.Printf("%s: %s\n", pos, fn)
}
return nil
}
// Visitor satisfies the ast.Visitor interface and is used by for
// inspecting every AST node using ast.Walk().
type Visitor struct {
matches chan Node
filter func(node ast.Node) bool
}
// Node represents an AST node with an identifier.
type Node struct {
Position token.Pos
Identifier *ast.Ident
}
// Visit checks the type a given AST node `n`. If the node is a
// function declaration, a return type check is performed.
func (v Visitor) Visit(node ast.Node) ast.Visitor {
if node == nil {
return nil
}
switch decl := node.(type) {
case *ast.FuncDecl:
if containsReference(decl.Type.Results) {
v.matches <- Node{
Position: decl.Pos(),
Identifier: decl.Name,
}
}
}
return v
}
// containsReference determines if one of a function's return types
// is a reference. Each return type is an entry in the FieldList.
func containsReference(fieldList *ast.FieldList) bool {
if fieldList == nil {
return false
}
for _, f := range fieldList.List {
if _, ok := f.Type.(*ast.StarExpr); ok {
return true
}
}
return false
}