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