github.com/cockroachdb/tools@v0.0.0-20230222021103-a6d27438930d/go/loader/util.go (about) 1 // Copyright 2013 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package loader 6 7 import ( 8 "go/ast" 9 "go/build" 10 "go/parser" 11 "go/token" 12 "io" 13 "os" 14 "strconv" 15 "sync" 16 17 "golang.org/x/tools/go/buildutil" 18 ) 19 20 // We use a counting semaphore to limit 21 // the number of parallel I/O calls per process. 22 var ioLimit = make(chan bool, 10) 23 24 // parseFiles parses the Go source files within directory dir and 25 // returns the ASTs of the ones that could be at least partially parsed, 26 // along with a list of I/O and parse errors encountered. 27 // 28 // I/O is done via ctxt, which may specify a virtual file system. 29 // displayPath is used to transform the filenames attached to the ASTs. 30 func parseFiles(fset *token.FileSet, ctxt *build.Context, displayPath func(string) string, dir string, files []string, mode parser.Mode) ([]*ast.File, []error) { 31 if displayPath == nil { 32 displayPath = func(path string) string { return path } 33 } 34 var wg sync.WaitGroup 35 n := len(files) 36 parsed := make([]*ast.File, n) 37 errors := make([]error, n) 38 for i, file := range files { 39 if !buildutil.IsAbsPath(ctxt, file) { 40 file = buildutil.JoinPath(ctxt, dir, file) 41 } 42 wg.Add(1) 43 go func(i int, file string) { 44 ioLimit <- true // wait 45 defer func() { 46 wg.Done() 47 <-ioLimit // signal 48 }() 49 var rd io.ReadCloser 50 var err error 51 if ctxt.OpenFile != nil { 52 rd, err = ctxt.OpenFile(file) 53 } else { 54 rd, err = os.Open(file) 55 } 56 if err != nil { 57 errors[i] = err // open failed 58 return 59 } 60 61 // ParseFile may return both an AST and an error. 62 parsed[i], errors[i] = parser.ParseFile(fset, displayPath(file), rd, mode) 63 rd.Close() 64 }(i, file) 65 } 66 wg.Wait() 67 68 // Eliminate nils, preserving order. 69 var o int 70 for _, f := range parsed { 71 if f != nil { 72 parsed[o] = f 73 o++ 74 } 75 } 76 parsed = parsed[:o] 77 78 o = 0 79 for _, err := range errors { 80 if err != nil { 81 errors[o] = err 82 o++ 83 } 84 } 85 errors = errors[:o] 86 87 return parsed, errors 88 } 89 90 // scanImports returns the set of all import paths from all 91 // import specs in the specified files. 92 func scanImports(files []*ast.File) map[string]bool { 93 imports := make(map[string]bool) 94 for _, f := range files { 95 for _, decl := range f.Decls { 96 if decl, ok := decl.(*ast.GenDecl); ok && decl.Tok == token.IMPORT { 97 for _, spec := range decl.Specs { 98 spec := spec.(*ast.ImportSpec) 99 100 // NB: do not assume the program is well-formed! 101 path, err := strconv.Unquote(spec.Path.Value) 102 if err != nil { 103 continue // quietly ignore the error 104 } 105 if path == "C" { 106 continue // skip pseudopackage 107 } 108 imports[path] = true 109 } 110 } 111 } 112 } 113 return imports 114 } 115 116 // ---------- Internal helpers ---------- 117 118 // TODO(adonovan): make this a method: func (*token.File) Contains(token.Pos) 119 func tokenFileContainsPos(f *token.File, pos token.Pos) bool { 120 p := int(pos) 121 base := f.Base() 122 return base <= p && p < base+f.Size() 123 }