util.go raw

   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