github.com/powerman/golang-tools@v0.1.11-0.20220410185822-5ad214d8d803/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 "github.com/powerman/golang-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 // 31 func parseFiles(fset *token.FileSet, ctxt *build.Context, displayPath func(string) string, dir string, files []string, mode parser.Mode) ([]*ast.File, []error) { 32 if displayPath == nil { 33 displayPath = func(path string) string { return path } 34 } 35 var wg sync.WaitGroup 36 n := len(files) 37 parsed := make([]*ast.File, n) 38 errors := make([]error, n) 39 for i, file := range files { 40 if !buildutil.IsAbsPath(ctxt, file) { 41 file = buildutil.JoinPath(ctxt, dir, file) 42 } 43 wg.Add(1) 44 go func(i int, file string) { 45 ioLimit <- true // wait 46 defer func() { 47 wg.Done() 48 <-ioLimit // signal 49 }() 50 var rd io.ReadCloser 51 var err error 52 if ctxt.OpenFile != nil { 53 rd, err = ctxt.OpenFile(file) 54 } else { 55 rd, err = os.Open(file) 56 } 57 if err != nil { 58 errors[i] = err // open failed 59 return 60 } 61 62 // ParseFile may return both an AST and an error. 63 parsed[i], errors[i] = parser.ParseFile(fset, displayPath(file), rd, mode) 64 rd.Close() 65 }(i, file) 66 } 67 wg.Wait() 68 69 // Eliminate nils, preserving order. 70 var o int 71 for _, f := range parsed { 72 if f != nil { 73 parsed[o] = f 74 o++ 75 } 76 } 77 parsed = parsed[:o] 78 79 o = 0 80 for _, err := range errors { 81 if err != nil { 82 errors[o] = err 83 o++ 84 } 85 } 86 errors = errors[:o] 87 88 return parsed, errors 89 } 90 91 // scanImports returns the set of all import paths from all 92 // import specs in the specified files. 93 func scanImports(files []*ast.File) map[string]bool { 94 imports := make(map[string]bool) 95 for _, f := range files { 96 for _, decl := range f.Decls { 97 if decl, ok := decl.(*ast.GenDecl); ok && decl.Tok == token.IMPORT { 98 for _, spec := range decl.Specs { 99 spec := spec.(*ast.ImportSpec) 100 101 // NB: do not assume the program is well-formed! 102 path, err := strconv.Unquote(spec.Path.Value) 103 if err != nil { 104 continue // quietly ignore the error 105 } 106 if path == "C" { 107 continue // skip pseudopackage 108 } 109 imports[path] = true 110 } 111 } 112 } 113 } 114 return imports 115 } 116 117 // ---------- Internal helpers ---------- 118 119 // TODO(adonovan): make this a method: func (*token.File) Contains(token.Pos) 120 func tokenFileContainsPos(f *token.File, pos token.Pos) bool { 121 p := int(pos) 122 base := f.Base() 123 return base <= p && p < base+f.Size() 124 }