Browse Source

Support for sourcemaps (#66)

* added support for external but local sourcemaps
noctarius aka Christoph Engelbert 7 years ago
parent
commit
ba650e5fe8
5 changed files with 70 additions and 8 deletions
  1. 3 0
      ast/node.go
  2. 2 2
      compiler.go
  3. 43 0
      parser/statement.go
  4. 21 5
      srcfile.go
  5. 1 1
      srcfile_test.go

+ 3 - 0
ast/node.go

@@ -12,6 +12,7 @@ package ast
 import (
 	"github.com/dop251/goja/file"
 	"github.com/dop251/goja/token"
+	"github.com/go-sourcemap/sourcemap"
 )
 
 // All nodes implement the Node interface.
@@ -383,6 +384,8 @@ type Program struct {
 	DeclarationList []Declaration
 
 	File *file.File
+
+	SourceMap *sourcemap.Consumer
 }
 
 // ==== //

+ 2 - 2
compiler.go

@@ -9,7 +9,7 @@ import (
 )
 
 const (
-	blockLoop = iota
+	blockLoop   = iota
 	blockTry
 	blockBranch
 	blockSwitch
@@ -247,7 +247,7 @@ func (c *compiler) markBlockStart() {
 }
 
 func (c *compiler) compile(in *ast.Program) {
-	c.p.src = NewSrcFile(in.File.Name(), in.File.Source())
+	c.p.src = NewSrcFile(in.File.Name(), in.File.Source(), in.SourceMap)
 
 	if len(in.Body) > 0 {
 		if !c.scope.strict {

+ 43 - 0
parser/statement.go

@@ -4,6 +4,11 @@ import (
 	"github.com/dop251/goja/ast"
 	"github.com/dop251/goja/file"
 	"github.com/dop251/goja/token"
+	"github.com/go-sourcemap/sourcemap"
+	"encoding/base64"
+	"strings"
+	"os"
+	"io/ioutil"
 )
 
 func (self *_parser) parseBlockStatement() *ast.BlockStatement {
@@ -544,9 +549,47 @@ func (self *_parser) parseProgram() *ast.Program {
 		Body:            self.parseSourceElements(),
 		DeclarationList: self.scope.declarationList,
 		File:            self.file,
+		SourceMap:       self.parseSourceMap(),
 	}
 }
 
+func (self *_parser) parseSourceMap() *sourcemap.Consumer {
+	lastLine := self.str[strings.LastIndexByte(self.str, '\n') + 1:]
+	if strings.HasPrefix(lastLine, "//# sourceMappingURL") {
+		urlIndex := strings.Index(lastLine, "=")
+		url := lastLine[urlIndex+1:]
+
+		var data []byte
+		if strings.HasPrefix(url, "data:application/json;base64") {
+			b64Index := strings.Index(url, ",")
+			b64 := url[b64Index+1:]
+			if d, err := base64.StdEncoding.DecodeString(b64); err == nil {
+				data = d
+			}
+		}
+
+		if strings.HasPrefix(strings.ToLower(url), "http") {
+			// Not implemented - compile error?
+			return nil
+		}
+
+		if f, err := os.Open(url); err == nil {
+			if d, err := ioutil.ReadAll(f); err == nil {
+				data = d
+			}
+		}
+
+		if data == nil {
+			return nil
+		}
+
+		if sm, err := sourcemap.Parse(self.file.Name(), data); err == nil {
+			return sm
+		}
+	}
+	return nil
+}
+
 func (self *_parser) parseBreakStatement() ast.Statement {
 	idx := self.expect(token.BREAK)
 	semicolon := self.implicitSemicolon

+ 21 - 5
srcfile.go

@@ -4,6 +4,7 @@ import (
 	"fmt"
 	"sort"
 	"strings"
+	"github.com/go-sourcemap/sourcemap"
 )
 
 type Position struct {
@@ -16,12 +17,14 @@ type SrcFile struct {
 
 	lineOffsets       []int
 	lastScannedOffset int
+	sourceMap         *sourcemap.Consumer
 }
 
-func NewSrcFile(name, src string) *SrcFile {
+func NewSrcFile(name, src string, sourceMap *sourcemap.Consumer) *SrcFile {
 	return &SrcFile{
-		name: name,
-		src:  src,
+		name:      name,
+		src:       src,
+		sourceMap: sourceMap,
 	}
 }
 
@@ -37,9 +40,22 @@ func (f *SrcFile) Position(offset int) Position {
 	if line >= 0 {
 		lineStart = f.lineOffsets[line]
 	}
+
+	row := line + 2
+	col := offset - lineStart + 1
+
+	if f.sourceMap != nil {
+		if _, _, row, col, ok := f.sourceMap.Source(row, col); ok {
+			return Position{
+				Line: row,
+				Col:  col,
+			}
+		}
+	}
+
 	return Position{
-		Line: line + 2,
-		Col:  offset - lineStart + 1,
+		Line: row,
+		Col:  col,
 	}
 }
 

+ 1 - 1
srcfile_test.go

@@ -6,7 +6,7 @@ func TestPosition(t *testing.T) {
 	const SRC = `line1
 line2
 line3`
-	f := NewSrcFile("", SRC)
+	f := NewSrcFile("", SRC, nil)
 
 	tests := []struct {
 		offset int