blob: 3a80acae64ec301878567ff6776578491f098cea [file] [log] [blame]
Alan Donovan713699d2013-08-27 18:49:13 -04001// 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
Alan Donovan3fc0fc12014-01-16 09:33:58 -05005package loader
Alan Donovanbe28dbb2013-05-31 16:14:13 -04006
Alan Donovanbe28dbb2013-05-31 16:14:13 -04007import (
Alan Donovanbe28dbb2013-05-31 16:14:13 -04008 "go/ast"
9 "go/build"
10 "go/parser"
11 "go/token"
Alan Donovane3dc58c2014-03-14 16:17:53 -040012 "io"
13 "os"
Alan Donovan9c9660e2014-12-15 13:20:21 -050014 "strconv"
Alan Donovane2921e12013-09-04 13:15:49 -040015 "sync"
Alan Donovan0dda50d2015-01-30 13:30:23 -050016
17 "golang.org/x/tools/go/buildutil"
Alan Donovanbe28dbb2013-05-31 16:14:13 -040018)
19
Alan Donovan8a9ac1b2015-04-02 13:05:03 -040020// We use a counting semaphore to limit
21// the number of parallel I/O calls per process.
Russ Cox93604a32015-08-18 13:24:30 -040022var ioLimit = make(chan bool, 10)
Alan Donovan8a9ac1b2015-04-02 13:05:03 -040023
Alan Donovanf0ff5112014-06-13 11:32:46 -040024// 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.
Alan Donovanbe28dbb2013-05-31 16:14:13 -040027//
Alan Donovane3dc58c2014-03-14 16:17:53 -040028// 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.
Alan Donovanf0ff5112014-06-13 11:32:46 -040030func parseFiles(fset *token.FileSet, ctxt *build.Context, displayPath func(string) string, dir string, files []string, mode parser.Mode) ([]*ast.File, []error) {
Alan Donovane3dc58c2014-03-14 16:17:53 -040031 if displayPath == nil {
32 displayPath = func(path string) string { return path }
33 }
Alan Donovane2921e12013-09-04 13:15:49 -040034 var wg sync.WaitGroup
35 n := len(files)
Alan Donovane3dc58c2014-03-14 16:17:53 -040036 parsed := make([]*ast.File, n)
37 errors := make([]error, n)
Alan Donovane2921e12013-09-04 13:15:49 -040038 for i, file := range files {
Alan Donovan0dda50d2015-01-30 13:30:23 -050039 if !buildutil.IsAbsPath(ctxt, file) {
40 file = buildutil.JoinPath(ctxt, dir, file)
Alan Donovanbe28dbb2013-05-31 16:14:13 -040041 }
Alan Donovane2921e12013-09-04 13:15:49 -040042 wg.Add(1)
43 go func(i int, file string) {
Russ Cox93604a32015-08-18 13:24:30 -040044 ioLimit <- true // wait
Alan Donovan8a9ac1b2015-04-02 13:05:03 -040045 defer func() {
46 wg.Done()
Russ Cox93604a32015-08-18 13:24:30 -040047 <-ioLimit // signal
Alan Donovan8a9ac1b2015-04-02 13:05:03 -040048 }()
Alan Donovane3dc58c2014-03-14 16:17:53 -040049 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 }
Alan Donovane3dc58c2014-03-14 16:17:53 -040056 if err != nil {
Alan Donovanf0ff5112014-06-13 11:32:46 -040057 errors[i] = err // open failed
Alan Donovane3dc58c2014-03-14 16:17:53 -040058 return
59 }
Alan Donovanf0ff5112014-06-13 11:32:46 -040060
61 // ParseFile may return both an AST and an error.
Alan Donovan24146772014-03-27 12:50:26 -040062 parsed[i], errors[i] = parser.ParseFile(fset, displayPath(file), rd, mode)
Alan Donovanf0ff5112014-06-13 11:32:46 -040063 rd.Close()
Alan Donovane2921e12013-09-04 13:15:49 -040064 }(i, file)
Alan Donovanbe28dbb2013-05-31 16:14:13 -040065 }
Alan Donovane2921e12013-09-04 13:15:49 -040066 wg.Wait()
67
Alan Donovanf0ff5112014-06-13 11:32:46 -040068 // Eliminate nils, preserving order.
69 var o int
70 for _, f := range parsed {
71 if f != nil {
72 parsed[o] = f
73 o++
Alan Donovane2921e12013-09-04 13:15:49 -040074 }
75 }
Alan Donovanf0ff5112014-06-13 11:32:46 -040076 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
Alan Donovanbe28dbb2013-05-31 16:14:13 -040088}
89
Alan Donovand6e83e52015-12-18 21:02:28 -050090// scanImports returns the set of all import paths from all
Alan Donovan9c9660e2014-12-15 13:20:21 -050091// import specs in the specified files.
92func 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 }
Alan Donovanae186f52016-01-08 14:46:14 -0500105 if path == "C" {
106 continue // skip pseudopackage
Alan Donovan9c9660e2014-12-15 13:20:21 -0500107 }
108 imports[path] = true
109 }
110 }
111 }
112 }
113 return imports
114}
115
Alan Donovanbe28dbb2013-05-31 16:14:13 -0400116// ---------- Internal helpers ----------
117
Alan Donovane8afbfa2014-01-15 21:37:55 -0500118// TODO(adonovan): make this a method: func (*token.File) Contains(token.Pos)
119func 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}