| 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 | } |
| 124 |
Members