|
@@ -4,7 +4,11 @@ package file
|
|
|
|
|
|
import (
|
|
import (
|
|
"fmt"
|
|
"fmt"
|
|
- "strings"
|
|
|
|
|
|
+ "path"
|
|
|
|
+ "sort"
|
|
|
|
+ "sync"
|
|
|
|
+
|
|
|
|
+ "github.com/go-sourcemap/sourcemap"
|
|
)
|
|
)
|
|
|
|
|
|
// Idx is a compact encoding of a source position within a file set.
|
|
// Idx is a compact encoding of a source position within a file set.
|
|
@@ -16,7 +20,6 @@ type Idx int
|
|
// including the filename, line, and column location.
|
|
// including the filename, line, and column location.
|
|
type Position struct {
|
|
type Position struct {
|
|
Filename string // The filename where the error occurred, if any
|
|
Filename string // The filename where the error occurred, if any
|
|
- Offset int // The src offset
|
|
|
|
Line int // The line number, starting at 1
|
|
Line int // The line number, starting at 1
|
|
Column int // The column number, starting at 1 (The character count)
|
|
Column int // The column number, starting at 1 (The character count)
|
|
|
|
|
|
@@ -35,7 +38,7 @@ func (self *Position) isValid() bool {
|
|
// file An invalid position with filename
|
|
// file An invalid position with filename
|
|
// - An invalid position without filename
|
|
// - An invalid position without filename
|
|
//
|
|
//
|
|
-func (self *Position) String() string {
|
|
|
|
|
|
+func (self Position) String() string {
|
|
str := self.Filename
|
|
str := self.Filename
|
|
if self.isValid() {
|
|
if self.isValid() {
|
|
if str != "" {
|
|
if str != "" {
|
|
@@ -89,29 +92,23 @@ func (self *FileSet) File(idx Idx) *File {
|
|
}
|
|
}
|
|
|
|
|
|
// Position converts an Idx in the FileSet into a Position.
|
|
// Position converts an Idx in the FileSet into a Position.
|
|
-func (self *FileSet) Position(idx Idx) *Position {
|
|
|
|
- position := &Position{}
|
|
|
|
|
|
+func (self *FileSet) Position(idx Idx) Position {
|
|
for _, file := range self.files {
|
|
for _, file := range self.files {
|
|
if idx <= Idx(file.base+len(file.src)) {
|
|
if idx <= Idx(file.base+len(file.src)) {
|
|
- offset := int(idx) - file.base
|
|
|
|
- src := file.src[:offset]
|
|
|
|
- position.Filename = file.name
|
|
|
|
- position.Offset = offset
|
|
|
|
- position.Line = 1 + strings.Count(src, "\n")
|
|
|
|
- if index := strings.LastIndex(src, "\n"); index >= 0 {
|
|
|
|
- position.Column = offset - index
|
|
|
|
- } else {
|
|
|
|
- position.Column = 1 + len(src)
|
|
|
|
- }
|
|
|
|
|
|
+ return file.Position(int(idx) - file.base)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
- return position
|
|
|
|
|
|
+ return Position{}
|
|
}
|
|
}
|
|
|
|
|
|
type File struct {
|
|
type File struct {
|
|
- name string
|
|
|
|
- src string
|
|
|
|
- base int // This will always be 1 or greater
|
|
|
|
|
|
+ mu sync.Mutex
|
|
|
|
+ name string
|
|
|
|
+ src string
|
|
|
|
+ base int // This will always be 1 or greater
|
|
|
|
+ sourceMap *sourcemap.Consumer
|
|
|
|
+ lineOffsets []int
|
|
|
|
+ lastScannedOffset int
|
|
}
|
|
}
|
|
|
|
|
|
func NewFile(filename, src string, base int) *File {
|
|
func NewFile(filename, src string, base int) *File {
|
|
@@ -133,3 +130,86 @@ func (fl *File) Source() string {
|
|
func (fl *File) Base() int {
|
|
func (fl *File) Base() int {
|
|
return fl.base
|
|
return fl.base
|
|
}
|
|
}
|
|
|
|
+
|
|
|
|
+func (fl *File) SetSourceMap(m *sourcemap.Consumer) {
|
|
|
|
+ fl.sourceMap = m
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+func (fl *File) Position(offset int) Position {
|
|
|
|
+ var line int
|
|
|
|
+ var lineOffsets []int
|
|
|
|
+ fl.mu.Lock()
|
|
|
|
+ if offset > fl.lastScannedOffset {
|
|
|
|
+ line = fl.scanTo(offset)
|
|
|
|
+ lineOffsets = fl.lineOffsets
|
|
|
|
+ fl.mu.Unlock()
|
|
|
|
+ } else {
|
|
|
|
+ lineOffsets = fl.lineOffsets
|
|
|
|
+ fl.mu.Unlock()
|
|
|
|
+ line = sort.Search(len(lineOffsets), func(x int) bool { return lineOffsets[x] > offset }) - 1
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ var lineStart int
|
|
|
|
+ if line >= 0 {
|
|
|
|
+ lineStart = lineOffsets[line]
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ row := line + 2
|
|
|
|
+ col := offset - lineStart + 1
|
|
|
|
+
|
|
|
|
+ if fl.sourceMap != nil {
|
|
|
|
+ if source, _, row, col, ok := fl.sourceMap.Source(row, col); ok {
|
|
|
|
+ if !path.IsAbs(source) {
|
|
|
|
+ source = path.Join(path.Dir(fl.name), source)
|
|
|
|
+ }
|
|
|
|
+ return Position{
|
|
|
|
+ Filename: source,
|
|
|
|
+ Line: row,
|
|
|
|
+ Column: col,
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return Position{
|
|
|
|
+ Filename: fl.name,
|
|
|
|
+ Line: row,
|
|
|
|
+ Column: col,
|
|
|
|
+ }
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+func findNextLineStart(s string) int {
|
|
|
|
+ for pos, ch := range s {
|
|
|
|
+ switch ch {
|
|
|
|
+ case '\r':
|
|
|
|
+ if pos < len(s)-1 && s[pos+1] == '\n' {
|
|
|
|
+ return pos + 2
|
|
|
|
+ }
|
|
|
|
+ return pos + 1
|
|
|
|
+ case '\n':
|
|
|
|
+ return pos + 1
|
|
|
|
+ case '\u2028', '\u2029':
|
|
|
|
+ return pos + 3
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ return -1
|
|
|
|
+}
|
|
|
|
+
|
|
|
|
+func (fl *File) scanTo(offset int) int {
|
|
|
|
+ o := fl.lastScannedOffset
|
|
|
|
+ for o < offset {
|
|
|
|
+ p := findNextLineStart(fl.src[o:])
|
|
|
|
+ if p == -1 {
|
|
|
|
+ fl.lastScannedOffset = len(fl.src)
|
|
|
|
+ return len(fl.lineOffsets) - 1
|
|
|
|
+ }
|
|
|
|
+ o = o + p
|
|
|
|
+ fl.lineOffsets = append(fl.lineOffsets, o)
|
|
|
|
+ }
|
|
|
|
+ fl.lastScannedOffset = o
|
|
|
|
+
|
|
|
|
+ if o == offset {
|
|
|
|
+ return len(fl.lineOffsets) - 1
|
|
|
|
+ }
|
|
|
|
+
|
|
|
|
+ return len(fl.lineOffsets) - 2
|
|
|
|
+}
|