diff --git a/core/engine_test.go b/core/engine_test.go index 2a2d12be66a8..6ef099278b33 100644 --- a/core/engine_test.go +++ b/core/engine_test.go @@ -783,8 +783,8 @@ func TestSetupException(t *testing.T) { require.Error(t, err) var exception errext.Exception require.ErrorAs(t, err, &exception) - require.Equal(t, "Error: baz\n\tat baz (file:///bar.js:7:8(3))\n"+ - "\tat file:///bar.js:4:5(3)\n\tat setup (file:///script.js:7:204(4))\n\tat native\n", + require.Equal(t, "Error: baz\n\tat baz (file:///bar.js:6:16(3))\n"+ + "\tat file:///bar.js:3:8(3)\n\tat setup (file:///script.js:4:2(4))\n\tat native\n", err.Error()) } } @@ -835,7 +835,7 @@ func TestVuInitException(t *testing.T) { var exception errext.Exception require.ErrorAs(t, err, &exception) - assert.Equal(t, "Error: oops in 2\n\tat file:///script.js:10:8(31)\n", err.Error()) + assert.Equal(t, "Error: oops in 2\n\tat file:///script.js:10:9(31)\n", err.Error()) var errWithHint errext.HasHint require.ErrorAs(t, err, &errWithHint) diff --git a/js/bundle.go b/js/bundle.go index 39bf259f6ffc..6cd197bc6ad8 100644 --- a/js/bundle.go +++ b/js/bundle.go @@ -30,7 +30,6 @@ import ( "runtime" "github.com/dop251/goja" - "github.com/dop251/goja/parser" "github.com/sirupsen/logrus" "github.com/spf13/afero" "gopkg.in/guregu/null.v3" @@ -84,7 +83,13 @@ func NewBundle( // Compile sources, both ES5 and ES6 are supported. code := string(src.Data) c := compiler.New(logger) - pgm, _, err := c.Compile(code, src.URL.String(), "", "", true, compatMode) + c.COpts = compiler.Options{ + CompatibilityMode: compatMode, + Strict: true, + SourceMapEnabled: true, + SourceMapLoader: generateSourceMapLoader(logger, filesystems), + } + pgm, _, err := c.Compile(code, src.URL.String(), true, c.COpts) if err != nil { return nil, err } @@ -132,7 +137,13 @@ func NewBundleFromArchive( } c := compiler.New(logger) - pgm, _, err := c.Compile(string(arc.Data), arc.FilenameURL.String(), "", "", true, compatMode) + c.COpts = compiler.Options{ + Strict: true, + CompatibilityMode: compatMode, + SourceMapEnabled: true, + SourceMapLoader: generateSourceMapLoader(logger, arc.Filesystems), + } + pgm, _, err := c.Compile(string(arc.Data), arc.FilenameURL.String(), true, c.COpts) if err != nil { return nil, err } @@ -291,7 +302,6 @@ func (b *Bundle) Instantiate(logger logrus.FieldLogger, vuID uint64) (bi *Bundle // Instantiates the bundle into an existing runtime. Not public because it also messes with a bunch // of other things, will potentially thrash data and makes a mess in it if the operation fails. func (b *Bundle) instantiate(logger logrus.FieldLogger, rt *goja.Runtime, init *InitContext, vuID uint64) error { - rt.SetParserOptions(parser.WithDisableSourceMaps) rt.SetFieldNameMapper(common.FieldNameMapper{}) rt.SetRandSource(common.NewRandSource()) @@ -338,3 +348,18 @@ func (b *Bundle) instantiate(logger logrus.FieldLogger, rt *goja.Runtime, init * return nil } + +func generateSourceMapLoader(logger logrus.FieldLogger, filesystems map[string]afero.Fs, +) func(path string) ([]byte, error) { + return func(path string) ([]byte, error) { + u, err := url.Parse(path) + if err != nil { + return nil, err + } + data, err := loader.Load(logger, filesystems, u, path) + if err != nil { + return nil, err + } + return data.Data, nil + } +} diff --git a/js/compiler/compiler.go b/js/compiler/compiler.go index 8a108a32378e..94708c8ad079 100644 --- a/js/compiler/compiler.go +++ b/js/compiler/compiler.go @@ -22,6 +22,9 @@ package compiler import ( _ "embed" // we need this for embedding Babel + "encoding/json" + "errors" + "strings" "sync" "time" @@ -86,10 +89,13 @@ var ( globalBabel *babel // nolint:gochecknoglobals ) +const sourceMapURLFromBabel = "k6://internal-should-not-leak/file.map" + // A Compiler compiles JavaScript source code (ES5.1 or ES6) into a goja.Program type Compiler struct { logger logrus.FieldLogger babel *babel + COpts Options } // New returns a new Compiler @@ -108,7 +114,7 @@ func (c *Compiler) initializeBabel() error { } // Transform the given code into ES5 -func (c *Compiler) Transform(src, filename string) (code string, srcmap []byte, err error) { +func (c *Compiler) Transform(src, filename string, inputSrcMap []byte) (code string, srcMap []byte, err error) { if c.babel == nil { onceBabel.Do(func() { globalBabel, err = newBabel() @@ -119,48 +125,96 @@ func (c *Compiler) Transform(src, filename string) (code string, srcmap []byte, return } - code, srcmap, err = c.babel.Transform(c.logger, src, filename) + code, srcMap, err = c.babel.transformImpl(c.logger, src, filename, c.COpts.SourceMapEnabled, inputSrcMap) return } +// Options are options to the compiler +type Options struct { + CompatibilityMode lib.CompatibilityMode + SourceMapEnabled bool + SourceMapLoader func(string) ([]byte, error) + Strict bool +} + +// compilationState is helper struct to keep the state of a compilation +type compilationState struct { + // set when we couldn't load external source map so we can try parsing without loading it + couldntLoadSourceMap bool + // srcMap is the current full sourceMap that has been generated read so far + srcMap []byte + main bool + + compiler *Compiler +} + // Compile the program in the given CompatibilityMode, wrapping it between pre and post code -func (c *Compiler) Compile(src, filename, pre, post string, - strict bool, compatMode lib.CompatibilityMode) (*goja.Program, string, error) { - code := pre + src + post - ast, err := parser.ParseFile(nil, filename, code, 0, parser.WithDisableSourceMaps) - if err != nil { - if compatMode == lib.CompatibilityModeExtended { - code, _, err = c.Transform(src, filename) - if err != nil { - return nil, code, err - } - // the compatibility mode "decreases" here as we shouldn't transform twice - return c.Compile(code, filename, pre, post, strict, lib.CompatibilityModeBase) +func (c *Compiler) Compile(src, filename string, main bool, cOpts Options) (*goja.Program, string, error) { + return c.compileImpl(src, filename, main, cOpts, nil) +} + +// here we take the srcMap as pointer so we can change in case that we have an original srcMap that will be loaded, but +// the code still needs to go through babel. In this way we can give the correct inputSourceMap to babel which hopefully +// will generate a sourcemap which goes from the final source file lines to the original ones that were transpiled +// outside of k6 +// TODO have some kind of "compilationContext" to save this instead and modify and move that +// This won't be needed if we weren't going through babel so removing babel would kind of remove the need for that +func (c *compilationState) sourceMapLoader(path string) ([]byte, error) { + if path == sourceMapURLFromBabel { + if !c.main { + return increaseMappingsByOne(c.srcMap) } - return nil, code, err + return c.srcMap, nil + } + var err error + c.srcMap, err = c.compiler.COpts.SourceMapLoader(path) + if err != nil { + c.couldntLoadSourceMap = true + return nil, err + } + if !c.main { + return increaseMappingsByOne(c.srcMap) + } + return c.srcMap, err +} + +func (c *Compiler) compileImpl( + src, filename string, main bool, cOpts Options, srcMap []byte, +) (*goja.Program, string, error) { + code := src + state := compilationState{srcMap: srcMap, compiler: c, main: main} + if !main { + code = "(function(module, exports){\n" + code + "\n})\n" + } + if len(srcMap) > 0 { + code += "\n//# sourceMappingURL=" + sourceMapURLFromBabel + } + opts := parser.WithDisableSourceMaps + if cOpts.SourceMapEnabled { + opts = parser.WithSourceMapLoader(state.sourceMapLoader) + } + ast, err := parser.ParseFile(nil, filename, code, 0, opts) + + if state.couldntLoadSourceMap { + state.couldntLoadSourceMap = false // reset + // we probably don't want to abort scripts which have source maps but they can't be found, + // this also will be a breaking change, so if we couldn't we retry with it disabled + c.logger.WithError(err).Warnf("Couldn't load source map for %s", filename) + ast, err = parser.ParseFile(nil, filename, code, 0, parser.WithDisableSourceMaps) } - pgm, err := goja.CompileAST(ast, strict) - // Parsing only checks the syntax, not whether what the syntax expresses - // is actually supported (sometimes). - // - // For example, destructuring looks a lot like an object with shorthand - // properties, but this is only noticeable once the code is compiled, not - // while parsing. Even now code such as `let [x] = [2]` doesn't return an - // error on the parsing stage but instead in the compilation in base mode. - // - // So, because of this, if there is an error during compilation, it still might - // be worth it to transform the code and try again. if err != nil { - if compatMode == lib.CompatibilityModeExtended { - code, _, err = c.Transform(src, filename) + if cOpts.CompatibilityMode == lib.CompatibilityModeExtended { + code, state.srcMap, err = c.Transform(src, filename, state.srcMap) if err != nil { return nil, code, err } // the compatibility mode "decreases" here as we shouldn't transform twice - return c.Compile(code, filename, pre, post, strict, lib.CompatibilityModeBase) + cOpts.CompatibilityMode = lib.CompatibilityModeBase + return c.compileImpl(code, filename, main, cOpts, state.srcMap) } return nil, code, err } + pgm, err := goja.CompileAST(ast, cOpts.Strict) return pgm, code, err } @@ -194,16 +248,53 @@ func newBabel() (*babel, error) { return result, err } -// Transform the given code into ES5, while synchronizing to ensure only a single +func increaseMappingsByOne(sourceMap []byte) ([]byte, error) { + var err error + m := make(map[string]interface{}) + if err = json.Unmarshal(sourceMap, &m); err != nil { + return nil, err + } + mappings, ok := m["mappings"] + if !ok { + // no mappings, no idea what this will do, but just return it as technically we can have sourcemap with sections + // TODO implement incrementing of `offset` in the sections? + // TODO (kind of alternatively) drop the newline in the "commonjs" wrapping and have only the first line wrong + // except everything being off by one + return sourceMap, nil + } + if str, ok := mappings.(string); ok { + // ';' is the separator between lines so just adding 1 will make all mappings be for the line after which they were + // originally + m["mappings"] = ";" + str + } else { + // we have mappings but it's not a string - this is some kind of erro + return nil, errors.New(`missing "mappings" in sourcemap`) + } + + return json.Marshal(m) +} + +// transformImpl the given code into ES5, while synchronizing to ensure only a single // bundle instance / Goja VM is in use at a time. -// TODO the []byte is there to be used as the returned sourcemap and will be done in PR #2082 -func (b *babel) Transform(logger logrus.FieldLogger, src, filename string) (string, []byte, error) { +func (b *babel) transformImpl( + logger logrus.FieldLogger, src, filename string, sourceMapsEnabled bool, inputSrcMap []byte, +) (string, []byte, error) { b.m.Lock() defer b.m.Unlock() opts := make(map[string]interface{}) for k, v := range DefaultOpts { opts[k] = v } + if sourceMapsEnabled { + opts["sourceMaps"] = true + if inputSrcMap != nil { + srcMap := new(map[string]interface{}) + if err := json.Unmarshal(inputSrcMap, &srcMap); err != nil { + return "", nil, err + } + opts["inputSourceMap"] = srcMap + } + } opts["filename"] = filename startTime := time.Now() @@ -218,7 +309,27 @@ func (b *babel) Transform(logger logrus.FieldLogger, src, filename string) (stri if err = b.vm.ExportTo(vO.Get("code"), &code); err != nil { return code, nil, err } - return code, nil, err + if !sourceMapsEnabled { + return code, nil, nil + } + + // this is to make goja try to load a sourcemap. + // it is a special url as it should never leak outside of this code + // additionally the alternative support from babel is to embed *the whole* sourcemap at the end + if i := strings.Index(code, "//# sourceMappingURL="); i > 0 { + code = code[:i] + } + code += "\n//# sourceMappingURL=" + sourceMapURLFromBabel + stringify, err := b.vm.RunString("(function(m) { return JSON.stringify(m)})") + if err != nil { + return code, nil, err + } + c, _ := goja.AssertFunction(stringify) + mapAsJSON, err := c(goja.Undefined(), vO.Get("map")) + if err != nil { + return code, nil, err + } + return code, []byte(mapAsJSON.String()), nil } // Pool is a pool of compilers so it can be used easier in parallel tests as they have their own babel. diff --git a/js/compiler/compiler_test.go b/js/compiler/compiler_test.go index 8ba055b8ca88..b2badd3b9999 100644 --- a/js/compiler/compiler_test.go +++ b/js/compiler/compiler_test.go @@ -34,31 +34,31 @@ import ( ) func TestTransform(t *testing.T) { - c := New(testutils.NewLogger(t)) + t.Parallel() t.Run("blank", func(t *testing.T) { - src, _, err := c.Transform("", "test.js") + t.Parallel() + c := New(testutils.NewLogger(t)) + src, _, err := c.Transform("", "test.js", nil) assert.NoError(t, err) assert.Equal(t, `"use strict";`, src) - // assert.Equal(t, 3, srcmap.Version) - // assert.Equal(t, "test.js", srcmap.File) - // assert.Equal(t, "", srcmap.Mappings) }) t.Run("double-arrow", func(t *testing.T) { - src, _, err := c.Transform("()=> true", "test.js") + t.Parallel() + c := New(testutils.NewLogger(t)) + src, _, err := c.Transform("()=> true", "test.js", nil) assert.NoError(t, err) assert.Equal(t, `"use strict";() => true;`, src) - // assert.Equal(t, 3, srcmap.Version) - // assert.Equal(t, "test.js", srcmap.File) - // assert.Equal(t, "aAAA,qBAAK,IAAL", srcmap.Mappings) }) t.Run("longer", func(t *testing.T) { + t.Parallel() + c := New(testutils.NewLogger(t)) src, _, err := c.Transform(strings.Join([]string{ `function add(a, b) {`, ` return a + b;`, `};`, ``, `let res = add(1, 2);`, - }, "\n"), "test.js") + }, "\n"), "test.js", nil) assert.NoError(t, err) assert.Equal(t, strings.Join([]string{ `"use strict";function add(a, b) {`, @@ -67,104 +67,133 @@ func TestTransform(t *testing.T) { ``, `let res = add(1, 2);`, }, "\n"), src) - // assert.Equal(t, 3, srcmap.Version) - // assert.Equal(t, "test.js", srcmap.File) - // assert.Equal(t, "aAAA,SAASA,GAAT,CAAaC,CAAb,EAAgBC,CAAhB,EAAmB;AACf,WAAOD,IAAIC,CAAX;AACH;;AAED,IAAIC,MAAMH,IAAI,CAAJ,EAAO,CAAP,CAAV", srcmap.Mappings) + }) + + t.Run("double-arrow with sourceMap", func(t *testing.T) { + t.Parallel() + c := New(testutils.NewLogger(t)) + c.COpts.SourceMapEnabled = true + src, _, err := c.Transform("()=> true", "test.js", nil) + assert.NoError(t, err) + assert.Equal(t, `"use strict";() => true; +//# sourceMappingURL=k6://internal-should-not-leak/file.map`, src) }) } func TestCompile(t *testing.T) { - c := New(testutils.NewLogger(t)) + t.Parallel() t.Run("ES5", func(t *testing.T) { + t.Parallel() + c := New(testutils.NewLogger(t)) src := `1+(function() { return 2; })()` - pgm, code, err := c.Compile(src, "script.js", "", "", true, lib.CompatibilityModeBase) + pgm, code, err := c.Compile(src, "script.js", true, c.COpts) require.NoError(t, err) assert.Equal(t, src, code) v, err := goja.New().RunProgram(pgm) if assert.NoError(t, err) { assert.Equal(t, int64(3), v.Export()) } + }) - t.Run("Wrap", func(t *testing.T) { - pgm, code, err := c.Compile(src, "script.js", - "(function(){return ", "})", true, lib.CompatibilityModeBase) - require.NoError(t, err) - assert.Equal(t, `(function(){return 1+(function() { return 2; })()})`, code) - v, err := goja.New().RunProgram(pgm) - if assert.NoError(t, err) { - fn, ok := goja.AssertFunction(v) - if assert.True(t, ok, "not a function") { - v, err := fn(goja.Undefined()) - if assert.NoError(t, err) { - assert.Equal(t, int64(3), v.Export()) - } + t.Run("ES5 Wrap", func(t *testing.T) { + t.Parallel() + c := New(testutils.NewLogger(t)) + src := `exports.d=1+(function() { return 2; })()` + pgm, code, err := c.Compile(src, "script.js", false, c.COpts) + require.NoError(t, err) + assert.Equal(t, "(function(module, exports){\nexports.d=1+(function() { return 2; })()\n})\n", code) + rt := goja.New() + v, err := rt.RunProgram(pgm) + if assert.NoError(t, err) { + fn, ok := goja.AssertFunction(v) + if assert.True(t, ok, "not a function") { + exp := make(map[string]goja.Value) + _, err := fn(goja.Undefined(), goja.Undefined(), rt.ToValue(exp)) + if assert.NoError(t, err) { + assert.Equal(t, int64(3), exp["d"].Export()) } } - }) + } + }) - t.Run("Invalid", func(t *testing.T) { - src := `1+(function() { return 2; )()` - _, _, err := c.Compile(src, "script.js", "", "", true, lib.CompatibilityModeExtended) - assert.IsType(t, &goja.Exception{}, err) - assert.Contains(t, err.Error(), `SyntaxError: script.js: Unexpected token (1:26) + t.Run("ES5 Invalid", func(t *testing.T) { + t.Parallel() + c := New(testutils.NewLogger(t)) + src := `1+(function() { return 2; )()` + c.COpts.CompatibilityMode = lib.CompatibilityModeExtended + _, _, err := c.Compile(src, "script.js", false, c.COpts) + assert.IsType(t, &goja.Exception{}, err) + assert.Contains(t, err.Error(), `SyntaxError: script.js: Unexpected token (1:26) > 1 | 1+(function() { return 2; )()`) - }) }) t.Run("ES6", func(t *testing.T) { - pgm, code, err := c.Compile(`3**2`, "script.js", "", "", true, lib.CompatibilityModeExtended) + t.Parallel() + c := New(testutils.NewLogger(t)) + c.COpts.CompatibilityMode = lib.CompatibilityModeExtended + pgm, code, err := c.Compile(`3**2`, "script.js", true, c.COpts) require.NoError(t, err) assert.Equal(t, `"use strict";Math.pow(3, 2);`, code) v, err := goja.New().RunProgram(pgm) if assert.NoError(t, err) { assert.Equal(t, int64(9), v.Export()) } + }) - t.Run("Wrap", func(t *testing.T) { - pgm, code, err := c.Compile(`fn(3**2)`, "script.js", "(function(fn){", "})", true, lib.CompatibilityModeExtended) - require.NoError(t, err) - assert.Equal(t, `(function(fn){"use strict";fn(Math.pow(3, 2));})`, code) - rt := goja.New() - v, err := rt.RunProgram(pgm) - if assert.NoError(t, err) { - fn, ok := goja.AssertFunction(v) - if assert.True(t, ok, "not a function") { - var out interface{} - _, err := fn(goja.Undefined(), rt.ToValue(func(v goja.Value) { - out = v.Export() - })) - assert.NoError(t, err) - assert.Equal(t, int64(9), out) - } + t.Run("Wrap", func(t *testing.T) { + t.Parallel() + c := New(testutils.NewLogger(t)) + c.COpts.CompatibilityMode = lib.CompatibilityModeExtended + pgm, code, err := c.Compile(`exports.fn(3**2)`, "script.js", false, c.COpts) + require.NoError(t, err) + assert.Equal(t, "(function(module, exports){\n\"use strict\";exports.fn(Math.pow(3, 2));\n})\n", code) + rt := goja.New() + v, err := rt.RunProgram(pgm) + if assert.NoError(t, err) { + fn, ok := goja.AssertFunction(v) + if assert.True(t, ok, "not a function") { + exp := make(map[string]goja.Value) + var out interface{} + exp["fn"] = rt.ToValue(func(v goja.Value) { + out = v.Export() + }) + _, err := fn(goja.Undefined(), goja.Undefined(), rt.ToValue(exp)) + assert.NoError(t, err) + assert.Equal(t, int64(9), out) } - }) + } + }) - t.Run("Invalid", func(t *testing.T) { - _, _, err := c.Compile(`1+(=>2)()`, "script.js", "", "", true, lib.CompatibilityModeExtended) - assert.IsType(t, &goja.Exception{}, err) - assert.Contains(t, err.Error(), `SyntaxError: script.js: Unexpected token (1:3) + t.Run("Invalid", func(t *testing.T) { + t.Parallel() + c := New(testutils.NewLogger(t)) + c.COpts.CompatibilityMode = lib.CompatibilityModeExtended + _, _, err := c.Compile(`1+(=>2)()`, "script.js", true, c.COpts) + assert.IsType(t, &goja.Exception{}, err) + assert.Contains(t, err.Error(), `SyntaxError: script.js: Unexpected token (1:3) > 1 | 1+(=>2)()`) - }) + }) - t.Run("Invalid for goja but not babel", func(t *testing.T) { - t.Skip("Find something else that breaks this as this was fixed in goja :(") - ch := make(chan struct{}) - go func() { - defer close(ch) - // This is a string with U+2029 Paragraph separator in it - // the important part is that goja won't parse it but babel will transform it but still - // goja won't be able to parse the result it is actually "\" - _, _, err := c.Compile(string([]byte{0x22, 0x5c, 0xe2, 0x80, 0xa9, 0x22}), "script.js", "", "", true, lib.CompatibilityModeExtended) - assert.IsType(t, parser.ErrorList{}, err) - assert.Contains(t, err.Error(), ` Unexpected token ILLEGAL`) - }() + t.Run("Invalid for goja but not babel", func(t *testing.T) { + t.Parallel() + t.Skip("Find something else that breaks this as this was fixed in goja :(") + ch := make(chan struct{}) + go func() { + defer close(ch) + // This is a string with U+2029 Paragraph separator in it + // the important part is that goja won't parse it but babel will transform it but still + // goja won't be able to parse the result it is actually "\" + c := New(testutils.NewLogger(t)) + _, _, err := c.Compile(string([]byte{0x22, 0x5c, 0xe2, 0x80, 0xa9, 0x22}), "script.js", true, c.COpts) + assert.IsType(t, parser.ErrorList{}, err) + assert.Contains(t, err.Error(), ` Unexpected token ILLEGAL`) + }() - select { - case <-ch: - // everything is fine - case <-time.After(time.Second): - // it took too long - t.Fatal("takes too long") - } - }) + select { + case <-ch: + // everything is fine + case <-time.After(time.Second): + // it took too long + t.Fatal("takes too long") + } }) } diff --git a/js/initcontext.go b/js/initcontext.go index 0847e3d1fa82..f2e026da89aa 100644 --- a/js/initcontext.go +++ b/js/initcontext.go @@ -270,8 +270,7 @@ func (i *InitContext) requireFile(name string) (goja.Value, error) { } func (i *InitContext) compileImport(src, filename string) (*goja.Program, error) { - pgm, _, err := i.compiler.Compile(src, filename, - "(function(module, exports){\n", "\n})\n", true, i.compatibilityMode) + pgm, _, err := i.compiler.Compile(src, filename, false, i.compiler.COpts) return pgm, err } diff --git a/js/initcontext_test.go b/js/initcontext_test.go index decba1f54e20..c2fc1df3df23 100644 --- a/js/initcontext_test.go +++ b/js/initcontext_test.go @@ -585,3 +585,103 @@ func TestInitContextVU(t *testing.T) { require.NoError(t, err) assert.Equal(t, int64(5), v.Export()) } + +func TestSourceMaps(t *testing.T) { + t.Parallel() + logger := testutils.NewLogger(t) + fs := afero.NewMemMapFs() + assert.NoError(t, afero.WriteFile(fs, "/module1.js", []byte(` +export function f2(){ + throw "exception in line 2" + console.log("in f2") +} +export function f1(){ + throw "exception in line 6" + console.log("in f1") +} +`[1:]), 0o644)) + data := ` +import * as module1 from "./module1.js" + +export default function(){ +// throw "exception in line 4" + module1.f2() + console.log("in default") +} +`[1:] + b, err := getSimpleBundle(t, "/script.js", data, fs) + require.NoError(t, err) + + bi, err := b.Instantiate(logger, 0) + require.NoError(t, err) + _, err = bi.exports[consts.DefaultFn](goja.Undefined()) + require.Error(t, err) + exception := new(goja.Exception) + require.ErrorAs(t, err, &exception) + require.Equal(t, exception.String(), "exception in line 2\n\tat f2 (file:///module1.js:2:4(2))\n\tat file:///script.js:5:4(4)\n\tat native\n") +} + +func TestSourceMapsExternal(t *testing.T) { + t.Parallel() + logger := testutils.NewLogger(t) + fs := afero.NewMemMapFs() + // This example is created through the template-typescript + assert.NoError(t, afero.WriteFile(fs, "/test1.js", []byte(` +(()=>{"use strict";var e={};(()=>{var o=e;Object.defineProperty(o,"__esModule",{value:!0}),o.default=function(){!function(e){throw"cool is cool"}()}})();var o=exports;for(var r in e)o[r]=e[r];e.__esModule&&Object.defineProperty(o,"__esModule",{value:!0})})(); +//# sourceMappingURL=test1.js.map +`[1:]), 0o644)) + assert.NoError(t, afero.WriteFile(fs, "/test1.js.map", []byte(` +{"version":3,"sources":["webpack:///./test1.ts"],"names":["s","coolThrow"],"mappings":"2FAGA,sBAHA,SAAmBA,GACf,KAAM,eAGNC,K","file":"test1.js","sourcesContent":["function coolThrow(s: string) {\n throw \"cool \"+ s\n}\nexport default () => {\n coolThrow(\"is cool\")\n};\n"],"sourceRoot":""} +`[1:]), 0o644)) + data := ` +import l from "./test1.js" + +export default function () { + l() +}; +`[1:] + b, err := getSimpleBundle(t, "/script.js", data, fs) + require.NoError(t, err) + + bi, err := b.Instantiate(logger, 0) + require.NoError(t, err) + _, err = bi.exports[consts.DefaultFn](goja.Undefined()) + require.Error(t, err) + exception := new(goja.Exception) + require.ErrorAs(t, err, &exception) + require.Equal(t, "cool is cool\n\tat webpack:///./test1.ts:2:4(2)\n\tat webpack:///./test1.ts:5:4(3)\n\tat file:///script.js:4:2(4)\n\tat native\n", exception.String()) +} + +func TestSourceMapsExternalExtented(t *testing.T) { + t.Parallel() + logger := testutils.NewLogger(t) + fs := afero.NewMemMapFs() + // This example is created through the template-typescript + // but was exported to use import/export syntax so it has to go through babel + assert.NoError(t, afero.WriteFile(fs, "/test1.js", []byte(` +var o={d:(e,r)=>{for(var t in r)o.o(r,t)&&!o.o(e,t)&&Object.defineProperty(e,t,{enumerable:!0,get:r[t]})},o:(o,e)=>Object.prototype.hasOwnProperty.call(o,e)},e={};o.d(e,{Z:()=>r});const r=()=>{!function(o){throw"cool is cool"}()};var t=e.Z;export{t as default}; +//# sourceMappingURL=test1.js.map +`[1:]), 0o644)) + assert.NoError(t, afero.WriteFile(fs, "/test1.js.map", []byte(` +{"version":3,"sources":["webpack:///webpack/bootstrap","webpack:///webpack/runtime/define property getters","webpack:///webpack/runtime/hasOwnProperty shorthand","webpack:///./test1.ts"],"names":["__webpack_require__","exports","definition","key","o","Object","defineProperty","enumerable","get","obj","prop","prototype","hasOwnProperty","call","s","coolThrow"],"mappings":"AACA,IAAIA,EAAsB,CCA1B,EAAwB,CAACC,EAASC,KACjC,IAAI,IAAIC,KAAOD,EACXF,EAAoBI,EAAEF,EAAYC,KAASH,EAAoBI,EAAEH,EAASE,IAC5EE,OAAOC,eAAeL,EAASE,EAAK,CAAEI,YAAY,EAAMC,IAAKN,EAAWC,MCJ3E,EAAwB,CAACM,EAAKC,IAAUL,OAAOM,UAAUC,eAAeC,KAAKJ,EAAKC,I,sBCGlF,cAHA,SAAmBI,GACf,KAAM,eAGNC,I","file":"test1.js","sourcesContent":["// The require scope\nvar __webpack_require__ = {};\n\n","// define getter functions for harmony exports\n__webpack_require__.d = (exports, definition) => {\n\tfor(var key in definition) {\n\t\tif(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {\n\t\t\tObject.defineProperty(exports, key, { enumerable: true, get: definition[key] });\n\t\t}\n\t}\n};","__webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))","function coolThrow(s: string) {\n throw \"cool \"+ s\n}\nexport default () => {\n coolThrow(\"is cool\")\n};\n"],"sourceRoot":""} +`[1:]), 0o644)) + data := ` +import l from "./test1.js" + +export default function () { + l() +}; +`[1:] + b, err := getSimpleBundle(t, "/script.js", data, fs) + require.NoError(t, err) + + bi, err := b.Instantiate(logger, 0) + require.NoError(t, err) + _, err = bi.exports[consts.DefaultFn](goja.Undefined()) + require.Error(t, err) + exception := new(goja.Exception) + require.ErrorAs(t, err, &exception) + // TODO figure out why those are not the same as the one in the previous test TestSourceMapsExternal + // likely settings in the transpilers + require.Equal(t, "cool is cool\n\tat webpack:///./test1.ts:2:4(2)\n\tat r (webpack:///./test1.ts:5:4(3))\n\tat file:///script.js:4:2(4)\n\tat native\n", exception.String()) +} diff --git a/js/tc39/tc39_test.go b/js/tc39/tc39_test.go index c83c0128f57e..8aa5fa2388af 100644 --- a/js/tc39/tc39_test.go +++ b/js/tc39/tc39_test.go @@ -475,9 +475,9 @@ func (ctx *tc39TestCtx) compile(base, name string) (*goja.Program, error) { } str := string(b) - compiler := ctx.compilerPool.Get() - defer ctx.compilerPool.Put(compiler) - prg, _, err = compiler.Compile(str, name, "", "", false, lib.CompatibilityModeExtended) + comp := ctx.compilerPool.Get() + defer ctx.compilerPool.Put(comp) + prg, _, err = comp.Compile(str, name, true, compiler.Options{Strict: false, CompatibilityMode: lib.CompatibilityModeExtended}) if err != nil { return nil, err } @@ -516,13 +516,13 @@ func (ctx *tc39TestCtx) runTC39Script(name, src string, includes []string, vm *g } var p *goja.Program - compiler := ctx.compilerPool.Get() - defer ctx.compilerPool.Put(compiler) - p, _, origErr = compiler.Compile(src, name, "", "", false, lib.CompatibilityModeBase) + comp := ctx.compilerPool.Get() + defer ctx.compilerPool.Put(comp) + p, _, origErr = comp.Compile(src, name, true, compiler.Options{Strict: false, CompatibilityMode: lib.CompatibilityModeBase}) if origErr != nil { - src, _, err = compiler.Transform(src, name) + src, _, err = comp.Transform(src, name, nil) if err == nil { - p, _, err = compiler.Compile(src, name, "", "", false, lib.CompatibilityModeBase) + p, _, err = comp.Compile(src, name, true, compiler.Options{Strict: false, CompatibilityMode: lib.CompatibilityModeBase}) } } else { err = origErr