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