Browse Source

Try to fix sourcemaps with urls (#325)

* Try to fix sourcemaps with urls

Previous to this sourcemap's and filenames were only considered as urls
when sourcemap location was calculated for the purposes of loading it
but when it was reported (for example in a stacktrace) it will just path
which leads to URLs with scheme getting mangled.

Also fixes some cases where the source map loader will not get a url
even though it should've

* DRY up the code and return url.URL

* fix test after reordering the arguments

* Drop TODO and add one more case
Mihail Stoykov 4 years ago
parent
commit
625017e51d
3 changed files with 55 additions and 26 deletions
  1. 18 4
      file/file.go
  2. 29 1
      file/file_test.go
  3. 8 21
      parser/statement.go

+ 18 - 4
file/file.go

@@ -4,6 +4,7 @@ package file
 
 import (
 	"fmt"
+	"net/url"
 	"path"
 	"sort"
 	"sync"
@@ -159,11 +160,8 @@ func (fl *File) Position(offset int) Position {
 
 	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,
+				Filename: ResolveSourcemapURL(fl.Name(), source).String(),
 				Line:     row,
 				Column:   col,
 			}
@@ -177,6 +175,22 @@ func (fl *File) Position(offset int) Position {
 	}
 }
 
+func ResolveSourcemapURL(basename, source string) *url.URL {
+	// if the url is absolute(has scheme) there is nothing to do
+	smURL, err := url.Parse(source)
+	if err == nil && !smURL.IsAbs() {
+		baseURL, err1 := url.Parse(basename)
+		if err1 == nil && path.IsAbs(baseURL.Path) {
+			smURL = baseURL.ResolveReference(smURL)
+		} else {
+			// pathological case where both are not absolute paths and using Resolve as above will produce an absolute
+			// one
+			smURL, _ = url.Parse(path.Join(path.Dir(basename), smURL.Path))
+		}
+	}
+	return smURL
+}
+
 func findNextLineStart(s string) int {
 	for pos, ch := range s {
 		switch ch {

+ 29 - 1
file/file_test.go

@@ -1,6 +1,8 @@
 package file
 
-import "testing"
+import (
+	"testing"
+)
 
 func TestPosition(t *testing.T) {
 	const SRC = `line1
@@ -43,3 +45,29 @@ line3`
 	}()
 	f.Position(2)
 }
+
+func TestGetSourceFilename(t *testing.T) {
+	tests := []struct {
+		source, basename, result string
+	}{
+		{"test.js", "base.js", "test.js"},
+		{"test.js", "../base.js", "../test.js"},
+		{"test.js", "/somewhere/base.js", "/somewhere/test.js"},
+		{"/test.js", "/somewhere/base.js", "/test.js"},
+		{"/test.js", "file:///somewhere/base.js", "file:///test.js"},
+		{"file:///test.js", "base.js", "file:///test.js"},
+		{"file:///test.js", "/somwehere/base.js", "file:///test.js"},
+		{"file:///test.js", "file:///somewhere/base.js", "file:///test.js"},
+		{"../test.js", "/somewhere/else/base.js", "/somewhere/test.js"},
+		{"../test.js", "file:///somewhere/else/base.js", "file:///somewhere/test.js"},
+		{"../test.js", "https://example.com/somewhere/else/base.js", "https://example.com/somewhere/test.js"},
+		// TODO find something that won't parse
+	}
+	for _, test := range tests {
+		resultURL := ResolveSourcemapURL(test.basename, test.source)
+		result := resultURL.String()
+		if result != test.result {
+			t.Fatalf("source: %q, basename %q produced %q instead of %q", test.source, test.basename, result, test.result)
+		}
+	}
+}

+ 8 - 21
parser/statement.go

@@ -3,14 +3,13 @@ package parser
 import (
 	"encoding/base64"
 	"fmt"
+	"io/ioutil"
+	"strings"
+
 	"github.com/dop251/goja/ast"
 	"github.com/dop251/goja/file"
 	"github.com/dop251/goja/token"
 	"github.com/go-sourcemap/sourcemap"
-	"io/ioutil"
-	"net/url"
-	"path"
-	"strings"
 )
 
 func (self *_parser) parseBlockStatement() *ast.BlockStatement {
@@ -694,26 +693,14 @@ func (self *_parser) parseSourceMap() *sourcemap.Consumer {
 			b64 := urlStr[b64Index+1:]
 			data, err = base64.StdEncoding.DecodeString(b64)
 		} else {
-			var smUrl *url.URL
-			if smUrl, err = url.Parse(urlStr); err == nil {
-				p := smUrl.Path
-				if !path.IsAbs(p) {
-					baseName := self.file.Name()
-					baseUrl, err1 := url.Parse(baseName)
-					if err1 == nil && baseUrl.Scheme != "" {
-						baseUrl.Path = path.Join(path.Dir(baseUrl.Path), p)
-						p = baseUrl.String()
-					} else {
-						p = path.Join(path.Dir(baseName), p)
-					}
-				}
+			if sourceURL := file.ResolveSourcemapURL(self.file.Name(), urlStr); sourceURL != nil {
 				if self.opts.sourceMapLoader != nil {
-					data, err = self.opts.sourceMapLoader(p)
+					data, err = self.opts.sourceMapLoader(sourceURL.String())
 				} else {
-					if smUrl.Scheme == "" || smUrl.Scheme == "file" {
-						data, err = ioutil.ReadFile(p)
+					if sourceURL.Scheme == "" || sourceURL.Scheme == "file" {
+						data, err = ioutil.ReadFile(sourceURL.Path)
 					} else {
-						err = fmt.Errorf("unsupported source map URL scheme: %s", smUrl.Scheme)
+						err = fmt.Errorf("unsupported source map URL scheme: %s", sourceURL.Scheme)
 					}
 				}
 			}