You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

124 lines
2.9 KiB

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