file.go 5.0 KB

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