gvisor.dev/gvisor@v0.0.0-20240520182842-f9d4d51c7e0f/tools/github/reviver/reviver.go (about) 1 // Copyright 2019 The gVisor Authors. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 // Package reviver scans the code looking for TODOs and pass them to registered 16 // Buggers to ensure TODOs point to active issues. 17 package reviver 18 19 import ( 20 "bufio" 21 "fmt" 22 "io/ioutil" 23 "os" 24 "path/filepath" 25 "regexp" 26 "sync" 27 ) 28 29 // regexTodo matches a TODO or FIXME comment. 30 var regexTodo = regexp.MustCompile(`(\/\/|#)\s*(TODO|FIXME)\(([a-zA-Z0-9.\/]+)\):\s*(.+)`) 31 32 // Bugger interface is called for every TODO found in the code. If it can handle 33 // the TODO, it must return true. If it returns false, the next Bugger is 34 // called. If no Bugger handles the TODO, it's dropped on the floor. 35 type Bugger interface { 36 Activate(todo *Todo) (bool, error) 37 } 38 39 // Location saves the location where the TODO was found. 40 type Location struct { 41 Comment string 42 File string 43 Line uint 44 } 45 46 // Todo represents a unique TODO. There can be several TODOs pointing to the 47 // same issue in the code. They are all grouped together. 48 type Todo struct { 49 Issue string 50 Locations []Location 51 } 52 53 // Reviver scans the given paths for TODOs and calls Buggers to handle them. 54 type Reviver struct { 55 paths []string 56 buggers []Bugger 57 58 mu sync.Mutex 59 todos map[string]*Todo 60 errs []error 61 } 62 63 // New create a new Reviver. 64 func New(paths []string, buggers []Bugger) *Reviver { 65 return &Reviver{ 66 paths: paths, 67 buggers: buggers, 68 todos: map[string]*Todo{}, 69 } 70 } 71 72 // Run runs. It returns all errors found during processing, it doesn't stop 73 // on errors. 74 func (r *Reviver) Run() []error { 75 // Process each directory in parallel. 76 wg := sync.WaitGroup{} 77 for _, path := range r.paths { 78 wg.Add(1) 79 go func(path string) { 80 defer wg.Done() 81 r.processPath(path, &wg) 82 }(path) 83 } 84 85 wg.Wait() 86 87 r.mu.Lock() 88 defer r.mu.Unlock() 89 90 fmt.Printf("Processing %d TODOs (%d errors)...\n", len(r.todos), len(r.errs)) 91 dropped := 0 92 for _, todo := range r.todos { 93 ok, err := r.processTodo(todo) 94 if err != nil { 95 r.errs = append(r.errs, err) 96 } 97 if !ok { 98 dropped++ 99 } 100 } 101 fmt.Printf("Processed %d TODOs, %d were skipped (%d errors)\n", len(r.todos)-dropped, dropped, len(r.errs)) 102 103 return r.errs 104 } 105 106 func (r *Reviver) processPath(path string, wg *sync.WaitGroup) { 107 fmt.Printf("Processing dir %q\n", path) 108 fis, err := ioutil.ReadDir(path) 109 if err != nil { 110 r.addErr(fmt.Errorf("error processing dir %q: %v", path, err)) 111 return 112 } 113 114 for _, fi := range fis { 115 childPath := filepath.Join(path, fi.Name()) 116 switch { 117 case fi.Mode().IsDir(): 118 wg.Add(1) 119 go func() { 120 defer wg.Done() 121 r.processPath(childPath, wg) 122 }() 123 124 case fi.Mode().IsRegular(): 125 file, err := os.Open(childPath) 126 if err != nil { 127 r.addErr(err) 128 continue 129 } 130 131 scanner := bufio.NewScanner(file) 132 lineno := uint(0) 133 for scanner.Scan() { 134 lineno++ 135 line := scanner.Text() 136 if todo := r.processLine(line, childPath, lineno); todo != nil { 137 r.addTodo(todo) 138 } 139 } 140 } 141 } 142 } 143 144 func (r *Reviver) processLine(line, path string, lineno uint) *Todo { 145 matches := regexTodo.FindStringSubmatch(line) 146 if matches == nil { 147 return nil 148 } 149 if len(matches) != 5 { 150 panic(fmt.Sprintf("regex returned wrong matches for %q: %v", line, matches)) 151 } 152 return &Todo{ 153 Issue: matches[3], 154 Locations: []Location{ 155 { 156 File: path, 157 Line: lineno, 158 Comment: matches[4], 159 }, 160 }, 161 } 162 } 163 164 func (r *Reviver) addTodo(newTodo *Todo) { 165 r.mu.Lock() 166 defer r.mu.Unlock() 167 168 if todo := r.todos[newTodo.Issue]; todo == nil { 169 r.todos[newTodo.Issue] = newTodo 170 } else { 171 todo.Locations = append(todo.Locations, newTodo.Locations...) 172 } 173 } 174 175 func (r *Reviver) addErr(err error) { 176 r.mu.Lock() 177 defer r.mu.Unlock() 178 r.errs = append(r.errs, err) 179 } 180 181 func (r *Reviver) processTodo(todo *Todo) (bool, error) { 182 for _, bugger := range r.buggers { 183 ok, err := bugger.Activate(todo) 184 if err != nil { 185 return false, err 186 } 187 if ok { 188 return true, nil 189 } 190 } 191 return false, nil 192 }