util.go 2.9 KB

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