file.go 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229
  1. // Package file encapsulates the file abstractions used by the ast & parser.
  2. //
  3. package file
  4. import (
  5. "fmt"
  6. "net/url"
  7. "path"
  8. "sort"
  9. "sync"
  10. "github.com/go-sourcemap/sourcemap"
  11. )
  12. // Idx is a compact encoding of a source position within a file set.
  13. // It can be converted into a Position for a more convenient, but much
  14. // larger, representation.
  15. type Idx int
  16. // Position describes an arbitrary source position
  17. // including the filename, line, and column location.
  18. type Position struct {
  19. Filename string // The filename where the error occurred, if any
  20. Line int // The line number, starting at 1
  21. Column int // The column number, starting at 1 (The character count)
  22. }
  23. // A Position is valid if the line number is > 0.
  24. func (self *Position) isValid() bool {
  25. return self.Line > 0
  26. }
  27. // String returns a string in one of several forms:
  28. //
  29. // file:line:column A valid position with filename
  30. // line:column A valid position without filename
  31. // file An invalid position with filename
  32. // - An invalid position without filename
  33. //
  34. func (self Position) String() string {
  35. str := self.Filename
  36. if self.isValid() {
  37. if str != "" {
  38. str += ":"
  39. }
  40. str += fmt.Sprintf("%d:%d", self.Line, self.Column)
  41. }
  42. if str == "" {
  43. str = "-"
  44. }
  45. return str
  46. }
  47. // FileSet
  48. // A FileSet represents a set of source files.
  49. type FileSet struct {
  50. files []*File
  51. last *File
  52. }
  53. // AddFile adds a new file with the given filename and src.
  54. //
  55. // This an internal method, but exported for cross-package use.
  56. func (self *FileSet) AddFile(filename, src string) int {
  57. base := self.nextBase()
  58. file := &File{
  59. name: filename,
  60. src: src,
  61. base: base,
  62. }
  63. self.files = append(self.files, file)
  64. self.last = file
  65. return base
  66. }
  67. func (self *FileSet) nextBase() int {
  68. if self.last == nil {
  69. return 1
  70. }
  71. return self.last.base + len(self.last.src) + 1
  72. }
  73. func (self *FileSet) File(idx Idx) *File {
  74. for _, file := range self.files {
  75. if idx <= Idx(file.base+len(file.src)) {
  76. return file
  77. }
  78. }
  79. return nil
  80. }
  81. // Position converts an Idx in the FileSet into a Position.
  82. func (self *FileSet) Position(idx Idx) Position {
  83. for _, file := range self.files {
  84. if idx <= Idx(file.base+len(file.src)) {
  85. return file.Position(int(idx) - file.base)
  86. }
  87. }
  88. return Position{}
  89. }
  90. type File struct {
  91. mu sync.Mutex
  92. name string
  93. src string
  94. base int // This will always be 1 or greater
  95. sourceMap *sourcemap.Consumer
  96. lineOffsets []int
  97. lastScannedOffset int
  98. }
  99. func NewFile(filename, src string, base int) *File {
  100. return &File{
  101. name: filename,
  102. src: src,
  103. base: base,
  104. }
  105. }
  106. func (fl *File) Name() string {
  107. return fl.name
  108. }
  109. func (fl *File) Source() string {
  110. return fl.src
  111. }
  112. func (fl *File) Base() int {
  113. return fl.base
  114. }
  115. func (fl *File) SetSourceMap(m *sourcemap.Consumer) {
  116. fl.sourceMap = m
  117. }
  118. func (fl *File) Position(offset int) Position {
  119. var line int
  120. var lineOffsets []int
  121. fl.mu.Lock()
  122. if offset > fl.lastScannedOffset {
  123. line = fl.scanTo(offset)
  124. lineOffsets = fl.lineOffsets
  125. fl.mu.Unlock()
  126. } else {
  127. lineOffsets = fl.lineOffsets
  128. fl.mu.Unlock()
  129. line = sort.Search(len(lineOffsets), func(x int) bool { return lineOffsets[x] > offset }) - 1
  130. }
  131. var lineStart int
  132. if line >= 0 {
  133. lineStart = lineOffsets[line]
  134. }
  135. row := line + 2
  136. col := offset - lineStart + 1
  137. if fl.sourceMap != nil {
  138. if source, _, row, col, ok := fl.sourceMap.Source(row, col); ok {
  139. return Position{
  140. Filename: ResolveSourcemapURL(fl.Name(), source).String(),
  141. Line: row,
  142. Column: col,
  143. }
  144. }
  145. }
  146. return Position{
  147. Filename: fl.name,
  148. Line: row,
  149. Column: col,
  150. }
  151. }
  152. func ResolveSourcemapURL(basename, source string) *url.URL {
  153. // if the url is absolute(has scheme) there is nothing to do
  154. smURL, err := url.Parse(source)
  155. if err == nil && !smURL.IsAbs() {
  156. baseURL, err1 := url.Parse(basename)
  157. if err1 == nil && path.IsAbs(baseURL.Path) {
  158. smURL = baseURL.ResolveReference(smURL)
  159. } else {
  160. // pathological case where both are not absolute paths and using Resolve as above will produce an absolute
  161. // one
  162. smURL, _ = url.Parse(path.Join(path.Dir(basename), smURL.Path))
  163. }
  164. }
  165. return smURL
  166. }
  167. func findNextLineStart(s string) int {
  168. for pos, ch := range s {
  169. switch ch {
  170. case '\r':
  171. if pos < len(s)-1 && s[pos+1] == '\n' {
  172. return pos + 2
  173. }
  174. return pos + 1
  175. case '\n':
  176. return pos + 1
  177. case '\u2028', '\u2029':
  178. return pos + 3
  179. }
  180. }
  181. return -1
  182. }
  183. func (fl *File) scanTo(offset int) int {
  184. o := fl.lastScannedOffset
  185. for o < offset {
  186. p := findNextLineStart(fl.src[o:])
  187. if p == -1 {
  188. fl.lastScannedOffset = len(fl.src)
  189. return len(fl.lineOffsets) - 1
  190. }
  191. o = o + p
  192. fl.lineOffsets = append(fl.lineOffsets, o)
  193. }
  194. fl.lastScannedOffset = o
  195. if o == offset {
  196. return len(fl.lineOffsets) - 1
  197. }
  198. return len(fl.lineOffsets) - 2
  199. }