更新goja版本,修复bug
This commit is contained in:
parent
8e728e2ed4
commit
db8742de7e
21
args.go
21
args.go
@ -2,7 +2,6 @@ package gojs
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"path/filepath"
|
|
||||||
|
|
||||||
"apigo.cc/gojs/goja"
|
"apigo.cc/gojs/goja"
|
||||||
"github.com/ssgo/log"
|
"github.com/ssgo/log"
|
||||||
@ -38,22 +37,6 @@ func GetLogger(vm *goja.Runtime) *log.Logger {
|
|||||||
return logger
|
return logger
|
||||||
}
|
}
|
||||||
|
|
||||||
func FindPath(vm *goja.Runtime, filename string) string {
|
|
||||||
if u.FileExists(filename) {
|
|
||||||
return filename
|
|
||||||
}
|
|
||||||
if !filepath.IsAbs(filename) {
|
|
||||||
startPath := u.String(vm.GetData("startPath"))
|
|
||||||
if startPath != "" {
|
|
||||||
tryFilename := filepath.Join(startPath, filename)
|
|
||||||
if u.FileExists(tryFilename) {
|
|
||||||
return tryFilename
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return filename
|
|
||||||
}
|
|
||||||
|
|
||||||
func MakeArgs(args *goja.FunctionCall, vm *goja.Runtime) *Arr {
|
func MakeArgs(args *goja.FunctionCall, vm *goja.Runtime) *Arr {
|
||||||
return &Arr{
|
return &Arr{
|
||||||
This: args.This,
|
This: args.This,
|
||||||
@ -176,7 +159,7 @@ func (args *Arr) Map2Array(index int) []any {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (args *Arr) Path(index int) string {
|
func (args *Arr) Path(index int) string {
|
||||||
return FindPath(args.vm, args.Str(index))
|
return FixPath(args.vm, args.Str(index))
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetFunc(v goja.Value) goja.Callable {
|
func GetFunc(v goja.Value) goja.Callable {
|
||||||
@ -296,7 +279,7 @@ func (obj *Obj) Map(name string) map[string]any {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (obj *Obj) Path(name string) string {
|
func (obj *Obj) Path(name string) string {
|
||||||
return FindPath(obj.vm, obj.Str(name))
|
return FixPath(obj.vm, obj.Str(name))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (obj *Obj) Func(name string) goja.Callable {
|
func (obj *Obj) Func(name string) goja.Callable {
|
||||||
|
|||||||
@ -101,6 +101,7 @@ func go2js(in any, n int, vm *goja.Runtime) any {
|
|||||||
return in
|
return in
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 将go的函数转换为js的函数
|
||||||
t := v.Type()
|
t := v.Type()
|
||||||
return func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value {
|
return func(argsIn goja.FunctionCall, vm *goja.Runtime) goja.Value {
|
||||||
needArgs := make([]reflect.Type, 0)
|
needArgs := make([]reflect.Type, 0)
|
||||||
@ -108,6 +109,7 @@ func go2js(in any, n int, vm *goja.Runtime) any {
|
|||||||
needArgsIndex := map[int]int{}
|
needArgsIndex := map[int]int{}
|
||||||
hasLastVariadicArg := false
|
hasLastVariadicArg := false
|
||||||
|
|
||||||
|
// fmt.Println(t.NumIn(), t.String(), u.BYellow(u.JsonP(argsIn.Arguments)))
|
||||||
for i := 0; i < t.NumIn(); i++ {
|
for i := 0; i < t.NumIn(); i++ {
|
||||||
inTypeString := t.In(i).String()
|
inTypeString := t.In(i).String()
|
||||||
|
|
||||||
@ -153,6 +155,7 @@ func go2js(in any, n int, vm *goja.Runtime) any {
|
|||||||
}
|
}
|
||||||
|
|
||||||
funcType := realNeedArgType
|
funcType := realNeedArgType
|
||||||
|
// 传入的参数是函数,自动转换为go函数
|
||||||
argValue = MakeFunc(vm, funcType, jsFunc, argsIn.This)
|
argValue = MakeFunc(vm, funcType, jsFunc, argsIn.This)
|
||||||
// argValue = reflect.MakeFunc(funcType, func(goArgs []reflect.Value) []reflect.Value {
|
// argValue = reflect.MakeFunc(funcType, func(goArgs []reflect.Value) []reflect.Value {
|
||||||
// ins := make([]goja.Value, 0)
|
// ins := make([]goja.Value, 0)
|
||||||
@ -236,7 +239,8 @@ func go2js(in any, n int, vm *goja.Runtime) any {
|
|||||||
jsValue := argsIn.Arguments[i]
|
jsValue := argsIn.Arguments[i]
|
||||||
anyV := jsValue.Export()
|
anyV := jsValue.Export()
|
||||||
recovered := false
|
recovered := false
|
||||||
if jsValue.ExportType().Kind() == reflect.Map {
|
et := jsValue.ExportType()
|
||||||
|
if et != nil && et.Kind() == reflect.Map {
|
||||||
mapV := reflect.ValueOf(anyV)
|
mapV := reflect.ValueOf(anyV)
|
||||||
// 尝试使用传递的objId直接恢复对象
|
// 尝试使用传递的objId直接恢复对象
|
||||||
if mapV.IsValid() && !mapV.IsNil() {
|
if mapV.IsValid() && !mapV.IsNil() {
|
||||||
@ -351,7 +355,8 @@ func go2js(in any, n int, vm *goja.Runtime) any {
|
|||||||
}
|
}
|
||||||
outs = append(outs, mapMap)
|
outs = append(outs, mapMap)
|
||||||
} else {
|
} else {
|
||||||
outs = append(outs, outValue.Interface())
|
// 其他返回内容转换JS(主要是Func自动转换)
|
||||||
|
outs = append(outs, ToJS(outValue.Interface()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if len(outs) == 1 {
|
if len(outs) == 1 {
|
||||||
|
|||||||
11
go.mod
11
go.mod
@ -3,20 +3,25 @@ module apigo.cc/gojs
|
|||||||
go 1.24.0
|
go 1.24.0
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
github.com/Masterminds/semver/v3 v3.2.1
|
||||||
github.com/dlclark/regexp2 v1.11.5
|
github.com/dlclark/regexp2 v1.11.5
|
||||||
|
github.com/dop251/base64dec v0.0.0-20231022112746-c6c9f9a96217
|
||||||
github.com/go-sourcemap/sourcemap v2.1.4+incompatible
|
github.com/go-sourcemap/sourcemap v2.1.4+incompatible
|
||||||
github.com/google/pprof v0.0.0-20250903194437-c28834ac2320
|
github.com/google/pprof v0.0.0-20250903194437-c28834ac2320
|
||||||
github.com/ssgo/log v1.7.9
|
github.com/ssgo/log v1.7.9
|
||||||
github.com/ssgo/tool v0.4.29
|
github.com/ssgo/tool v0.4.29
|
||||||
github.com/ssgo/u v1.7.23
|
github.com/ssgo/u v1.7.23
|
||||||
golang.org/x/net v0.44.0
|
go.uber.org/goleak v1.3.0
|
||||||
golang.org/x/text v0.29.0
|
golang.org/x/net v0.47.0
|
||||||
|
golang.org/x/text v0.31.0
|
||||||
|
gopkg.in/yaml.v2 v2.4.0
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/fsnotify/fsnotify v1.9.0 // indirect
|
github.com/fsnotify/fsnotify v1.9.0 // indirect
|
||||||
|
github.com/kr/text v0.2.0 // indirect
|
||||||
github.com/ssgo/config v1.7.10 // indirect
|
github.com/ssgo/config v1.7.10 // indirect
|
||||||
github.com/ssgo/standard v1.7.7 // indirect
|
github.com/ssgo/standard v1.7.7 // indirect
|
||||||
golang.org/x/sys v0.36.0 // indirect
|
golang.org/x/sys v0.38.0 // indirect
|
||||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
15
goja/LICENSE
Normal file
15
goja/LICENSE
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
Copyright (c) 2016 Dmitry Panov
|
||||||
|
|
||||||
|
Copyright (c) 2012 Robert Krimen
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
|
||||||
|
documentation files (the "Software"), to deal in the Software without restriction, including without limitation
|
||||||
|
the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
|
||||||
|
permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
|
||||||
|
WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
||||||
|
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
335
goja/README.md
Normal file
335
goja/README.md
Normal file
@ -0,0 +1,335 @@
|
|||||||
|
goja
|
||||||
|
====
|
||||||
|
|
||||||
|
ECMAScript 5.1(+) implementation in Go.
|
||||||
|
|
||||||
|
[](https://pkg.go.dev/apigo.cc/gojs/goja)
|
||||||
|
|
||||||
|
Goja is an implementation of ECMAScript 5.1 in pure Go with emphasis on standard compliance and
|
||||||
|
performance.
|
||||||
|
|
||||||
|
This project was largely inspired by [otto](https://github.com/robertkrimen/otto).
|
||||||
|
|
||||||
|
The minimum required Go version is 1.20.
|
||||||
|
|
||||||
|
Features
|
||||||
|
--------
|
||||||
|
|
||||||
|
* Full ECMAScript 5.1 support (including regex and strict mode).
|
||||||
|
* Passes nearly all [tc39 tests](https://github.com/tc39/test262) for the features implemented so far. The goal is to
|
||||||
|
pass all of them. See .tc39_test262_checkout.sh for the latest working commit id.
|
||||||
|
* Capable of running Babel, Typescript compiler and pretty much anything written in ES5.
|
||||||
|
* Sourcemaps.
|
||||||
|
* Most of ES6 functionality, still work in progress, see https://apigo.cc/gojs/goja/milestone/1?closed=1
|
||||||
|
|
||||||
|
Known incompatibilities and caveats
|
||||||
|
-----------------------------------
|
||||||
|
|
||||||
|
### WeakMap
|
||||||
|
WeakMap is implemented by embedding references to the values into the keys. This means that as long
|
||||||
|
as the key is reachable all values associated with it in any weak maps also remain reachable and therefore
|
||||||
|
cannot be garbage collected even if they are not otherwise referenced, even after the WeakMap is gone.
|
||||||
|
The reference to the value is dropped either when the key is explicitly removed from the WeakMap or when the
|
||||||
|
key becomes unreachable.
|
||||||
|
|
||||||
|
To illustrate this:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
var m = new WeakMap();
|
||||||
|
var key = {};
|
||||||
|
var value = {/* a very large object */};
|
||||||
|
m.set(key, value);
|
||||||
|
value = undefined;
|
||||||
|
m = undefined; // The value does NOT become garbage-collectable at this point
|
||||||
|
key = undefined; // Now it does
|
||||||
|
// m.delete(key); // This would work too
|
||||||
|
```
|
||||||
|
|
||||||
|
The reason for it is the limitation of the Go runtime. At the time of writing (version 1.15) having a finalizer
|
||||||
|
set on an object which is part of a reference cycle makes the whole cycle non-garbage-collectable. The solution
|
||||||
|
above is the only reasonable way I can think of without involving finalizers. This is the third attempt
|
||||||
|
(see https://apigo.cc/gojs/goja/issues/250 and https://apigo.cc/gojs/goja/issues/199 for more details).
|
||||||
|
|
||||||
|
Note, this does not have any effect on the application logic, but may cause a higher-than-expected memory usage.
|
||||||
|
|
||||||
|
### WeakRef and FinalizationRegistry
|
||||||
|
For the reason mentioned above implementing WeakRef and FinalizationRegistry does not seem to be possible at this stage.
|
||||||
|
|
||||||
|
### JSON
|
||||||
|
`JSON.parse()` uses the standard Go library which operates in UTF-8. Therefore, it cannot correctly parse broken UTF-16
|
||||||
|
surrogate pairs, for example:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
JSON.parse(`"\\uD800"`).charCodeAt(0).toString(16) // returns "fffd" instead of "d800"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Date
|
||||||
|
Conversion from calendar date to epoch timestamp uses the standard Go library which uses `int`, rather than `float` as per
|
||||||
|
ECMAScript specification. This means if you pass arguments that overflow int to the `Date()` constructor or if there is
|
||||||
|
an integer overflow, the result will be incorrect, for example:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
Date.UTC(1970, 0, 1, 80063993375, 29, 1, -288230376151711740) // returns 29256 instead of 29312
|
||||||
|
```
|
||||||
|
|
||||||
|
FAQ
|
||||||
|
---
|
||||||
|
|
||||||
|
### How fast is it?
|
||||||
|
|
||||||
|
Although it's faster than many scripting language implementations in Go I have seen
|
||||||
|
(for example it's 6-7 times faster than otto on average) it is not a
|
||||||
|
replacement for V8 or SpiderMonkey or any other general-purpose JavaScript engine.
|
||||||
|
You can find some benchmarks [here](https://apigo.cc/gojs/goja/issues/2).
|
||||||
|
|
||||||
|
### Why would I want to use it over a V8 wrapper?
|
||||||
|
|
||||||
|
It greatly depends on your usage scenario. If most of the work is done in javascript
|
||||||
|
(for example crypto or any other heavy calculations) you are definitely better off with V8.
|
||||||
|
|
||||||
|
If you need a scripting language that drives an engine written in Go so that
|
||||||
|
you need to make frequent calls between Go and javascript passing complex data structures
|
||||||
|
then the cgo overhead may outweigh the benefits of having a faster javascript engine.
|
||||||
|
|
||||||
|
Because it's written in pure Go there are no cgo dependencies, it's very easy to build and it
|
||||||
|
should run on any platform supported by Go.
|
||||||
|
|
||||||
|
It gives you a much better control over execution environment so can be useful for research.
|
||||||
|
|
||||||
|
### Is it goroutine-safe?
|
||||||
|
|
||||||
|
No. An instance of goja.Runtime can only be used by a single goroutine
|
||||||
|
at a time. You can create as many instances of Runtime as you like but
|
||||||
|
it's not possible to pass object values between runtimes.
|
||||||
|
|
||||||
|
### Where is setTimeout()/setInterval()?
|
||||||
|
|
||||||
|
setTimeout() and setInterval() are common functions to provide concurrent execution in ECMAScript environments, but the two functions are not part of the ECMAScript standard.
|
||||||
|
Browsers and NodeJS just happen to provide similar, but not identical, functions. The hosting application need to control the environment for concurrent execution, e.g. an event loop, and supply the functionality to script code.
|
||||||
|
|
||||||
|
There is a [separate project](https://apigo.cc/gojs/goja_nodejs) aimed at providing some NodeJS functionality,
|
||||||
|
and it includes an event loop.
|
||||||
|
|
||||||
|
### Can you implement (feature X from ES6 or higher)?
|
||||||
|
|
||||||
|
I will be adding features in their dependency order and as quickly as time permits. Please do not ask
|
||||||
|
for ETAs. Features that are open in the [milestone](https://apigo.cc/gojs/goja/milestone/1) are either in progress
|
||||||
|
or will be worked on next.
|
||||||
|
|
||||||
|
The ongoing work is done in separate feature branches which are merged into master when appropriate.
|
||||||
|
Every commit in these branches represents a relatively stable state (i.e. it compiles and passes all enabled tc39 tests),
|
||||||
|
however because the version of tc39 tests I use is quite old, it may be not as well tested as the ES5.1 functionality. Because there are (usually) no major breaking changes between ECMAScript revisions
|
||||||
|
it should not break your existing code. You are encouraged to give it a try and report any bugs found. Please do not submit fixes though without discussing it first, as the code could be changed in the meantime.
|
||||||
|
|
||||||
|
### How do I contribute?
|
||||||
|
|
||||||
|
Before submitting a pull request please make sure that:
|
||||||
|
|
||||||
|
- You followed ECMA standard as close as possible. If adding a new feature make sure you've read the specification,
|
||||||
|
do not just base it on a couple of examples that work fine.
|
||||||
|
- Your change does not have a significant negative impact on performance (unless it's a bugfix and it's unavoidable)
|
||||||
|
- It passes all relevant tc39 tests.
|
||||||
|
|
||||||
|
Current Status
|
||||||
|
--------------
|
||||||
|
|
||||||
|
* There should be no breaking changes in the API, however it may be extended.
|
||||||
|
* Some of the AnnexB functionality is missing.
|
||||||
|
|
||||||
|
Basic Example
|
||||||
|
-------------
|
||||||
|
|
||||||
|
Run JavaScript and get the result value.
|
||||||
|
|
||||||
|
```go
|
||||||
|
vm := goja.New()
|
||||||
|
v, err := vm.RunString("2 + 2")
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
if num := v.Export().(int64); num != 4 {
|
||||||
|
panic(num)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Passing Values to JS
|
||||||
|
--------------------
|
||||||
|
Any Go value can be passed to JS using Runtime.ToValue() method. See the method's [documentation](https://pkg.go.dev/apigo.cc/gojs/goja#Runtime.ToValue) for more details.
|
||||||
|
|
||||||
|
Exporting Values from JS
|
||||||
|
------------------------
|
||||||
|
A JS value can be exported into its default Go representation using Value.Export() method.
|
||||||
|
|
||||||
|
Alternatively it can be exported into a specific Go variable using [Runtime.ExportTo()](https://pkg.go.dev/apigo.cc/gojs/goja#Runtime.ExportTo) method.
|
||||||
|
|
||||||
|
Within a single export operation the same Object will be represented by the same Go value (either the same map, slice or
|
||||||
|
a pointer to the same struct). This includes circular objects and makes it possible to export them.
|
||||||
|
|
||||||
|
Calling JS functions from Go
|
||||||
|
----------------------------
|
||||||
|
There are 2 approaches:
|
||||||
|
|
||||||
|
- Using [AssertFunction()](https://pkg.go.dev/apigo.cc/gojs/goja#AssertFunction):
|
||||||
|
```go
|
||||||
|
const SCRIPT = `
|
||||||
|
function sum(a, b) {
|
||||||
|
return +a + b;
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
vm := goja.New()
|
||||||
|
_, err := vm.RunString(SCRIPT)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
sum, ok := goja.AssertFunction(vm.Get("sum"))
|
||||||
|
if !ok {
|
||||||
|
panic("Not a function")
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err := sum(goja.Undefined(), vm.ToValue(40), vm.ToValue(2))
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
fmt.Println(res)
|
||||||
|
// Output: 42
|
||||||
|
```
|
||||||
|
- Using [Runtime.ExportTo()](https://pkg.go.dev/apigo.cc/gojs/goja#Runtime.ExportTo):
|
||||||
|
```go
|
||||||
|
const SCRIPT = `
|
||||||
|
function sum(a, b) {
|
||||||
|
return +a + b;
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
vm := goja.New()
|
||||||
|
_, err := vm.RunString(SCRIPT)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var sum func(int, int) int
|
||||||
|
err = vm.ExportTo(vm.Get("sum"), &sum)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println(sum(40, 2)) // note, _this_ value in the function will be undefined.
|
||||||
|
// Output: 42
|
||||||
|
```
|
||||||
|
|
||||||
|
The first one is more low level and allows specifying _this_ value, whereas the second one makes the function look like
|
||||||
|
a normal Go function.
|
||||||
|
|
||||||
|
Mapping struct field and method names
|
||||||
|
-------------------------------------
|
||||||
|
By default, the names are passed through as is which means they are capitalised. This does not match
|
||||||
|
the standard JavaScript naming convention, so if you need to make your JS code look more natural or if you are
|
||||||
|
dealing with a 3rd party library, you can use a [FieldNameMapper](https://pkg.go.dev/apigo.cc/gojs/goja#FieldNameMapper):
|
||||||
|
|
||||||
|
```go
|
||||||
|
vm := goja.New()
|
||||||
|
vm.SetFieldNameMapper(TagFieldNameMapper("json", true))
|
||||||
|
type S struct {
|
||||||
|
Field int `json:"field"`
|
||||||
|
}
|
||||||
|
vm.Set("s", S{Field: 42})
|
||||||
|
res, _ := vm.RunString(`s.field`) // without the mapper it would have been s.Field
|
||||||
|
fmt.Println(res.Export())
|
||||||
|
// Output: 42
|
||||||
|
```
|
||||||
|
|
||||||
|
There are two standard mappers: [TagFieldNameMapper](https://pkg.go.dev/apigo.cc/gojs/goja#TagFieldNameMapper) and
|
||||||
|
[UncapFieldNameMapper](https://pkg.go.dev/apigo.cc/gojs/goja#UncapFieldNameMapper), or you can use your own implementation.
|
||||||
|
|
||||||
|
Native Constructors
|
||||||
|
-------------------
|
||||||
|
|
||||||
|
In order to implement a constructor function in Go use `func (goja.ConstructorCall) *goja.Object`.
|
||||||
|
See [Runtime.ToValue()](https://pkg.go.dev/apigo.cc/gojs/goja#Runtime.ToValue) documentation for more details.
|
||||||
|
|
||||||
|
Regular Expressions
|
||||||
|
-------------------
|
||||||
|
|
||||||
|
Goja uses the embedded Go regexp library where possible, otherwise it falls back to [regexp2](https://github.com/dlclark/regexp2).
|
||||||
|
|
||||||
|
Exceptions
|
||||||
|
----------
|
||||||
|
|
||||||
|
Any exception thrown in JavaScript is returned as an error of type *Exception. It is possible to extract the value thrown
|
||||||
|
by using the Value() method:
|
||||||
|
|
||||||
|
```go
|
||||||
|
vm := goja.New()
|
||||||
|
_, err := vm.RunString(`
|
||||||
|
|
||||||
|
throw("Test");
|
||||||
|
|
||||||
|
`)
|
||||||
|
|
||||||
|
if jserr, ok := err.(*Exception); ok {
|
||||||
|
if jserr.Value().Export() != "Test" {
|
||||||
|
panic("wrong value")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
panic("wrong type")
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
If a native Go function panics with a Value, it is thrown as a Javascript exception (and therefore can be caught):
|
||||||
|
|
||||||
|
```go
|
||||||
|
var vm *Runtime
|
||||||
|
|
||||||
|
func Test() {
|
||||||
|
panic(vm.ToValue("Error"))
|
||||||
|
}
|
||||||
|
|
||||||
|
vm = goja.New()
|
||||||
|
vm.Set("Test", Test)
|
||||||
|
_, err := vm.RunString(`
|
||||||
|
|
||||||
|
try {
|
||||||
|
Test();
|
||||||
|
} catch(e) {
|
||||||
|
if (e !== "Error") {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
`)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Interrupting
|
||||||
|
------------
|
||||||
|
|
||||||
|
```go
|
||||||
|
func TestInterrupt(t *testing.T) {
|
||||||
|
const SCRIPT = `
|
||||||
|
var i = 0;
|
||||||
|
for (;;) {
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
vm := goja.New()
|
||||||
|
time.AfterFunc(200 * time.Millisecond, func() {
|
||||||
|
vm.Interrupt("halt")
|
||||||
|
})
|
||||||
|
|
||||||
|
_, err := vm.RunString(SCRIPT)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("Err is nil")
|
||||||
|
}
|
||||||
|
// err is of type *InterruptError and its Value() method returns whatever has been passed to vm.Interrupt()
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
NodeJS Compatibility
|
||||||
|
--------------------
|
||||||
|
|
||||||
|
There is a [separate project](https://apigo.cc/gojs/goja_nodejs) aimed at providing some of the NodeJS functionality.
|
||||||
264
goja/array_sparse_test.go
Normal file
264
goja/array_sparse_test.go
Normal file
@ -0,0 +1,264 @@
|
|||||||
|
package goja
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestSparseArraySetLengthWithPropItems(t *testing.T) {
|
||||||
|
const SCRIPT = `
|
||||||
|
var a = [1,2,3,4];
|
||||||
|
a[100000] = 5;
|
||||||
|
var thrown = false;
|
||||||
|
|
||||||
|
Object.defineProperty(a, "2", {value: 42, configurable: false, writable: false});
|
||||||
|
try {
|
||||||
|
Object.defineProperty(a, "length", {value: 0, writable: false});
|
||||||
|
} catch (e) {
|
||||||
|
thrown = e instanceof TypeError;
|
||||||
|
}
|
||||||
|
thrown && a.length === 3;
|
||||||
|
`
|
||||||
|
|
||||||
|
testScript(SCRIPT, valueTrue, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSparseArraySwitch(t *testing.T) {
|
||||||
|
vm := New()
|
||||||
|
_, err := vm.RunString(`
|
||||||
|
var a = [];
|
||||||
|
a[20470] = 5; // switch to sparse`)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
a := vm.Get("a").(*Object)
|
||||||
|
if _, ok := a.self.(*sparseArrayObject); !ok {
|
||||||
|
t.Fatal("1: array is not sparse")
|
||||||
|
}
|
||||||
|
_, err = vm.RunString(`
|
||||||
|
var cutoffIdx = Math.round(20470 - 20470/8);
|
||||||
|
for (var i = a.length - 1; i >= cutoffIdx; i--) {
|
||||||
|
a[i] = i;
|
||||||
|
}
|
||||||
|
|
||||||
|
// At this point it will have switched to a normal array
|
||||||
|
if (a.length != 20471) {
|
||||||
|
throw new Error("Invalid length: " + a.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var i = 0; i < cutoffIdx; i++) {
|
||||||
|
if (a[i] !== undefined) {
|
||||||
|
throw new Error("Invalid value at " + i + ": " + a[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var i = cutoffIdx; i < a.length; i++) {
|
||||||
|
if (a[i] !== i) {
|
||||||
|
throw new Error("Invalid value at " + i + ": " + a[i]);
|
||||||
|
}
|
||||||
|
}`)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if _, ok := a.self.(*arrayObject); !ok {
|
||||||
|
t.Fatal("2: array is not normal")
|
||||||
|
}
|
||||||
|
_, err = vm.RunString(`
|
||||||
|
// Now try to expand. Should stay a normal array
|
||||||
|
a[20471] = 20471;
|
||||||
|
if (a.length != 20472) {
|
||||||
|
throw new Error("Invalid length: " + a.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var i = 0; i < cutoffIdx; i++) {
|
||||||
|
if (a[i] !== undefined) {
|
||||||
|
throw new Error("Invalid value at " + i + ": " + a[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var i = cutoffIdx; i < a.length; i++) {
|
||||||
|
if (a[i] !== i) {
|
||||||
|
throw new Error("Invalid value at " + i + ": " + a[i]);
|
||||||
|
}
|
||||||
|
}`)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if _, ok := a.self.(*arrayObject); !ok {
|
||||||
|
t.Fatal("3: array is not normal")
|
||||||
|
}
|
||||||
|
_, err = vm.RunString(`
|
||||||
|
// Delete enough elements for it to become sparse again.
|
||||||
|
var cutoffIdx1 = Math.round(20472 - 20472/10);
|
||||||
|
for (var i = cutoffIdx; i < cutoffIdx1; i++) {
|
||||||
|
delete a[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
// This should switch it back to sparse.
|
||||||
|
a[25590] = 25590;
|
||||||
|
if (a.length != 25591) {
|
||||||
|
throw new Error("Invalid length: " + a.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var i = 0; i < cutoffIdx1; i++) {
|
||||||
|
if (a[i] !== undefined) {
|
||||||
|
throw new Error("Invalid value at " + i + ": " + a[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var i = cutoffIdx1; i < 20472; i++) {
|
||||||
|
if (a[i] !== i) {
|
||||||
|
throw new Error("Invalid value at " + i + ": " + a[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var i = 20472; i < 25590; i++) {
|
||||||
|
if (a[i] !== undefined) {
|
||||||
|
throw new Error("Invalid value at " + i + ": " + a[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (a[25590] !== 25590) {
|
||||||
|
throw new Error("Invalid value at 25590: " + a[25590]);
|
||||||
|
}
|
||||||
|
`)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if _, ok := a.self.(*sparseArrayObject); !ok {
|
||||||
|
t.Fatal("4: array is not sparse")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSparseArrayOwnKeys(t *testing.T) {
|
||||||
|
const SCRIPT = `
|
||||||
|
var a1 = [];
|
||||||
|
a1[500000] = 1;
|
||||||
|
var seen = false;
|
||||||
|
var count = 0;
|
||||||
|
var keys = Object.keys(a1);
|
||||||
|
keys.length === 1 && keys[0] === "500000";
|
||||||
|
`
|
||||||
|
|
||||||
|
testScript(SCRIPT, valueTrue, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSparseArrayEnumerate(t *testing.T) {
|
||||||
|
const SCRIPT = `
|
||||||
|
var a1 = [];
|
||||||
|
a1[500000] = 1;
|
||||||
|
var seen = false;
|
||||||
|
var count = 0;
|
||||||
|
for (var i in a1) {
|
||||||
|
if (i === "500000") {
|
||||||
|
if (seen) {
|
||||||
|
throw new Error("seen twice");
|
||||||
|
}
|
||||||
|
seen = true;
|
||||||
|
}
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
seen && count === 1;
|
||||||
|
`
|
||||||
|
|
||||||
|
testScript(SCRIPT, valueTrue, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestArraySparseMaxLength(t *testing.T) {
|
||||||
|
const SCRIPT = `
|
||||||
|
var a = [];
|
||||||
|
a[4294967294]=1;
|
||||||
|
a.length === 4294967295 && a[4294967294] === 1;
|
||||||
|
`
|
||||||
|
|
||||||
|
testScript(SCRIPT, valueTrue, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestArraySparseExportProps(t *testing.T) {
|
||||||
|
vm := New()
|
||||||
|
proto := vm.NewArray()
|
||||||
|
for _, idx := range []string{"0", "500", "9999", "10001", "20471"} {
|
||||||
|
err := proto.Set(idx, true)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
arr := vm.NewArray()
|
||||||
|
err := arr.SetPrototype(proto)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
err = arr.DefineDataProperty("20470", vm.ToValue(true), FLAG_TRUE, FLAG_FALSE, FLAG_TRUE)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
err = arr.DefineDataProperty("10000", vm.ToValue(true), FLAG_TRUE, FLAG_FALSE, FLAG_TRUE)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
err = arr.Set("length", 20472)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
actual := arr.Export()
|
||||||
|
if actualArr, ok := actual.([]interface{}); ok {
|
||||||
|
if len(actualArr) == 20472 {
|
||||||
|
expectedIdx := map[int]struct{}{
|
||||||
|
0: {},
|
||||||
|
500: {},
|
||||||
|
9999: {},
|
||||||
|
10000: {},
|
||||||
|
10001: {},
|
||||||
|
20470: {},
|
||||||
|
20471: {},
|
||||||
|
}
|
||||||
|
for i, v := range actualArr {
|
||||||
|
if _, exists := expectedIdx[i]; exists {
|
||||||
|
if v != true {
|
||||||
|
t.Fatalf("Expected true at %d, got %v", i, v)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if v != nil {
|
||||||
|
t.Fatalf("Expected nil at %d, got %v", i, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
t.Fatalf("Expected len 20471, actual: %d", len(actualArr))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
t.Fatalf("Invalid export type: %T", actual)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSparseArrayExportToSlice(t *testing.T) {
|
||||||
|
vm := New()
|
||||||
|
arr := vm.NewArray()
|
||||||
|
err := arr.Set("20470", 120470)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
err = arr.DefineDataProperty("20471", vm.ToValue(220471), FLAG_TRUE, FLAG_FALSE, FLAG_TRUE)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
var exp []int
|
||||||
|
err = vm.ExportTo(arr, &exp)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if len(exp) != 20472 {
|
||||||
|
t.Fatalf("len: %d", len(exp))
|
||||||
|
}
|
||||||
|
if e := exp[20470]; e != 120470 {
|
||||||
|
t.Fatalf("20470: %d", e)
|
||||||
|
}
|
||||||
|
if e := exp[20471]; e != 220471 {
|
||||||
|
t.Fatalf("20471: %d", e)
|
||||||
|
}
|
||||||
|
for i := 0; i < 20470; i++ {
|
||||||
|
if exp[i] != 0 {
|
||||||
|
t.Fatalf("at %d: %d", i, exp[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
133
goja/array_test.go
Normal file
133
goja/array_test.go
Normal file
@ -0,0 +1,133 @@
|
|||||||
|
package goja
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestArray1(t *testing.T) {
|
||||||
|
r := &Runtime{}
|
||||||
|
a := r.newArray(nil)
|
||||||
|
a.setOwnIdx(valueInt(0), asciiString("test"), true)
|
||||||
|
if l := a.getStr("length", nil).ToInteger(); l != 1 {
|
||||||
|
t.Fatalf("Unexpected length: %d", l)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestArrayExportProps(t *testing.T) {
|
||||||
|
vm := New()
|
||||||
|
arr := vm.NewArray()
|
||||||
|
err := arr.DefineDataProperty("0", vm.ToValue(true), FLAG_TRUE, FLAG_FALSE, FLAG_TRUE)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
actual := arr.Export()
|
||||||
|
expected := []interface{}{true}
|
||||||
|
if !reflect.DeepEqual(actual, expected) {
|
||||||
|
t.Fatalf("Expected: %#v, actual: %#v", expected, actual)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestArrayCanonicalIndex(t *testing.T) {
|
||||||
|
const SCRIPT = `
|
||||||
|
var a = [];
|
||||||
|
a["00"] = 1;
|
||||||
|
a["01"] = 2;
|
||||||
|
if (a[0] !== undefined) {
|
||||||
|
throw new Error("a[0]");
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
testScript(SCRIPT, _undefined, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkArrayGetStr(b *testing.B) {
|
||||||
|
b.StopTimer()
|
||||||
|
r := New()
|
||||||
|
v := &Object{runtime: r}
|
||||||
|
|
||||||
|
a := &arrayObject{
|
||||||
|
baseObject: baseObject{
|
||||||
|
val: v,
|
||||||
|
extensible: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
v.self = a
|
||||||
|
|
||||||
|
a.init()
|
||||||
|
|
||||||
|
v.setOwn(valueInt(0), asciiString("test"), false)
|
||||||
|
b.StartTimer()
|
||||||
|
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
a.getStr("0", nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkArrayGet(b *testing.B) {
|
||||||
|
b.StopTimer()
|
||||||
|
r := New()
|
||||||
|
v := &Object{runtime: r}
|
||||||
|
|
||||||
|
a := &arrayObject{
|
||||||
|
baseObject: baseObject{
|
||||||
|
val: v,
|
||||||
|
extensible: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
v.self = a
|
||||||
|
|
||||||
|
a.init()
|
||||||
|
|
||||||
|
var idx Value = valueInt(0)
|
||||||
|
|
||||||
|
v.setOwn(idx, asciiString("test"), false)
|
||||||
|
|
||||||
|
b.StartTimer()
|
||||||
|
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
v.get(idx, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkArrayPut(b *testing.B) {
|
||||||
|
b.StopTimer()
|
||||||
|
r := New()
|
||||||
|
|
||||||
|
v := &Object{runtime: r}
|
||||||
|
|
||||||
|
a := &arrayObject{
|
||||||
|
baseObject: baseObject{
|
||||||
|
val: v,
|
||||||
|
extensible: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
v.self = a
|
||||||
|
|
||||||
|
a.init()
|
||||||
|
|
||||||
|
var idx Value = valueInt(0)
|
||||||
|
var val Value = asciiString("test")
|
||||||
|
|
||||||
|
b.StartTimer()
|
||||||
|
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
v.setOwn(idx, val, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkArraySetEmpty(b *testing.B) {
|
||||||
|
r := New()
|
||||||
|
_ = r.Get("Array").(*Object).Get("prototype").String() // materialise Array.prototype
|
||||||
|
a := r.NewArray(0, 0)
|
||||||
|
values := a.self.(*arrayObject).values
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
values[0] = nil
|
||||||
|
a.self.setOwnIdx(0, valueTrue, true)
|
||||||
|
}
|
||||||
|
}
|
||||||
1068
goja/ast/README.markdown
Normal file
1068
goja/ast/README.markdown
Normal file
File diff suppressed because it is too large
Load Diff
367
goja/builtin_arrray_test.go
Normal file
367
goja/builtin_arrray_test.go
Normal file
@ -0,0 +1,367 @@
|
|||||||
|
package goja
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestArrayProtoProp(t *testing.T) {
|
||||||
|
const SCRIPT = `
|
||||||
|
Object.defineProperty(Array.prototype, '0', {value: 42, configurable: true, writable: false})
|
||||||
|
var a = []
|
||||||
|
a[0] = 1
|
||||||
|
a[0]
|
||||||
|
`
|
||||||
|
|
||||||
|
testScript(SCRIPT, valueInt(42), t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestArrayDelete(t *testing.T) {
|
||||||
|
const SCRIPT = `
|
||||||
|
var a = [1, 2];
|
||||||
|
var deleted = delete a[0];
|
||||||
|
var undef = a[0] === undefined;
|
||||||
|
var len = a.length;
|
||||||
|
|
||||||
|
deleted && undef && len === 2;
|
||||||
|
`
|
||||||
|
|
||||||
|
testScript(SCRIPT, valueTrue, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestArrayDeleteNonexisting(t *testing.T) {
|
||||||
|
const SCRIPT = `
|
||||||
|
Array.prototype[0] = 42;
|
||||||
|
var a = [];
|
||||||
|
delete a[0] && a[0] === 42;
|
||||||
|
`
|
||||||
|
|
||||||
|
testScript(SCRIPT, valueTrue, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestArraySetLength(t *testing.T) {
|
||||||
|
const SCRIPT = `
|
||||||
|
var a = [1, 2];
|
||||||
|
var assert0 = a.length == 2;
|
||||||
|
a.length = "1";
|
||||||
|
a.length = 1.0;
|
||||||
|
a.length = 1;
|
||||||
|
var assert1 = a.length == 1;
|
||||||
|
a.length = 2;
|
||||||
|
var assert2 = a.length == 2;
|
||||||
|
assert0 && assert1 && assert2 && a[1] === undefined;
|
||||||
|
|
||||||
|
`
|
||||||
|
|
||||||
|
testScript(SCRIPT, valueTrue, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestArrayReverseNonOptimisable(t *testing.T) {
|
||||||
|
const SCRIPT = `
|
||||||
|
var a = [];
|
||||||
|
Object.defineProperty(a, "0", {get: function() {return 42}, set: function(v) {Object.defineProperty(a, "0", {value: v + 1, writable: true, configurable: true})}, configurable: true})
|
||||||
|
a[1] = 43;
|
||||||
|
a.reverse();
|
||||||
|
|
||||||
|
a.length === 2 && a[0] === 44 && a[1] === 42;
|
||||||
|
`
|
||||||
|
|
||||||
|
testScript(SCRIPT, valueTrue, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestArrayPushNonOptimisable(t *testing.T) {
|
||||||
|
const SCRIPT = `
|
||||||
|
Object.defineProperty(Object.prototype, "0", {value: 42});
|
||||||
|
var a = [];
|
||||||
|
var thrown = false;
|
||||||
|
try {
|
||||||
|
a.push(1);
|
||||||
|
} catch (e) {
|
||||||
|
thrown = e instanceof TypeError;
|
||||||
|
}
|
||||||
|
thrown;
|
||||||
|
`
|
||||||
|
|
||||||
|
testScript(SCRIPT, valueTrue, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestArraySetLengthWithPropItems(t *testing.T) {
|
||||||
|
const SCRIPT = `
|
||||||
|
var a = [1,2,3,4];
|
||||||
|
var thrown = false;
|
||||||
|
|
||||||
|
Object.defineProperty(a, "2", {value: 42, configurable: false, writable: false});
|
||||||
|
try {
|
||||||
|
Object.defineProperty(a, "length", {value: 0, writable: false});
|
||||||
|
} catch (e) {
|
||||||
|
thrown = e instanceof TypeError;
|
||||||
|
}
|
||||||
|
thrown && a.length === 3;
|
||||||
|
`
|
||||||
|
|
||||||
|
testScript(SCRIPT, valueTrue, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestArrayFrom(t *testing.T) {
|
||||||
|
const SCRIPT = `
|
||||||
|
function checkDestHoles(dest, prefix) {
|
||||||
|
assert(dest !== source, prefix + ": dest !== source");
|
||||||
|
assert.sameValue(dest.length, 3, prefix + ": dest.length");
|
||||||
|
assert.sameValue(dest[0], 1, prefix + ": [0]");
|
||||||
|
assert.sameValue(dest[1], undefined, prefix + ": [1]");
|
||||||
|
assert(dest.hasOwnProperty("1"), prefix + ': hasOwnProperty("1")');
|
||||||
|
assert.sameValue(dest[2], 3, prefix + ": [2]");
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkDest(dest, prefix) {
|
||||||
|
assert(dest !== source, prefix + ": dest !== source");
|
||||||
|
assert.sameValue(dest.length, 3, prefix + ": dest.length");
|
||||||
|
assert.sameValue(dest[0], 1, prefix + ": [0]");
|
||||||
|
assert.sameValue(dest[1], 2, prefix + ": [1]");
|
||||||
|
assert.sameValue(dest[2], 3, prefix + ": [2]");
|
||||||
|
}
|
||||||
|
|
||||||
|
var source = [1,2,3];
|
||||||
|
var srcHoles = [1,,3];
|
||||||
|
|
||||||
|
checkDest(Array.from(source), "std source/std dest");
|
||||||
|
checkDestHoles(Array.from(srcHoles), "std source (holes)/std dest");
|
||||||
|
|
||||||
|
function Iter() {
|
||||||
|
this.idx = 0;
|
||||||
|
}
|
||||||
|
Iter.prototype.next = function() {
|
||||||
|
if (this.idx < source.length) {
|
||||||
|
return {value: source[this.idx++]};
|
||||||
|
} else {
|
||||||
|
return {done: true};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var src = {};
|
||||||
|
src[Symbol.iterator] = function() {
|
||||||
|
return new Iter();
|
||||||
|
}
|
||||||
|
checkDest(Array.from(src), "iter src/std dest");
|
||||||
|
|
||||||
|
src = {0: 1, 2: 3, length: 3};
|
||||||
|
checkDestHoles(Array.from(src), "arrayLike src/std dest");
|
||||||
|
|
||||||
|
function A() {}
|
||||||
|
A.from = Array.from;
|
||||||
|
|
||||||
|
checkDest(A.from(source), "std src/cust dest");
|
||||||
|
checkDestHoles(A.from(srcHoles), "std src (holes)/cust dest");
|
||||||
|
checkDestHoles(A.from(src), "arrayLike src/cust dest");
|
||||||
|
|
||||||
|
function T2() {
|
||||||
|
Object.defineProperty(this, 0, {
|
||||||
|
configurable: false,
|
||||||
|
writable: true,
|
||||||
|
enumerable: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.throws(TypeError, function() {
|
||||||
|
Array.from.call(T2, source);
|
||||||
|
});
|
||||||
|
|
||||||
|
`
|
||||||
|
|
||||||
|
testScriptWithTestLib(SCRIPT, _undefined, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestArrayOf(t *testing.T) {
|
||||||
|
const SCRIPT = `
|
||||||
|
function T1() {
|
||||||
|
Object.preventExtensions(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.throws(TypeError, function() {
|
||||||
|
Array.of.call(T1, 'Bob');
|
||||||
|
});
|
||||||
|
|
||||||
|
function T2() {
|
||||||
|
Object.defineProperty(this, 0, {
|
||||||
|
configurable: false,
|
||||||
|
writable: true,
|
||||||
|
enumerable: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.throws(TypeError, function() {
|
||||||
|
Array.of.call(T2, 'Bob');
|
||||||
|
})
|
||||||
|
|
||||||
|
result = Array.of.call(undefined);
|
||||||
|
assert(
|
||||||
|
result instanceof Array,
|
||||||
|
'this is not a constructor'
|
||||||
|
);
|
||||||
|
|
||||||
|
result = Array.of.call(Math.cos);
|
||||||
|
assert(
|
||||||
|
result instanceof Array,
|
||||||
|
'this is a builtin function with no [[Construct]] slot'
|
||||||
|
);
|
||||||
|
|
||||||
|
`
|
||||||
|
|
||||||
|
testScriptWithTestLib(SCRIPT, _undefined, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUnscopables(t *testing.T) {
|
||||||
|
const SCRIPT = `
|
||||||
|
var keys = [];
|
||||||
|
var _length;
|
||||||
|
with (Array.prototype) {
|
||||||
|
_length = length;
|
||||||
|
keys.push('something');
|
||||||
|
}
|
||||||
|
_length === 0 && keys.length === 1 && keys[0] === "something";
|
||||||
|
`
|
||||||
|
testScript(SCRIPT, valueTrue, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestArraySort(t *testing.T) {
|
||||||
|
const SCRIPT = `
|
||||||
|
assert.throws(TypeError, function() {
|
||||||
|
[1,2].sort(null);
|
||||||
|
}, "null compare function");
|
||||||
|
assert.throws(TypeError, function() {
|
||||||
|
[1,2].sort({});
|
||||||
|
}, "non-callable compare function");
|
||||||
|
`
|
||||||
|
testScriptWithTestLib(SCRIPT, _undefined, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestArraySortNonStdArray(t *testing.T) {
|
||||||
|
const SCRIPT = `
|
||||||
|
const array = [undefined, 'c', /*hole*/, 'b', undefined, /*hole*/, 'a', 'd'];
|
||||||
|
|
||||||
|
Object.defineProperty(array, '2', {
|
||||||
|
get() {
|
||||||
|
array.pop();
|
||||||
|
array.pop();
|
||||||
|
return this.foo;
|
||||||
|
},
|
||||||
|
set(v) {
|
||||||
|
this.foo = v;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
array.sort();
|
||||||
|
|
||||||
|
assert.sameValue(array[0], 'b');
|
||||||
|
assert.sameValue(array[1], 'c');
|
||||||
|
assert.sameValue(array[3], undefined);
|
||||||
|
assert.sameValue(array[4], undefined);
|
||||||
|
assert.sameValue('5' in array, false);
|
||||||
|
assert.sameValue(array.hasOwnProperty('5'), false);
|
||||||
|
assert.sameValue(array.length, 6);
|
||||||
|
assert.sameValue(array.foo, undefined);
|
||||||
|
|
||||||
|
assert.sameValue(array[2], undefined);
|
||||||
|
assert.sameValue(array.length, 4);
|
||||||
|
`
|
||||||
|
testScriptWithTestLib(SCRIPT, _undefined, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestArrayConcat(t *testing.T) {
|
||||||
|
const SCRIPT = `
|
||||||
|
var concat = Array.prototype.concat;
|
||||||
|
var array = [1, 2];
|
||||||
|
var sparseArray = [1, , 2];
|
||||||
|
var nonSpreadableArray = [1, 2];
|
||||||
|
nonSpreadableArray[Symbol.isConcatSpreadable] = false;
|
||||||
|
var arrayLike = { 0: 1, 1: 2, length: 2 };
|
||||||
|
var spreadableArrayLike = { 0: 1, 1: 2, length: 2 };
|
||||||
|
spreadableArrayLike[Symbol.isConcatSpreadable] = true;
|
||||||
|
assert(looksNative(concat));
|
||||||
|
assert(deepEqual(array.concat(), [1, 2]), '#1');
|
||||||
|
assert(deepEqual(sparseArray.concat(), [1, , 2]), '#2');
|
||||||
|
assert(deepEqual(nonSpreadableArray.concat(), [[1, 2]]), '#3');
|
||||||
|
assert(deepEqual(concat.call(arrayLike), [{ 0: 1, 1: 2, length: 2 }]), '#4');
|
||||||
|
assert(deepEqual(concat.call(spreadableArrayLike), [1, 2]), '#5');
|
||||||
|
assert(deepEqual([].concat(array), [1, 2]), '#6');
|
||||||
|
assert(deepEqual([].concat(sparseArray), [1, , 2]), '#7');
|
||||||
|
assert(deepEqual([].concat(nonSpreadableArray), [[1, 2]]), '#8');
|
||||||
|
assert(deepEqual([].concat(arrayLike), [{ 0: 1, 1: 2, length: 2 }]), '#9');
|
||||||
|
assert(deepEqual([].concat(spreadableArrayLike), [1, 2]), '#10');
|
||||||
|
assert(deepEqual(array.concat(sparseArray, nonSpreadableArray, arrayLike, spreadableArrayLike), [
|
||||||
|
1, 2, 1, , 2, [1, 2], { 0: 1, 1: 2, length: 2 }, 1, 2,
|
||||||
|
]), '#11');
|
||||||
|
array = [];
|
||||||
|
array.constructor = {};
|
||||||
|
array.constructor[Symbol.species] = function () {
|
||||||
|
return { foo: 1 };
|
||||||
|
}
|
||||||
|
assert.sameValue(array.concat().foo, 1, '@@species');
|
||||||
|
`
|
||||||
|
testScriptWithTestLibX(SCRIPT, _undefined, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestArrayFlat(t *testing.T) {
|
||||||
|
const SCRIPT = `
|
||||||
|
var array = [1, [2,3,[4,5,6]], [[[[7,8,9]]]]];
|
||||||
|
assert(deepEqual(array.flat(), [1,2,3,[4,5,6],[[[7,8,9]]]]), '#1');
|
||||||
|
assert(deepEqual(array.flat(1), [1,2,3,[4,5,6],[[[7,8,9]]]]), '#2');
|
||||||
|
assert(deepEqual(array.flat(3), [1,2,3,4,5,6,[7,8,9]]), '#3');
|
||||||
|
assert(deepEqual(array.flat(4), [1,2,3,4,5,6,7,8,9]), '#4');
|
||||||
|
assert(deepEqual(array.flat(10), [1,2,3,4,5,6,7,8,9]), '#5');
|
||||||
|
`
|
||||||
|
testScriptWithTestLibX(SCRIPT, _undefined, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestArrayFlatMap(t *testing.T) {
|
||||||
|
const SCRIPT = `
|
||||||
|
var double = function(x) {
|
||||||
|
if (isNaN(x)) {
|
||||||
|
return x
|
||||||
|
}
|
||||||
|
return x * 2
|
||||||
|
}
|
||||||
|
var array = [1, [2,3,[4,5,6]], [[[[7,8,9]]]]];
|
||||||
|
assert(deepEqual(array.flatMap(double), [2,2,3,[4,5,6],[[[7,8,9]]]]), '#1');
|
||||||
|
`
|
||||||
|
testScriptWithTestLibX(SCRIPT, _undefined, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestArrayProto(t *testing.T) {
|
||||||
|
const SCRIPT = `
|
||||||
|
const a = Array.prototype;
|
||||||
|
a.push(1, 2, 3, 4, 5);
|
||||||
|
assert.sameValue(a.length, 5);
|
||||||
|
assert.sameValue(a[0], 1);
|
||||||
|
a.length = 3;
|
||||||
|
assert.sameValue(a.length, 3);
|
||||||
|
assert(compareArray(a, [1, 2, 3]));
|
||||||
|
a.shift();
|
||||||
|
assert.sameValue(a.length, 2);
|
||||||
|
assert(compareArray(a, [2, 3]));
|
||||||
|
`
|
||||||
|
testScriptWithTestLib(SCRIPT, _undefined, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestArrayToSpliced(t *testing.T) {
|
||||||
|
const SCRIPT = `
|
||||||
|
const a = [1, 2, 3];
|
||||||
|
a.push(4)
|
||||||
|
assert(compareArray(a, [1, 2, 3, 4]));
|
||||||
|
const b = a.toSpliced(2)
|
||||||
|
assert(compareArray(a, [1, 2, 3, 4]));
|
||||||
|
assert(compareArray(b, [1, 2]));
|
||||||
|
a.push(5)
|
||||||
|
const c = a.toSpliced(1, 2);
|
||||||
|
assert(compareArray(a, [1, 2, 3, 4, 5]));
|
||||||
|
assert(compareArray(c, [1, 4, 5]));
|
||||||
|
assert(compareArray(a.toSpliced(4, 2, 'a', 'b'), [1, 2, 3, 4, 'a', 'b']));
|
||||||
|
assert(compareArray(a, [1, 2, 3, 4, 5]));
|
||||||
|
assert(compareArray(a.toSpliced(-2, 2), [1, 2, 3]));
|
||||||
|
assert(compareArray(a, [1, 2, 3, 4, 5]));
|
||||||
|
assert(compareArray(a.toSpliced(2, 10), [1, 2]));
|
||||||
|
assert(compareArray(a, [1, 2, 3, 4, 5]));
|
||||||
|
assert(compareArray(a.toSpliced(1, 0, 'a'), [1, 'a', 2, 3, 4, 5]));
|
||||||
|
assert(compareArray(a, [1, 2, 3, 4, 5]));
|
||||||
|
`
|
||||||
|
testScriptWithTestLib(SCRIPT, _undefined, t)
|
||||||
|
}
|
||||||
117
goja/builtin_bigint_test.go
Normal file
117
goja/builtin_bigint_test.go
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
package goja
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math/big"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestBigInt(t *testing.T) {
|
||||||
|
const SCRIPT = `0xabcdef0123456789abcdef0123n`
|
||||||
|
b := new(big.Int)
|
||||||
|
b.SetString("0xabcdef0123456789abcdef0123", 0)
|
||||||
|
testScript(SCRIPT, (*valueBigInt)(b), t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBigIntExportTo(t *testing.T) {
|
||||||
|
vm := New()
|
||||||
|
|
||||||
|
t.Run("bigint exportType", func(t *testing.T) {
|
||||||
|
v, err := vm.RunString(`BigInt(Number.MAX_SAFE_INTEGER + 10);`)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if typ := v.ExportType(); typ != typeBigInt {
|
||||||
|
t.Fatal(typ)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("bigint", func(t *testing.T) {
|
||||||
|
var b big.Int
|
||||||
|
err := vm.ExportTo(vm.ToValue(big.NewInt(10)), &b)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if b.Cmp(big.NewInt(10)) != 0 {
|
||||||
|
t.Fatalf("bigint: %s", b.String())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBigIntFormat(t *testing.T) {
|
||||||
|
const SCRIPT = `
|
||||||
|
assert.sameValue((1n).toString(undefined), "1", "radius undefined");
|
||||||
|
assert.throws(RangeError, () => { (1n).toString(-1); }, "radius -1");
|
||||||
|
assert.throws(RangeError, () => { (1n).toString(37); }, "radius 37");
|
||||||
|
assert.sameValue((1n).toString(2), "1", "radius 2");
|
||||||
|
assert.sameValue((10n).toString(3), "101", "radius 3");
|
||||||
|
`
|
||||||
|
testScriptWithTestLib(SCRIPT, _undefined, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBigIntOperator(t *testing.T) {
|
||||||
|
const SCRIPT = `
|
||||||
|
assert.throws(TypeError, () => { 1 - 1n; }, "mix type add");
|
||||||
|
assert.throws(TypeError, () => { 1n - 1; }, "mix type add");
|
||||||
|
assert.throws(TypeError, () => { 1n + 1; }, "mix type sub");
|
||||||
|
assert.throws(TypeError, () => { 1 + 1n; }, "mix type sub");
|
||||||
|
assert.throws(TypeError, () => { 1 * 1n; }, "mix type mul");
|
||||||
|
assert.throws(TypeError, () => { 1n * 1; }, "mix type mul");
|
||||||
|
assert.throws(TypeError, () => { 1 / 1n; }, "mix type div");
|
||||||
|
assert.throws(TypeError, () => { 1n / 1; }, "mix type div");
|
||||||
|
assert.throws(TypeError, () => { 1 % 1n; }, "mix type mod");
|
||||||
|
assert.throws(TypeError, () => { 1n % 1; }, "mix type mod");
|
||||||
|
assert.throws(TypeError, () => { 1n ** 1; }, "mix type exp");
|
||||||
|
assert.throws(TypeError, () => { 1 ** 1n; }, "mix type exp");
|
||||||
|
assert.throws(TypeError, () => { 1 & 1n; }, "mix type and");
|
||||||
|
assert.throws(TypeError, () => { 1n & 1; }, "mix type and");
|
||||||
|
assert.throws(TypeError, () => { 1 | 1n; }, "mix type or");
|
||||||
|
assert.throws(TypeError, () => { 1n | 1; }, "mix type or");
|
||||||
|
assert.throws(TypeError, () => { 1 ^ 1n; }, "mix type xor");
|
||||||
|
assert.throws(TypeError, () => { 1n ^ 1; }, "mix type xor");
|
||||||
|
assert.throws(TypeError, () => { 1 << 1n; }, "mix type lsh");
|
||||||
|
assert.throws(TypeError, () => { 1n << 1; }, "mix type lsh");
|
||||||
|
assert.throws(TypeError, () => { 1 >> 1n; }, "mix type rsh");
|
||||||
|
assert.throws(TypeError, () => { 1n >> 1; }, "mix type rsh");
|
||||||
|
assert.throws(TypeError, () => { 1 >>> 1n; }, "mix type ursh");
|
||||||
|
assert.throws(TypeError, () => { 1n >>> 1; }, "mix type ursh");
|
||||||
|
|
||||||
|
assert.sameValue(1n + 1n, 2n, "add");
|
||||||
|
assert.sameValue(1n - 1n, 0n, "sub");
|
||||||
|
assert.sameValue(1n * 2n, 2n, "mul");
|
||||||
|
assert.sameValue(1n / 2n, 0n, "div");
|
||||||
|
assert.sameValue(1n % 2n, 1n, "mod");
|
||||||
|
assert.sameValue(1n ** 2n, 1n, "exp");
|
||||||
|
assert.sameValue(1n & 1n, 1n, "and");
|
||||||
|
assert.sameValue(1n | 1n, 1n, "or");
|
||||||
|
assert.sameValue(2n ^ 1n, 3n, "xor");
|
||||||
|
assert.sameValue(1n << 1n, 2n, "lsh");
|
||||||
|
assert.sameValue(4n << -1n, 2n, "neg lsh");
|
||||||
|
assert.sameValue(4n >> 1n, 2n, "rsh");
|
||||||
|
assert.sameValue(2n >> -2n, 8n, "neg rsh");
|
||||||
|
|
||||||
|
let a = 1n;
|
||||||
|
assert.sameValue(++a, 2n, "inc");
|
||||||
|
assert.sameValue(--a, 1n, "dec");
|
||||||
|
|
||||||
|
assert.sameValue(Object(1n) - 1n, 0n, "primitive sub");
|
||||||
|
assert.sameValue(Object(Object(1n)) - 1n, 0n, "primitive sub");
|
||||||
|
assert.sameValue({ [Symbol.toPrimitive]: () => 1n } - 1n, 0n, "primitive sub");
|
||||||
|
assert.sameValue({ valueOf: () => 1n } - 1n, 0n, "valueOf sub");
|
||||||
|
|
||||||
|
assert.sameValue(1n > 0, true, "gt");
|
||||||
|
assert.sameValue(0 > 1n, false, "gt");
|
||||||
|
assert.sameValue(Object(1n) > 0, true, "gt");
|
||||||
|
assert.sameValue(0 > Object(1n), false, "gt");
|
||||||
|
|
||||||
|
assert.sameValue(1n < 0, false, "lt");
|
||||||
|
assert.sameValue(0 < 1n, true, "lt");
|
||||||
|
assert.sameValue(Object(1n) < 0, false, "lt");
|
||||||
|
assert.sameValue(0 < Object(1n), true, "lt");
|
||||||
|
|
||||||
|
assert.sameValue(1n >= 0, true, "ge");
|
||||||
|
assert.sameValue(0 >= 1n, false, "ge");
|
||||||
|
assert.sameValue(1n <= 0, false, "le");
|
||||||
|
assert.sameValue(0 <= 1n, true, "le");
|
||||||
|
`
|
||||||
|
testScriptWithTestLib(SCRIPT, _undefined, t)
|
||||||
|
}
|
||||||
@ -90,8 +90,10 @@ func (r *Runtime) createListFromArrayLike(a Value) []Value {
|
|||||||
|
|
||||||
func (r *Runtime) functionproto_apply(call FunctionCall) Value {
|
func (r *Runtime) functionproto_apply(call FunctionCall) Value {
|
||||||
var args []Value
|
var args []Value
|
||||||
if len(call.Arguments) >= 2 {
|
|
||||||
args = r.createListFromArrayLike(call.Arguments[1])
|
argArray := call.Argument(1)
|
||||||
|
if !IsUndefined(argArray) && !IsNull(argArray) {
|
||||||
|
args = r.createListFromArrayLike(argArray)
|
||||||
}
|
}
|
||||||
|
|
||||||
f := r.toCallable(call.This)
|
f := r.toCallable(call.This)
|
||||||
|
|||||||
21
goja/builtin_function_test.go
Normal file
21
goja/builtin_function_test.go
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
package goja
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestHashbangInFunctionConstructor(t *testing.T) {
|
||||||
|
const SCRIPT = `
|
||||||
|
assert.throws(SyntaxError, function() {
|
||||||
|
new Function("#!")
|
||||||
|
});
|
||||||
|
`
|
||||||
|
testScriptWithTestLib(SCRIPT, _undefined, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFunctionApplyNullArgArray(t *testing.T) {
|
||||||
|
const SCRIPT = `
|
||||||
|
assert.sameValue(0, (function() {return arguments.length}).apply(undefined, null))
|
||||||
|
`
|
||||||
|
testScriptWithTestLib(SCRIPT, _undefined, t)
|
||||||
|
}
|
||||||
21
goja/builtin_global_test.go
Normal file
21
goja/builtin_global_test.go
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
package goja
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestEncodeURI(t *testing.T) {
|
||||||
|
const SCRIPT = `
|
||||||
|
encodeURI('тест')
|
||||||
|
`
|
||||||
|
|
||||||
|
testScript(SCRIPT, asciiString("%D1%82%D0%B5%D1%81%D1%82"), t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDecodeURI(t *testing.T) {
|
||||||
|
const SCRIPT = `
|
||||||
|
decodeURI("http://ru.wikipedia.org/wiki/%d0%ae%D0%bd%D0%B8%D0%BA%D0%BE%D0%B4")
|
||||||
|
`
|
||||||
|
|
||||||
|
testScript(SCRIPT, newStringValue("http://ru.wikipedia.org/wiki/Юникод"), t)
|
||||||
|
}
|
||||||
140
goja/builtin_json_test.go
Normal file
140
goja/builtin_json_test.go
Normal file
@ -0,0 +1,140 @@
|
|||||||
|
package goja
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestJSONMarshalObject(t *testing.T) {
|
||||||
|
vm := New()
|
||||||
|
o := vm.NewObject()
|
||||||
|
o.Set("test", 42)
|
||||||
|
o.Set("testfunc", vm.Get("Error"))
|
||||||
|
b, err := json.Marshal(o)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if string(b) != `{"test":42}` {
|
||||||
|
t.Fatalf("Unexpected value: %s", b)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestJSONMarshalGoDate(t *testing.T) {
|
||||||
|
vm := New()
|
||||||
|
o := vm.NewObject()
|
||||||
|
o.Set("test", time.Unix(86400, 0).UTC())
|
||||||
|
b, err := json.Marshal(o)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if string(b) != `{"test":"1970-01-02T00:00:00Z"}` {
|
||||||
|
t.Fatalf("Unexpected value: %s", b)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestJSONMarshalObjectCircular(t *testing.T) {
|
||||||
|
vm := New()
|
||||||
|
o := vm.NewObject()
|
||||||
|
o.Set("o", o)
|
||||||
|
_, err := json.Marshal(o)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("Expected error")
|
||||||
|
}
|
||||||
|
if !strings.HasSuffix(err.Error(), "Converting circular structure to JSON") {
|
||||||
|
t.Fatalf("Unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestJSONStringifyCircularWrappedGo(t *testing.T) {
|
||||||
|
type CircularType struct {
|
||||||
|
Self *CircularType
|
||||||
|
}
|
||||||
|
vm := New()
|
||||||
|
v := CircularType{}
|
||||||
|
v.Self = &v
|
||||||
|
vm.Set("v", &v)
|
||||||
|
_, err := vm.RunString("JSON.stringify(v)")
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("Expected error")
|
||||||
|
}
|
||||||
|
if !strings.HasPrefix(err.Error(), "TypeError: Converting circular structure to JSON") {
|
||||||
|
t.Fatalf("Unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestJSONParseReviver(t *testing.T) {
|
||||||
|
// example from
|
||||||
|
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/parse
|
||||||
|
const SCRIPT = `
|
||||||
|
JSON.parse('{"p": 5}', function(key, value) {
|
||||||
|
return typeof value === 'number'
|
||||||
|
? value * 2 // return value * 2 for numbers
|
||||||
|
: value // return everything else unchanged
|
||||||
|
})["p"]
|
||||||
|
`
|
||||||
|
|
||||||
|
testScript(SCRIPT, intToValue(10), t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestQuoteMalformedSurrogatePair(t *testing.T) {
|
||||||
|
testScript(`JSON.stringify("\uD800")`, asciiString(`"\ud800"`), t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEOFWrapping(t *testing.T) {
|
||||||
|
vm := New()
|
||||||
|
|
||||||
|
_, err := vm.RunString("JSON.parse('{')")
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("Expected error")
|
||||||
|
}
|
||||||
|
|
||||||
|
if !strings.Contains(err.Error(), "Unexpected end of JSON input") {
|
||||||
|
t.Fatalf("Error doesn't contain human-friendly wrapper: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type testMarshalJSONErrorStruct struct {
|
||||||
|
e error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *testMarshalJSONErrorStruct) MarshalJSON() ([]byte, error) {
|
||||||
|
return nil, s.e
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMarshalJSONError(t *testing.T) {
|
||||||
|
vm := New()
|
||||||
|
v := testMarshalJSONErrorStruct{e: errors.New("test error")}
|
||||||
|
vm.Set("v", &v)
|
||||||
|
_, err := vm.RunString("JSON.stringify(v)")
|
||||||
|
if !errors.Is(err, v.e) {
|
||||||
|
t.Fatalf("Unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkJSONStringify(b *testing.B) {
|
||||||
|
b.StopTimer()
|
||||||
|
vm := New()
|
||||||
|
var createObj func(level int) *Object
|
||||||
|
createObj = func(level int) *Object {
|
||||||
|
o := vm.NewObject()
|
||||||
|
o.Set("field1", "test")
|
||||||
|
o.Set("field2", 42)
|
||||||
|
if level > 0 {
|
||||||
|
level--
|
||||||
|
o.Set("obj1", createObj(level))
|
||||||
|
o.Set("obj2", createObj(level))
|
||||||
|
}
|
||||||
|
return o
|
||||||
|
}
|
||||||
|
|
||||||
|
o := createObj(3)
|
||||||
|
json := vm.Get("JSON").(*Object)
|
||||||
|
stringify, _ := AssertFunction(json.Get("stringify"))
|
||||||
|
b.StartTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
stringify(nil, o)
|
||||||
|
}
|
||||||
|
}
|
||||||
244
goja/builtin_map_test.go
Normal file
244
goja/builtin_map_test.go
Normal file
@ -0,0 +1,244 @@
|
|||||||
|
package goja
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"hash/maphash"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestMapEvilIterator(t *testing.T) {
|
||||||
|
const SCRIPT = `
|
||||||
|
'use strict';
|
||||||
|
var o = {};
|
||||||
|
|
||||||
|
function Iter(value) {
|
||||||
|
this.value = value;
|
||||||
|
this.idx = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
Iter.prototype.next = function() {
|
||||||
|
var idx = this.idx;
|
||||||
|
if (idx === 0) {
|
||||||
|
this.idx++;
|
||||||
|
return this.value;
|
||||||
|
}
|
||||||
|
return {done: true};
|
||||||
|
}
|
||||||
|
|
||||||
|
o[Symbol.iterator] = function() {
|
||||||
|
return new Iter({});
|
||||||
|
}
|
||||||
|
|
||||||
|
assert.throws(TypeError, function() {
|
||||||
|
new Map(o);
|
||||||
|
});
|
||||||
|
|
||||||
|
o[Symbol.iterator] = function() {
|
||||||
|
return new Iter({value: []});
|
||||||
|
}
|
||||||
|
|
||||||
|
function t(prefix) {
|
||||||
|
var m = new Map(o);
|
||||||
|
assert.sameValue(1, m.size, prefix+": m.size");
|
||||||
|
assert.sameValue(true, m.has(undefined), prefix+": m.has(undefined)");
|
||||||
|
assert.sameValue(undefined, m.get(undefined), prefix+": m.get(undefined)");
|
||||||
|
}
|
||||||
|
|
||||||
|
t("standard adder");
|
||||||
|
|
||||||
|
var count = 0;
|
||||||
|
var origSet = Map.prototype.set;
|
||||||
|
|
||||||
|
Map.prototype.set = function() {
|
||||||
|
count++;
|
||||||
|
origSet.apply(this, arguments);
|
||||||
|
}
|
||||||
|
|
||||||
|
t("custom adder");
|
||||||
|
assert.sameValue(1, count, "count");
|
||||||
|
|
||||||
|
undefined;
|
||||||
|
`
|
||||||
|
testScriptWithTestLib(SCRIPT, _undefined, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMapExportToNilMap(t *testing.T) {
|
||||||
|
vm := New()
|
||||||
|
var m map[int]interface{}
|
||||||
|
res, err := vm.RunString("new Map([[1, true]])")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
err = vm.ExportTo(res, &m)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if len(m) != 1 {
|
||||||
|
t.Fatal(m)
|
||||||
|
}
|
||||||
|
if _, exists := m[1]; !exists {
|
||||||
|
t.Fatal(m)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMapExportToNonNilMap(t *testing.T) {
|
||||||
|
vm := New()
|
||||||
|
m := map[int]interface{}{
|
||||||
|
2: true,
|
||||||
|
}
|
||||||
|
res, err := vm.RunString("new Map([[1, true]])")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
err = vm.ExportTo(res, &m)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if len(m) != 1 {
|
||||||
|
t.Fatal(m)
|
||||||
|
}
|
||||||
|
if _, exists := m[1]; !exists {
|
||||||
|
t.Fatal(m)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMapGetAdderGetIteratorOrder(t *testing.T) {
|
||||||
|
const SCRIPT = `
|
||||||
|
let getterCalled = 0;
|
||||||
|
|
||||||
|
class M extends Map {
|
||||||
|
get set() {
|
||||||
|
getterCalled++;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let getIteratorCalled = 0;
|
||||||
|
|
||||||
|
let iterable = {};
|
||||||
|
iterable[Symbol.iterator] = () => {
|
||||||
|
getIteratorCalled++
|
||||||
|
return {
|
||||||
|
next: 1
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
let thrown = false;
|
||||||
|
|
||||||
|
try {
|
||||||
|
new M(iterable);
|
||||||
|
} catch (e) {
|
||||||
|
if (e instanceof TypeError) {
|
||||||
|
thrown = true;
|
||||||
|
} else {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
thrown && getterCalled === 1 && getIteratorCalled === 0;
|
||||||
|
`
|
||||||
|
testScript(SCRIPT, valueTrue, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleObject_Export_map() {
|
||||||
|
vm := New()
|
||||||
|
m, err := vm.RunString(`
|
||||||
|
new Map([[1, true], [2, false]]);
|
||||||
|
`)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
exp := m.Export()
|
||||||
|
fmt.Printf("%T, %v\n", exp, exp)
|
||||||
|
// Output: [][2]interface {}, [[1 true] [2 false]]
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleRuntime_ExportTo_mapToMap() {
|
||||||
|
vm := New()
|
||||||
|
m, err := vm.RunString(`
|
||||||
|
new Map([[1, true], [2, false]]);
|
||||||
|
`)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
exp := make(map[int]bool)
|
||||||
|
err = vm.ExportTo(m, &exp)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
fmt.Println(exp)
|
||||||
|
// Output: map[1:true 2:false]
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleRuntime_ExportTo_mapToSlice() {
|
||||||
|
vm := New()
|
||||||
|
m, err := vm.RunString(`
|
||||||
|
new Map([[1, true], [2, false]]);
|
||||||
|
`)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
exp := make([][]interface{}, 0)
|
||||||
|
err = vm.ExportTo(m, &exp)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
fmt.Println(exp)
|
||||||
|
// Output: [[1 true] [2 false]]
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleRuntime_ExportTo_mapToTypedSlice() {
|
||||||
|
vm := New()
|
||||||
|
m, err := vm.RunString(`
|
||||||
|
new Map([[1, true], [2, false]]);
|
||||||
|
`)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
exp := make([][2]interface{}, 0)
|
||||||
|
err = vm.ExportTo(m, &exp)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
fmt.Println(exp)
|
||||||
|
// Output: [[1 true] [2 false]]
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkMapDelete(b *testing.B) {
|
||||||
|
var key1 Value = asciiString("a")
|
||||||
|
var key2 Value = asciiString("b")
|
||||||
|
one := intToValue(1)
|
||||||
|
two := intToValue(2)
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
m := newOrderedMap(&maphash.Hash{})
|
||||||
|
m.set(key1, one)
|
||||||
|
m.set(key2, two)
|
||||||
|
if !m.remove(key1) {
|
||||||
|
b.Fatal("remove() returned false")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkMapDeleteJS(b *testing.B) {
|
||||||
|
prg, err := Compile("test.js", `
|
||||||
|
var m = new Map([['a',1], ['b', 2]]);
|
||||||
|
|
||||||
|
var result = m.delete('a');
|
||||||
|
|
||||||
|
if (!result || m.size !== 1) {
|
||||||
|
throw new Error("Fail!");
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
false)
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
vm := New()
|
||||||
|
_, err := vm.RunProgram(prg)
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -596,14 +596,17 @@ func (r *Runtime) getPromise() *Object {
|
|||||||
return ret
|
return ret
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Runtime) wrapPromiseReaction(fObj *Object) func(interface{}) {
|
func (r *Runtime) wrapPromiseReaction(fObj *Object) func(interface{}) error {
|
||||||
f, _ := AssertFunction(fObj)
|
f, _ := AssertFunction(fObj)
|
||||||
return func(x interface{}) {
|
return func(x interface{}) error {
|
||||||
_, _ = f(nil, r.ToValue(x))
|
_, err := f(nil, r.ToValue(x))
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewPromise creates and returns a Promise and resolving functions for it.
|
// NewPromise creates and returns a Promise and resolving functions for it.
|
||||||
|
// The returned errors will be uncatchable errors, such as InterruptedError or StackOverflowError, which should be propagated upwards.
|
||||||
|
// Exceptions are handled through [PromiseRejectionTracker].
|
||||||
//
|
//
|
||||||
// WARNING: The returned values are not goroutine-safe and must not be called in parallel with VM running.
|
// WARNING: The returned values are not goroutine-safe and must not be called in parallel with VM running.
|
||||||
// In order to make use of this method you need an event loop such as the one in goja_nodejs (https://apigo.cc/gojs/goja_nodejs)
|
// In order to make use of this method you need an event loop such as the one in goja_nodejs (https://apigo.cc/gojs/goja_nodejs)
|
||||||
@ -618,11 +621,12 @@ func (r *Runtime) wrapPromiseReaction(fObj *Object) func(interface{}) {
|
|||||||
// go func() {
|
// go func() {
|
||||||
// time.Sleep(500 * time.Millisecond) // or perform any other blocking operation
|
// time.Sleep(500 * time.Millisecond) // or perform any other blocking operation
|
||||||
// loop.RunOnLoop(func(*goja.Runtime) { // resolve() must be called on the loop, cannot call it here
|
// loop.RunOnLoop(func(*goja.Runtime) { // resolve() must be called on the loop, cannot call it here
|
||||||
// resolve(result)
|
// err := resolve(result)
|
||||||
|
// // Handle uncatchable errors (e.g. by stopping the loop, panicking or setting a flag)
|
||||||
// })
|
// })
|
||||||
// }()
|
// }()
|
||||||
// }
|
// }
|
||||||
func (r *Runtime) NewPromise() (promise *Promise, resolve func(result interface{}), reject func(reason interface{})) {
|
func (r *Runtime) NewPromise() (promise *Promise, resolve, reject func(reason interface{}) error) {
|
||||||
p := r.newPromise(r.getPromisePrototype())
|
p := r.newPromise(r.getPromisePrototype())
|
||||||
resolveF, rejectF := p.createResolvingFunctions()
|
resolveF, rejectF := p.createResolvingFunctions()
|
||||||
return p, r.wrapPromiseReaction(resolveF), r.wrapPromiseReaction(rejectF)
|
return p, r.wrapPromiseReaction(resolveF), r.wrapPromiseReaction(rejectF)
|
||||||
|
|||||||
1275
goja/builtin_proxy_test.go
Normal file
1275
goja/builtin_proxy_test.go
Normal file
File diff suppressed because it is too large
Load Diff
180
goja/builtin_set_test.go
Normal file
180
goja/builtin_set_test.go
Normal file
@ -0,0 +1,180 @@
|
|||||||
|
package goja
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestSetEvilIterator(t *testing.T) {
|
||||||
|
const SCRIPT = `
|
||||||
|
var o = {};
|
||||||
|
o[Symbol.iterator] = function() {
|
||||||
|
return {
|
||||||
|
next: function() {
|
||||||
|
if (!this.flag) {
|
||||||
|
this.flag = true;
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
return {done: true};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
new Set(o);
|
||||||
|
undefined;
|
||||||
|
`
|
||||||
|
testScript(SCRIPT, _undefined, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleRuntime_ExportTo_setToMap() {
|
||||||
|
vm := New()
|
||||||
|
s, err := vm.RunString(`
|
||||||
|
new Set([1, 2, 3])
|
||||||
|
`)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
m := make(map[int]struct{})
|
||||||
|
err = vm.ExportTo(s, &m)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
fmt.Println(m)
|
||||||
|
// Output: map[1:{} 2:{} 3:{}]
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleRuntime_ExportTo_setToSlice() {
|
||||||
|
vm := New()
|
||||||
|
s, err := vm.RunString(`
|
||||||
|
new Set([1, 2, 3])
|
||||||
|
`)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
var a []int
|
||||||
|
err = vm.ExportTo(s, &a)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
fmt.Println(a)
|
||||||
|
// Output: [1 2 3]
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSetExportToSliceCircular(t *testing.T) {
|
||||||
|
vm := New()
|
||||||
|
s, err := vm.RunString(`
|
||||||
|
let s = new Set();
|
||||||
|
s.add(s);
|
||||||
|
s;
|
||||||
|
`)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
var a []Value
|
||||||
|
err = vm.ExportTo(s, &a)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if len(a) != 1 {
|
||||||
|
t.Fatalf("len: %d", len(a))
|
||||||
|
}
|
||||||
|
if a[0] != s {
|
||||||
|
t.Fatalf("a: %v", a)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSetExportToArrayMismatchedLengths(t *testing.T) {
|
||||||
|
vm := New()
|
||||||
|
s, err := vm.RunString(`
|
||||||
|
new Set([1, 2])
|
||||||
|
`)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
var s1 [3]int
|
||||||
|
err = vm.ExportTo(s, &s1)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("expected error")
|
||||||
|
}
|
||||||
|
if msg := err.Error(); !strings.Contains(msg, "lengths mismatch") {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSetExportToNilMap(t *testing.T) {
|
||||||
|
vm := New()
|
||||||
|
var m map[int]interface{}
|
||||||
|
res, err := vm.RunString("new Set([1])")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
err = vm.ExportTo(res, &m)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if len(m) != 1 {
|
||||||
|
t.Fatal(m)
|
||||||
|
}
|
||||||
|
if _, exists := m[1]; !exists {
|
||||||
|
t.Fatal(m)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSetExportToNonNilMap(t *testing.T) {
|
||||||
|
vm := New()
|
||||||
|
m := map[int]interface{}{
|
||||||
|
2: true,
|
||||||
|
}
|
||||||
|
res, err := vm.RunString("new Set([1])")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
err = vm.ExportTo(res, &m)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if len(m) != 1 {
|
||||||
|
t.Fatal(m)
|
||||||
|
}
|
||||||
|
if _, exists := m[1]; !exists {
|
||||||
|
t.Fatal(m)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSetGetAdderGetIteratorOrder(t *testing.T) {
|
||||||
|
const SCRIPT = `
|
||||||
|
let getterCalled = 0;
|
||||||
|
|
||||||
|
class S extends Set {
|
||||||
|
get add() {
|
||||||
|
getterCalled++;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let getIteratorCalled = 0;
|
||||||
|
|
||||||
|
let iterable = {};
|
||||||
|
iterable[Symbol.iterator] = () => {
|
||||||
|
getIteratorCalled++
|
||||||
|
return {
|
||||||
|
next: 1
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
let thrown = false;
|
||||||
|
|
||||||
|
try {
|
||||||
|
new S(iterable);
|
||||||
|
} catch (e) {
|
||||||
|
if (e instanceof TypeError) {
|
||||||
|
thrown = true;
|
||||||
|
} else {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
thrown && getterCalled === 1 && getIteratorCalled === 0;
|
||||||
|
`
|
||||||
|
testScript(SCRIPT, valueTrue, t)
|
||||||
|
}
|
||||||
288
goja/builtin_string_test.go
Normal file
288
goja/builtin_string_test.go
Normal file
@ -0,0 +1,288 @@
|
|||||||
|
package goja
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
func TestSubstr(t *testing.T) {
|
||||||
|
const SCRIPT = `
|
||||||
|
assert.sameValue('abc'.substr(0, false), '', 'start: 0, length: false');
|
||||||
|
assert.sameValue('abc'.substr(1, false), '', 'start: 1, length: false');
|
||||||
|
assert.sameValue('abc'.substr(2, false), '', 'start: 2, length: false');
|
||||||
|
assert.sameValue('abc'.substr(3, false), '', 'start: 3, length: false');
|
||||||
|
|
||||||
|
assert.sameValue('abc'.substr(0, NaN), '', 'start: 0, length: NaN');
|
||||||
|
assert.sameValue('abc'.substr(1, NaN), '', 'start: 1, length: NaN');
|
||||||
|
assert.sameValue('abc'.substr(2, NaN), '', 'start: 2, length: NaN');
|
||||||
|
assert.sameValue('abc'.substr(3, NaN), '', 'start: 3, length: NaN');
|
||||||
|
|
||||||
|
assert.sameValue('abc'.substr(0, ''), '', 'start: 0, length: ""');
|
||||||
|
assert.sameValue('abc'.substr(1, ''), '', 'start: 1, length: ""');
|
||||||
|
assert.sameValue('abc'.substr(2, ''), '', 'start: 2, length: ""');
|
||||||
|
assert.sameValue('abc'.substr(3, ''), '', 'start: 3, length: ""');
|
||||||
|
|
||||||
|
assert.sameValue('abc'.substr(0, null), '', 'start: 0, length: null');
|
||||||
|
assert.sameValue('abc'.substr(1, null), '', 'start: 1, length: null');
|
||||||
|
assert.sameValue('abc'.substr(2, null), '', 'start: 2, length: null');
|
||||||
|
assert.sameValue('abc'.substr(3, null), '', 'start: 3, length: null');
|
||||||
|
|
||||||
|
assert.sameValue('abc'.substr(0, -1), '', '0, -1');
|
||||||
|
assert.sameValue('abc'.substr(0, -2), '', '0, -2');
|
||||||
|
assert.sameValue('abc'.substr(0, -3), '', '0, -3');
|
||||||
|
assert.sameValue('abc'.substr(0, -4), '', '0, -4');
|
||||||
|
|
||||||
|
assert.sameValue('abc'.substr(1, -1), '', '1, -1');
|
||||||
|
assert.sameValue('abc'.substr(1, -2), '', '1, -2');
|
||||||
|
assert.sameValue('abc'.substr(1, -3), '', '1, -3');
|
||||||
|
assert.sameValue('abc'.substr(1, -4), '', '1, -4');
|
||||||
|
|
||||||
|
assert.sameValue('abc'.substr(2, -1), '', '2, -1');
|
||||||
|
assert.sameValue('abc'.substr(2, -2), '', '2, -2');
|
||||||
|
assert.sameValue('abc'.substr(2, -3), '', '2, -3');
|
||||||
|
assert.sameValue('abc'.substr(2, -4), '', '2, -4');
|
||||||
|
|
||||||
|
assert.sameValue('abc'.substr(3, -1), '', '3, -1');
|
||||||
|
assert.sameValue('abc'.substr(3, -2), '', '3, -2');
|
||||||
|
assert.sameValue('abc'.substr(3, -3), '', '3, -3');
|
||||||
|
assert.sameValue('abc'.substr(3, -4), '', '3, -4');
|
||||||
|
|
||||||
|
assert.sameValue('abc'.substr(0, 1), 'a', '0, 1');
|
||||||
|
assert.sameValue('abc'.substr(0, 2), 'ab', '0, 1');
|
||||||
|
assert.sameValue('abc'.substr(0, 3), 'abc', '0, 1');
|
||||||
|
assert.sameValue('abc'.substr(0, 4), 'abc', '0, 1');
|
||||||
|
|
||||||
|
assert.sameValue('abc'.substr(1, 1), 'b', '1, 1');
|
||||||
|
assert.sameValue('abc'.substr(1, 2), 'bc', '1, 1');
|
||||||
|
assert.sameValue('abc'.substr(1, 3), 'bc', '1, 1');
|
||||||
|
assert.sameValue('abc'.substr(1, 4), 'bc', '1, 1');
|
||||||
|
|
||||||
|
assert.sameValue('abc'.substr(2, 1), 'c', '2, 1');
|
||||||
|
assert.sameValue('abc'.substr(2, 2), 'c', '2, 1');
|
||||||
|
assert.sameValue('abc'.substr(2, 3), 'c', '2, 1');
|
||||||
|
assert.sameValue('abc'.substr(2, 4), 'c', '2, 1');
|
||||||
|
|
||||||
|
assert.sameValue('abc'.substr(3, 1), '', '3, 1');
|
||||||
|
assert.sameValue('abc'.substr(3, 2), '', '3, 1');
|
||||||
|
assert.sameValue('abc'.substr(3, 3), '', '3, 1');
|
||||||
|
assert.sameValue('abc'.substr(3, 4), '', '3, 1');
|
||||||
|
|
||||||
|
assert.sameValue('abc'.substr(0), 'abc', 'start: 0, length: unspecified');
|
||||||
|
assert.sameValue('abc'.substr(1), 'bc', 'start: 1, length: unspecified');
|
||||||
|
assert.sameValue('abc'.substr(2), 'c', 'start: 2, length: unspecified');
|
||||||
|
assert.sameValue('abc'.substr(3), '', 'start: 3, length: unspecified');
|
||||||
|
|
||||||
|
assert.sameValue(
|
||||||
|
'abc'.substr(0, undefined), 'abc', 'start: 0, length: undefined'
|
||||||
|
);
|
||||||
|
assert.sameValue(
|
||||||
|
'abc'.substr(1, undefined), 'bc', 'start: 1, length: undefined'
|
||||||
|
);
|
||||||
|
assert.sameValue(
|
||||||
|
'abc'.substr(2, undefined), 'c', 'start: 2, length: undefined'
|
||||||
|
);
|
||||||
|
assert.sameValue(
|
||||||
|
'abc'.substr(3, undefined), '', 'start: 3, length: undefined'
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.sameValue('A—', String.fromCharCode(65, 0x2014));
|
||||||
|
|
||||||
|
`
|
||||||
|
|
||||||
|
testScriptWithTestLib(SCRIPT, _undefined, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStringMatchSym(t *testing.T) {
|
||||||
|
const SCRIPT = `
|
||||||
|
function Prefix(p) {
|
||||||
|
this.p = p;
|
||||||
|
}
|
||||||
|
|
||||||
|
Prefix.prototype[Symbol.match] = function(s) {
|
||||||
|
return s.substring(0, this.p.length) === this.p;
|
||||||
|
}
|
||||||
|
|
||||||
|
var prefix1 = new Prefix("abc");
|
||||||
|
var prefix2 = new Prefix("def");
|
||||||
|
|
||||||
|
"abc123".match(prefix1) === true && "abc123".match(prefix2) === false &&
|
||||||
|
"def123".match(prefix1) === false && "def123".match(prefix2) === true;
|
||||||
|
`
|
||||||
|
testScript(SCRIPT, valueTrue, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStringMatchAllSym(t *testing.T) {
|
||||||
|
const SCRIPT = `
|
||||||
|
function Prefix(p) {
|
||||||
|
this.p = p;
|
||||||
|
}
|
||||||
|
|
||||||
|
Prefix.prototype[Symbol.matchAll] = function(s) {
|
||||||
|
return s.substring(0, this.p.length) === this.p;
|
||||||
|
}
|
||||||
|
|
||||||
|
var prefix1 = new Prefix("abc");
|
||||||
|
var prefix2 = new Prefix("def");
|
||||||
|
|
||||||
|
"abc123".matchAll(prefix1) === true && "abc123".matchAll(prefix2) === false &&
|
||||||
|
"def123".matchAll(prefix1) === false && "def123".matchAll(prefix2) === true;
|
||||||
|
`
|
||||||
|
testScript(SCRIPT, valueTrue, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGenericSplitter(t *testing.T) {
|
||||||
|
const SCRIPT = `
|
||||||
|
function MyRegexp(pattern, flags) {
|
||||||
|
if (pattern instanceof MyRegexp) {
|
||||||
|
pattern = pattern.wrapped;
|
||||||
|
}
|
||||||
|
this.wrapped = new RegExp(pattern, flags);
|
||||||
|
}
|
||||||
|
|
||||||
|
MyRegexp.prototype.exec = function() {
|
||||||
|
return this.wrapped.exec.apply(this.wrapped, arguments);
|
||||||
|
}
|
||||||
|
|
||||||
|
Object.defineProperty(MyRegexp.prototype, "lastIndex", {
|
||||||
|
get: function() {
|
||||||
|
return this.wrapped.lastIndex;
|
||||||
|
},
|
||||||
|
set: function(v) {
|
||||||
|
this.wrapped.lastIndex = v;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Object.defineProperty(MyRegexp.prototype, "flags", {
|
||||||
|
get: function() {
|
||||||
|
return this.wrapped.flags;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
MyRegexp[Symbol.species] = MyRegexp;
|
||||||
|
MyRegexp.prototype[Symbol.split] = RegExp.prototype[Symbol.split];
|
||||||
|
|
||||||
|
var r = new MyRegexp(/ /);
|
||||||
|
var res = "a b c".split(r);
|
||||||
|
res.length === 3 && res[0] === "a" && res[1] === "b" && res[2] === "c";
|
||||||
|
`
|
||||||
|
testScript(SCRIPT, valueTrue, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStringIterSurrPair(t *testing.T) {
|
||||||
|
const SCRIPT = `
|
||||||
|
var lo = '\uD834';
|
||||||
|
var hi = '\uDF06';
|
||||||
|
var pair = lo + hi;
|
||||||
|
var string = 'a' + pair + 'b' + lo + pair + hi + lo;
|
||||||
|
var iterator = string[Symbol.iterator]();
|
||||||
|
var result;
|
||||||
|
|
||||||
|
result = iterator.next();
|
||||||
|
if (result.value !== 'a') {
|
||||||
|
throw new Error("at 0: " + result.value);
|
||||||
|
}
|
||||||
|
result = iterator.next();
|
||||||
|
if (result.value !== pair) {
|
||||||
|
throw new Error("at 1: " + result.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
`
|
||||||
|
testScript(SCRIPT, _undefined, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestValueStringBuilder(t *testing.T) {
|
||||||
|
t.Run("substringASCII", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
var sb StringBuilder
|
||||||
|
str := newStringValue("a\U00010000b")
|
||||||
|
sb.WriteSubstring(str, 0, 1)
|
||||||
|
res := sb.String()
|
||||||
|
if res != asciiString("a") {
|
||||||
|
t.Fatal(res)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("substringASCIIPure", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
var sb StringBuilder
|
||||||
|
str := newStringValue("ab")
|
||||||
|
sb.WriteSubstring(str, 0, 1)
|
||||||
|
res := sb.String()
|
||||||
|
if res != asciiString("a") {
|
||||||
|
t.Fatal(res)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("substringUnicode", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
var sb StringBuilder
|
||||||
|
str := newStringValue("a\U00010000b")
|
||||||
|
sb.WriteSubstring(str, 1, 3)
|
||||||
|
res := sb.String()
|
||||||
|
if !res.SameAs(unicodeStringFromRunes([]rune{0x10000})) {
|
||||||
|
t.Fatal(res)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("substringASCIIUnicode", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
var sb StringBuilder
|
||||||
|
str := newStringValue("a\U00010000b")
|
||||||
|
sb.WriteSubstring(str, 0, 2)
|
||||||
|
res := sb.String()
|
||||||
|
if !res.SameAs(unicodeStringFromRunes([]rune{'a', 0xD800})) {
|
||||||
|
t.Fatal(res)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("substringUnicodeASCII", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
var sb StringBuilder
|
||||||
|
str := newStringValue("a\U00010000b")
|
||||||
|
sb.WriteSubstring(str, 2, 4)
|
||||||
|
res := sb.String()
|
||||||
|
if !res.SameAs(unicodeStringFromRunes([]rune{0xDC00, 'b'})) {
|
||||||
|
t.Fatal(res)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("concatSubstringUnicodeASCII", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
var sb StringBuilder
|
||||||
|
sb.WriteString(newStringValue("юникод"))
|
||||||
|
sb.WriteSubstring(asciiString(" ascii"), 0, 6)
|
||||||
|
if res := sb.String(); !res.SameAs(newStringValue("юникод ascii")) {
|
||||||
|
t.Fatal(res)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("concat_ASCII_importedASCII", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
var sb StringBuilder
|
||||||
|
sb.WriteString(asciiString("ascii"))
|
||||||
|
sb.WriteString(&importedString{s: " imported_ascii1234567890"})
|
||||||
|
s := sb.String()
|
||||||
|
if res, ok := s.(asciiString); !ok || res != "ascii imported_ascii1234567890" {
|
||||||
|
t.Fatal(s)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("concat_ASCII_importedUnicode", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
var sb StringBuilder
|
||||||
|
sb.WriteString(asciiString("ascii"))
|
||||||
|
sb.WriteString(&importedString{s: " imported_юникод"})
|
||||||
|
s := sb.String()
|
||||||
|
if res, ok := s.(unicodeString); !ok || !res.SameAs(newStringValue("ascii imported_юникод")) {
|
||||||
|
t.Fatal(s)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStringSplit(t *testing.T) {
|
||||||
|
const SCRIPT = `
|
||||||
|
assert(compareArray("".split("#",2), [""]));
|
||||||
|
assert(compareArray("".split("#"), [""]));
|
||||||
|
assert(compareArray("".split("",2), []));
|
||||||
|
assert(compareArray("".split(""), []));
|
||||||
|
`
|
||||||
|
testScriptWithTestLib(SCRIPT, _undefined, t)
|
||||||
|
}
|
||||||
326
goja/builtin_typedarrays_test.go
Normal file
326
goja/builtin_typedarrays_test.go
Normal file
@ -0,0 +1,326 @@
|
|||||||
|
package goja
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
/*
|
||||||
|
func TestArrayBufferNew(t *testing.T) {
|
||||||
|
const SCRIPT = `
|
||||||
|
var b = new ArrayBuffer(16);
|
||||||
|
b.byteLength;
|
||||||
|
`
|
||||||
|
|
||||||
|
testScript(SCRIPT, intToValue(16), t)
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
func TestArrayBufferSetUint32(t *testing.T) {
|
||||||
|
vm := New()
|
||||||
|
b := vm._newArrayBuffer(vm.global.ArrayBufferPrototype, nil)
|
||||||
|
b.data = make([]byte, 4)
|
||||||
|
b.setUint32(0, 0xCAFEBABE, bigEndian)
|
||||||
|
|
||||||
|
i := b.getUint32(0, bigEndian)
|
||||||
|
if i != 0xCAFEBABE {
|
||||||
|
t.Fatal(i)
|
||||||
|
}
|
||||||
|
i = b.getUint32(0, littleEndian)
|
||||||
|
if i != 0xBEBAFECA {
|
||||||
|
t.Fatal(i)
|
||||||
|
}
|
||||||
|
|
||||||
|
b.setUint32(0, 0xBEBAFECA, littleEndian)
|
||||||
|
i = b.getUint32(0, bigEndian)
|
||||||
|
if i != 0xCAFEBABE {
|
||||||
|
t.Fatal(i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestArrayBufferSetInt32(t *testing.T) {
|
||||||
|
vm := New()
|
||||||
|
b := vm._newArrayBuffer(vm.global.ArrayBufferPrototype, nil)
|
||||||
|
b.data = make([]byte, 4)
|
||||||
|
b.setInt32(0, -42, littleEndian)
|
||||||
|
if v := b.getInt32(0, littleEndian); v != -42 {
|
||||||
|
t.Fatal(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
b.setInt32(0, -42, bigEndian)
|
||||||
|
if v := b.getInt32(0, bigEndian); v != -42 {
|
||||||
|
t.Fatal(v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewUint8Array(t *testing.T) {
|
||||||
|
const SCRIPT = `
|
||||||
|
var a = new Uint8Array(1);
|
||||||
|
a[0] = 42;
|
||||||
|
a.byteLength === 1 && a.length === 1 && a[0] === 42;
|
||||||
|
`
|
||||||
|
|
||||||
|
testScript(SCRIPT, valueTrue, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewUint16Array(t *testing.T) {
|
||||||
|
const SCRIPT = `
|
||||||
|
var a = new Uint16Array(1);
|
||||||
|
a[0] = 42;
|
||||||
|
a.byteLength === 2 && a.length === 1 && a[0] === 42;
|
||||||
|
`
|
||||||
|
|
||||||
|
testScript(SCRIPT, valueTrue, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTypedArraysSpeciesConstructor(t *testing.T) {
|
||||||
|
const SCRIPT = `
|
||||||
|
'use strict';
|
||||||
|
function MyArray() {
|
||||||
|
var NewTarget = this.__proto__.constructor;
|
||||||
|
return Reflect.construct(Uint16Array, arguments, NewTarget);
|
||||||
|
}
|
||||||
|
MyArray.prototype = Object.create(Uint16Array.prototype, {
|
||||||
|
constructor: {
|
||||||
|
value: MyArray,
|
||||||
|
writable: true,
|
||||||
|
configurable: true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
var a = new MyArray(1);
|
||||||
|
Object.defineProperty(MyArray, Symbol.species, {value: Uint8Array, configurable: true});
|
||||||
|
a[0] = 32767;
|
||||||
|
var b = a.filter(function() {
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
if (a[0] !== 32767) {
|
||||||
|
throw new Error("a[0]=" + a[0]);
|
||||||
|
}
|
||||||
|
if (!(b instanceof Uint8Array)) {
|
||||||
|
throw new Error("b instanceof Uint8Array");
|
||||||
|
}
|
||||||
|
if (b[0] != 255) {
|
||||||
|
throw new Error("b[0]=" + b[0]);
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
testScript(SCRIPT, _undefined, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTypedArrayFromArrayBuffer(t *testing.T) {
|
||||||
|
const SCRIPT = `
|
||||||
|
var buf = new ArrayBuffer(2);
|
||||||
|
var a16 = new Uint16Array(buf);
|
||||||
|
if (!(a16 instanceof Uint16Array)) {
|
||||||
|
throw new Error("a16 is not an instance");
|
||||||
|
}
|
||||||
|
if (a16.buffer !== buf) {
|
||||||
|
throw new Error("a16.buffer !== buf");
|
||||||
|
}
|
||||||
|
if (a16.length !== 1) {
|
||||||
|
throw new Error("a16.length=" + a16.length);
|
||||||
|
}
|
||||||
|
var a8 = new Uint8Array(buf);
|
||||||
|
a8.fill(0xAA);
|
||||||
|
if (a16[0] !== 0xAAAA) {
|
||||||
|
throw new Error("a16[0]=" + a16[0]);
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
testScript(SCRIPT, _undefined, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTypedArraySetOverlapDifSize(t *testing.T) {
|
||||||
|
const SCRIPT = `
|
||||||
|
var buf = new ArrayBuffer(4);
|
||||||
|
var src = new Uint8Array(buf, 1, 2);
|
||||||
|
src[0] = 1;
|
||||||
|
src[1] = 2;
|
||||||
|
var dst = new Uint16Array(buf);
|
||||||
|
dst.set(src);
|
||||||
|
if (dst[0] !== 1 || dst[1] !== 2) {
|
||||||
|
throw new Error("dst: " + dst.join(","));
|
||||||
|
}
|
||||||
|
`
|
||||||
|
testScript(SCRIPT, _undefined, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTypedArraySetOverlapDifSize2(t *testing.T) {
|
||||||
|
const SCRIPT = `
|
||||||
|
var buf = new ArrayBuffer(4);
|
||||||
|
var src = new Uint8Array(buf, 0, 2);
|
||||||
|
src[0] = 1;
|
||||||
|
src[1] = 2;
|
||||||
|
var dst = new Uint16Array(buf);
|
||||||
|
dst.set(src);
|
||||||
|
if (dst[0] !== 1 || dst[1] !== 2) {
|
||||||
|
throw new Error("dst: " + dst.join(","));
|
||||||
|
}
|
||||||
|
`
|
||||||
|
testScript(SCRIPT, _undefined, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTypedArraySetOverlapDifSize3(t *testing.T) {
|
||||||
|
const SCRIPT = `
|
||||||
|
var buf = new ArrayBuffer(8);
|
||||||
|
var src = new Uint8Array(buf, 2, 4);
|
||||||
|
src[0] = 1;
|
||||||
|
src[1] = 2;
|
||||||
|
src[2] = 3;
|
||||||
|
src[3] = 4;
|
||||||
|
var dst = new Uint16Array(buf);
|
||||||
|
dst.set(src);
|
||||||
|
if (dst[0] !== 1 || dst[1] !== 2 || dst[2] !== 3 || dst[3] !== 4) {
|
||||||
|
throw new Error("dst: " + dst.join(","));
|
||||||
|
}
|
||||||
|
`
|
||||||
|
testScript(SCRIPT, _undefined, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTypedArraySetOverlapDifSize4(t *testing.T) {
|
||||||
|
const SCRIPT = `
|
||||||
|
var buf = new ArrayBuffer(10);
|
||||||
|
var dst = new Uint8Array(buf, 2, 5);
|
||||||
|
var src = new Uint16Array(buf);
|
||||||
|
src[0] = 1;
|
||||||
|
src[1] = 2;
|
||||||
|
src[2] = 3;
|
||||||
|
src[3] = 4;
|
||||||
|
src[4] = 5;
|
||||||
|
dst.set(src);
|
||||||
|
if (dst[0] !== 1 || dst[1] !== 2 || dst[2] !== 3 || dst[3] !== 4 || dst[4] !== 5) {
|
||||||
|
throw new Error("dst: " + dst.join(","));
|
||||||
|
}
|
||||||
|
`
|
||||||
|
testScript(SCRIPT, _undefined, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTypedArraySetNoOverlapDifSizeForward(t *testing.T) {
|
||||||
|
const SCRIPT = `
|
||||||
|
var buf = new ArrayBuffer(10);
|
||||||
|
var dst = new Uint8Array(buf, 7, 2);
|
||||||
|
var src = new Uint16Array(buf, 0, 2);
|
||||||
|
src[0] = 1;
|
||||||
|
src[1] = 2;
|
||||||
|
dst.set(src);
|
||||||
|
if (dst[0] !== 1 || dst[1] !== 2 || src[0] !== 1 || src[1] !== 2) {
|
||||||
|
throw new Error("dst: " + dst.join(","));
|
||||||
|
}
|
||||||
|
`
|
||||||
|
testScript(SCRIPT, _undefined, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTypedArraySetNoOverlapDifSizeBackward(t *testing.T) {
|
||||||
|
const SCRIPT = `
|
||||||
|
var buf = new ArrayBuffer(10);
|
||||||
|
var dst = new Uint8Array(buf, 0, 2);
|
||||||
|
var src = new Uint16Array(buf, 6, 2);
|
||||||
|
src[0] = 1;
|
||||||
|
src[1] = 2;
|
||||||
|
dst.set(src);
|
||||||
|
if (dst[0] !== 1 || dst[1] !== 2 || src[0] !== 1 || src[1] !== 2) {
|
||||||
|
throw new Error("dst: " + dst.join(","));
|
||||||
|
}
|
||||||
|
`
|
||||||
|
testScript(SCRIPT, _undefined, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTypedArraySetNoOverlapDifSizeDifBuffers(t *testing.T) {
|
||||||
|
const SCRIPT = `
|
||||||
|
var dstBuf = new ArrayBuffer(1024);
|
||||||
|
var dst = new Uint8Array(dstBuf, 0, 2);
|
||||||
|
var src = new Uint16Array(2);
|
||||||
|
src[0] = 1;
|
||||||
|
src[1] = 2;
|
||||||
|
dst.set(src);
|
||||||
|
if (dst[0] !== 1 || dst[1] !== 2 || src[0] !== 1 || src[1] !== 2) {
|
||||||
|
throw new Error("dst: " + dst.join(","));
|
||||||
|
}
|
||||||
|
`
|
||||||
|
testScript(SCRIPT, _undefined, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTypedArraySliceSameType(t *testing.T) {
|
||||||
|
const SCRIPT = `
|
||||||
|
var src = Uint8Array.of(1,2,3,4);
|
||||||
|
var dst = src.slice(1, 3);
|
||||||
|
if (dst.length !== 2 || dst[0] !== 2 || dst[1] !== 3) {
|
||||||
|
throw new Error("dst: " + dst.join(","));
|
||||||
|
}
|
||||||
|
`
|
||||||
|
testScript(SCRIPT, _undefined, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTypedArraySliceDifType(t *testing.T) {
|
||||||
|
const SCRIPT = `
|
||||||
|
var src = Uint8Array.of(1,2,3,4);
|
||||||
|
Object.defineProperty(Uint8Array, Symbol.species, {value: Uint16Array, configurable: true});
|
||||||
|
var dst = src.slice(1, 3);
|
||||||
|
if (!(dst instanceof Uint16Array)) {
|
||||||
|
throw new Error("wrong dst type: " + dst);
|
||||||
|
}
|
||||||
|
if (dst.length !== 2 || dst[0] !== 2 || dst[1] !== 3) {
|
||||||
|
throw new Error("dst: " + dst.join(","));
|
||||||
|
}
|
||||||
|
`
|
||||||
|
testScript(SCRIPT, _undefined, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTypedArraySortComparatorReturnValueFloats(t *testing.T) {
|
||||||
|
const SCRIPT = `
|
||||||
|
var a = Float64Array.of(
|
||||||
|
5.97,
|
||||||
|
9.91,
|
||||||
|
4.13,
|
||||||
|
9.28,
|
||||||
|
3.29
|
||||||
|
);
|
||||||
|
a.sort( function(a, b) { return a - b; } );
|
||||||
|
for (var i = 1; i < a.length; i++) {
|
||||||
|
if (a[i] < a[i-1]) {
|
||||||
|
throw new Error("Array is not sorted: " + a);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
testScript(SCRIPT, _undefined, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTypedArraySortComparatorReturnValueNegZero(t *testing.T) {
|
||||||
|
const SCRIPT = `
|
||||||
|
var a = new Uint8Array([2, 1]);
|
||||||
|
a.sort( function(a, b) { return a > b ? 0 : -0; } );
|
||||||
|
for (var i = 1; i < a.length; i++) {
|
||||||
|
if (a[i] < a[i-1]) {
|
||||||
|
throw new Error("Array is not sorted: " + a);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
testScript(SCRIPT, _undefined, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInt32ArrayNegativeIndex(t *testing.T) {
|
||||||
|
const SCRIPT = `
|
||||||
|
new Int32Array()[-1] === undefined;
|
||||||
|
`
|
||||||
|
|
||||||
|
testScript(SCRIPT, valueTrue, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTypedArrayDeleteUnconfigurable(t *testing.T) {
|
||||||
|
const SCRIPT = `
|
||||||
|
try {
|
||||||
|
(function() {
|
||||||
|
'use strict';
|
||||||
|
delete Uint8Array.prototype.BYTES_PER_ELEMENT;
|
||||||
|
})();
|
||||||
|
} catch(e) {
|
||||||
|
if (!(e instanceof TypeError)) {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
if (!e.message.startsWith("Cannot delete property")) {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
testScript(SCRIPT, _undefined, t)
|
||||||
|
}
|
||||||
76
goja/builtin_weakmap_test.go
Normal file
76
goja/builtin_weakmap_test.go
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
package goja
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestWeakMap(t *testing.T) {
|
||||||
|
vm := New()
|
||||||
|
_, err := vm.RunString(`
|
||||||
|
var m = new WeakMap();
|
||||||
|
var m1 = new WeakMap();
|
||||||
|
var key = {};
|
||||||
|
m.set(key, true);
|
||||||
|
m1.set(key, false);
|
||||||
|
if (!m.has(key)) {
|
||||||
|
throw new Error("has");
|
||||||
|
}
|
||||||
|
if (m.get(key) !== true) {
|
||||||
|
throw new Error("value does not match");
|
||||||
|
}
|
||||||
|
if (!m1.has(key)) {
|
||||||
|
throw new Error("has (m1)");
|
||||||
|
}
|
||||||
|
if (m1.get(key) !== false) {
|
||||||
|
throw new Error("m1 value does not match");
|
||||||
|
}
|
||||||
|
m.delete(key);
|
||||||
|
if (m.has(key)) {
|
||||||
|
throw new Error("m still has after delete");
|
||||||
|
}
|
||||||
|
if (!m1.has(key)) {
|
||||||
|
throw new Error("m1 does not have after delete from m");
|
||||||
|
}
|
||||||
|
`)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWeakMapGetAdderGetIteratorOrder(t *testing.T) {
|
||||||
|
const SCRIPT = `
|
||||||
|
let getterCalled = 0;
|
||||||
|
|
||||||
|
class M extends WeakMap {
|
||||||
|
get set() {
|
||||||
|
getterCalled++;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let getIteratorCalled = 0;
|
||||||
|
|
||||||
|
let iterable = {};
|
||||||
|
iterable[Symbol.iterator] = () => {
|
||||||
|
getIteratorCalled++
|
||||||
|
return {
|
||||||
|
next: 1
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
let thrown = false;
|
||||||
|
|
||||||
|
try {
|
||||||
|
new M(iterable);
|
||||||
|
} catch (e) {
|
||||||
|
if (e instanceof TypeError) {
|
||||||
|
thrown = true;
|
||||||
|
} else {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
thrown && getterCalled === 1 && getIteratorCalled === 0;
|
||||||
|
`
|
||||||
|
testScript(SCRIPT, valueTrue, t)
|
||||||
|
}
|
||||||
101
goja/builtin_weakset_test.go
Normal file
101
goja/builtin_weakset_test.go
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
package goja
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestWeakSetBasic(t *testing.T) {
|
||||||
|
const SCRIPT = `
|
||||||
|
var s = new WeakSet();
|
||||||
|
var o = {};
|
||||||
|
s.add(o);
|
||||||
|
if (!s.has(o)) {
|
||||||
|
throw new Error("has");
|
||||||
|
}
|
||||||
|
s.delete(o);
|
||||||
|
if (s.has(o)) {
|
||||||
|
throw new Error("still has");
|
||||||
|
}
|
||||||
|
`
|
||||||
|
testScript(SCRIPT, _undefined, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWeakSetArraySimple(t *testing.T) {
|
||||||
|
const SCRIPT = `
|
||||||
|
var o1 = {}, o2 = {}, o3 = {};
|
||||||
|
|
||||||
|
var s = new WeakSet([o1, o2, o3]);
|
||||||
|
s.has(o1) && s.has(o2) && s.has(o3);
|
||||||
|
`
|
||||||
|
testScript(SCRIPT, valueTrue, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWeakSetArrayGeneric(t *testing.T) {
|
||||||
|
const SCRIPT = `
|
||||||
|
var o1 = {}, o2 = {}, o3 = {};
|
||||||
|
var a = new Array();
|
||||||
|
var s;
|
||||||
|
var thrown = false;
|
||||||
|
a[1] = o2;
|
||||||
|
|
||||||
|
try {
|
||||||
|
s = new WeakSet(a);
|
||||||
|
} catch (e) {
|
||||||
|
if (e instanceof TypeError) {
|
||||||
|
thrown = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!thrown) {
|
||||||
|
throw new Error("Case 1 does not throw");
|
||||||
|
}
|
||||||
|
|
||||||
|
Object.defineProperty(a.__proto__, "0", {value: o1, writable: true, enumerable: true, configurable: true});
|
||||||
|
s = new WeakSet(a);
|
||||||
|
if (!(s.has(o1) && s.has(o2) && !s.has(o3))) {
|
||||||
|
throw new Error("Case 2 failed");
|
||||||
|
}
|
||||||
|
|
||||||
|
Object.defineProperty(a, "2", {value: o3, configurable: true});
|
||||||
|
s = new WeakSet(a);
|
||||||
|
s.has(o1) && s.has(o2) && s.has(o3);
|
||||||
|
`
|
||||||
|
testScript(SCRIPT, valueTrue, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWeakSetGetAdderGetIteratorOrder(t *testing.T) {
|
||||||
|
const SCRIPT = `
|
||||||
|
let getterCalled = 0;
|
||||||
|
|
||||||
|
class S extends WeakSet {
|
||||||
|
get add() {
|
||||||
|
getterCalled++;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let getIteratorCalled = 0;
|
||||||
|
|
||||||
|
let iterable = {};
|
||||||
|
iterable[Symbol.iterator] = () => {
|
||||||
|
getIteratorCalled++
|
||||||
|
return {
|
||||||
|
next: 1
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
let thrown = false;
|
||||||
|
|
||||||
|
try {
|
||||||
|
new S(iterable);
|
||||||
|
} catch (e) {
|
||||||
|
if (e instanceof TypeError) {
|
||||||
|
thrown = true;
|
||||||
|
} else {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
thrown && getterCalled === 1 && getIteratorCalled === 0;
|
||||||
|
`
|
||||||
|
testScript(SCRIPT, valueTrue, t)
|
||||||
|
}
|
||||||
@ -650,6 +650,8 @@ func (s *scope) finaliseVarAlloc(stackOffset int) (stashSize, stackSize int) {
|
|||||||
*ap = loadThisStash(idx)
|
*ap = loadThisStash(idx)
|
||||||
case initStack:
|
case initStack:
|
||||||
*ap = initStash(idx)
|
*ap = initStash(idx)
|
||||||
|
case initStackP:
|
||||||
|
*ap = initStashP(idx)
|
||||||
case resolveThisStack:
|
case resolveThisStack:
|
||||||
*ap = resolveThisStash(idx)
|
*ap = resolveThisStash(idx)
|
||||||
case _ret:
|
case _ret:
|
||||||
@ -666,6 +668,8 @@ func (s *scope) finaliseVarAlloc(stackOffset int) (stashSize, stackSize int) {
|
|||||||
*ap = loadStash(idx)
|
*ap = loadStash(idx)
|
||||||
case initStack:
|
case initStack:
|
||||||
*ap = initStash(idx)
|
*ap = initStash(idx)
|
||||||
|
case initStackP:
|
||||||
|
*ap = initStashP(idx)
|
||||||
default:
|
default:
|
||||||
s.c.assert(false, s.c.p.sourceOffset(pc), "Unsupported instruction for 'this'")
|
s.c.assert(false, s.c.p.sourceOffset(pc), "Unsupported instruction for 'this'")
|
||||||
}
|
}
|
||||||
@ -735,6 +739,8 @@ func (s *scope) finaliseVarAlloc(stackOffset int) (stashSize, stackSize int) {
|
|||||||
*ap = loadThisStack{}
|
*ap = loadThisStack{}
|
||||||
case initStack:
|
case initStack:
|
||||||
// no-op
|
// no-op
|
||||||
|
case initStackP:
|
||||||
|
// no-op
|
||||||
case resolveThisStack:
|
case resolveThisStack:
|
||||||
// no-op
|
// no-op
|
||||||
case _ret:
|
case _ret:
|
||||||
|
|||||||
@ -1255,6 +1255,44 @@ func (e *compiledAssignExpr) emitGetter(putOnStack bool) {
|
|||||||
e.right.emitGetter(true)
|
e.right.emitGetter(true)
|
||||||
e.c.emit(shr)
|
e.c.emit(shr)
|
||||||
}, false, putOnStack)
|
}, false, putOnStack)
|
||||||
|
case token.LOGICAL_AND, token.LOGICAL_OR, token.COALESCE:
|
||||||
|
e.left.emitRef()
|
||||||
|
e.c.emit(getValue)
|
||||||
|
mark := len(e.c.p.code)
|
||||||
|
e.c.emit(nil)
|
||||||
|
if id, ok := e.left.(*compiledIdentifierExpr); ok {
|
||||||
|
e.c.emitNamedOrConst(e.right, id.name)
|
||||||
|
} else {
|
||||||
|
e.right.emitGetter(true)
|
||||||
|
}
|
||||||
|
if putOnStack {
|
||||||
|
e.c.emit(putValue)
|
||||||
|
} else {
|
||||||
|
e.c.emit(putValueP)
|
||||||
|
}
|
||||||
|
e.c.emit(jump(2))
|
||||||
|
offset := len(e.c.p.code) - mark
|
||||||
|
switch e.operator {
|
||||||
|
case token.LOGICAL_AND:
|
||||||
|
if putOnStack {
|
||||||
|
e.c.p.code[mark] = jne(offset)
|
||||||
|
} else {
|
||||||
|
e.c.p.code[mark] = jneP(offset)
|
||||||
|
}
|
||||||
|
case token.LOGICAL_OR:
|
||||||
|
if putOnStack {
|
||||||
|
e.c.p.code[mark] = jeq(offset)
|
||||||
|
} else {
|
||||||
|
e.c.p.code[mark] = jeqP(offset)
|
||||||
|
}
|
||||||
|
case token.COALESCE:
|
||||||
|
if putOnStack {
|
||||||
|
e.c.p.code[mark] = jcoalesc(offset)
|
||||||
|
} else {
|
||||||
|
e.c.p.code[mark] = jcoalescP(offset)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
e.c.emit(popRef)
|
||||||
default:
|
default:
|
||||||
e.c.assert(false, e.offset, "Unknown assign operator: %s", e.operator.String())
|
e.c.assert(false, e.offset, "Unknown assign operator: %s", e.operator.String())
|
||||||
panic("unreachable")
|
panic("unreachable")
|
||||||
@ -1647,6 +1685,7 @@ func (e *compiledFunctionLiteral) compile() (prg *Program, name unistring.String
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
needInitThis := false
|
||||||
if thisBinding != nil {
|
if thisBinding != nil {
|
||||||
if !s.isDynamic() && thisBinding.useCount() == 0 {
|
if !s.isDynamic() && thisBinding.useCount() == 0 {
|
||||||
s.deleteBinding(thisBinding)
|
s.deleteBinding(thisBinding)
|
||||||
@ -1655,13 +1694,15 @@ func (e *compiledFunctionLiteral) compile() (prg *Program, name unistring.String
|
|||||||
if thisBinding.inStash || s.isDynamic() {
|
if thisBinding.inStash || s.isDynamic() {
|
||||||
delta++
|
delta++
|
||||||
thisBinding.emitInitAtScope(s, preambleLen-delta)
|
thisBinding.emitInitAtScope(s, preambleLen-delta)
|
||||||
|
needInitThis = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
stashSize, stackSize := s.finaliseVarAlloc(0)
|
stashSize, stackSize := s.finaliseVarAlloc(0)
|
||||||
|
|
||||||
if thisBinding != nil && thisBinding.inStash && (!s.argsInStash || stackSize > 0) {
|
if needInitThis && (!s.argsInStash || firstForwardRef != -1 || stackSize > 0) {
|
||||||
|
code[preambleLen-delta] = initStashP(code[preambleLen-delta].(initStash))
|
||||||
delta++
|
delta++
|
||||||
code[preambleLen-delta] = loadStack(0)
|
code[preambleLen-delta] = loadStack(0)
|
||||||
} // otherwise, 'this' will be at stack[sp-1], no need to load
|
} // otherwise, 'this' will be at stack[sp-1], no need to load
|
||||||
@ -2598,7 +2639,7 @@ func (e *compiledConditionalExpr) emitGetter(putOnStack bool) {
|
|||||||
e.consequent.emitGetter(putOnStack)
|
e.consequent.emitGetter(putOnStack)
|
||||||
j1 := len(e.c.p.code)
|
j1 := len(e.c.p.code)
|
||||||
e.c.emit(nil)
|
e.c.emit(nil)
|
||||||
e.c.p.code[j] = jne(len(e.c.p.code) - j)
|
e.c.p.code[j] = jneP(len(e.c.p.code) - j)
|
||||||
e.alternate.emitGetter(putOnStack)
|
e.alternate.emitGetter(putOnStack)
|
||||||
e.c.p.code[j1] = jump(len(e.c.p.code) - j1)
|
e.c.p.code[j1] = jump(len(e.c.p.code) - j1)
|
||||||
}
|
}
|
||||||
@ -2648,7 +2689,7 @@ func (e *compiledLogicalOr) emitGetter(putOnStack bool) {
|
|||||||
e.addSrcMap()
|
e.addSrcMap()
|
||||||
e.c.emit(nil)
|
e.c.emit(nil)
|
||||||
e.c.emitExpr(e.right, true)
|
e.c.emitExpr(e.right, true)
|
||||||
e.c.p.code[j] = jeq1(len(e.c.p.code) - j)
|
e.c.p.code[j] = jeq(len(e.c.p.code) - j)
|
||||||
if !putOnStack {
|
if !putOnStack {
|
||||||
e.c.emit(pop)
|
e.c.emit(pop)
|
||||||
}
|
}
|
||||||
@ -2730,7 +2771,7 @@ func (e *compiledLogicalAnd) emitGetter(putOnStack bool) {
|
|||||||
e.addSrcMap()
|
e.addSrcMap()
|
||||||
e.c.emit(nil)
|
e.c.emit(nil)
|
||||||
e.c.emitExpr(e.right, true)
|
e.c.emitExpr(e.right, true)
|
||||||
e.c.p.code[j] = jneq1(len(e.c.p.code) - j)
|
e.c.p.code[j] = jne(len(e.c.p.code) - j)
|
||||||
if !putOnStack {
|
if !putOnStack {
|
||||||
e.c.emit(pop)
|
e.c.emit(pop)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -226,7 +226,7 @@ func (c *compiler) compileLabeledDoWhileStatement(v *ast.DoWhileStatement, needR
|
|||||||
c.compileStatement(v.Body, needResult)
|
c.compileStatement(v.Body, needResult)
|
||||||
c.block.cont = len(c.p.code)
|
c.block.cont = len(c.p.code)
|
||||||
c.emitExpr(c.compileExpression(v.Test), true)
|
c.emitExpr(c.compileExpression(v.Test), true)
|
||||||
c.emit(jeq(start - len(c.p.code)))
|
c.emit(jeqP(start - len(c.p.code)))
|
||||||
c.leaveBlock()
|
c.leaveBlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -339,7 +339,7 @@ func (c *compiler) compileLabeledForStatement(v *ast.ForStatement, needResult bo
|
|||||||
c.emit(jump(start - len(c.p.code)))
|
c.emit(jump(start - len(c.p.code)))
|
||||||
if v.Test != nil {
|
if v.Test != nil {
|
||||||
if !testConst {
|
if !testConst {
|
||||||
c.p.code[j] = jne(len(c.p.code) - j)
|
c.p.code[j] = jneP(len(c.p.code) - j)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
end:
|
end:
|
||||||
@ -535,7 +535,7 @@ func (c *compiler) compileLabeledWhileStatement(v *ast.WhileStatement, needResul
|
|||||||
c.compileStatement(v.Body, needResult)
|
c.compileStatement(v.Body, needResult)
|
||||||
c.emit(jump(start - len(c.p.code)))
|
c.emit(jump(start - len(c.p.code)))
|
||||||
if !testTrue {
|
if !testTrue {
|
||||||
c.p.code[j] = jne(len(c.p.code) - j)
|
c.p.code[j] = jneP(len(c.p.code) - j)
|
||||||
}
|
}
|
||||||
end:
|
end:
|
||||||
c.leaveBlock()
|
c.leaveBlock()
|
||||||
@ -713,16 +713,16 @@ func (c *compiler) compileIfStatement(v *ast.IfStatement, needResult bool) {
|
|||||||
if v.Alternate != nil {
|
if v.Alternate != nil {
|
||||||
jmp1 := len(c.p.code)
|
jmp1 := len(c.p.code)
|
||||||
c.emit(nil)
|
c.emit(nil)
|
||||||
c.p.code[jmp] = jne(len(c.p.code) - jmp)
|
c.p.code[jmp] = jneP(len(c.p.code) - jmp)
|
||||||
c.compileIfBody(v.Alternate, needResult)
|
c.compileIfBody(v.Alternate, needResult)
|
||||||
c.p.code[jmp1] = jump(len(c.p.code) - jmp1)
|
c.p.code[jmp1] = jump(len(c.p.code) - jmp1)
|
||||||
} else {
|
} else {
|
||||||
if needResult {
|
if needResult {
|
||||||
c.emit(jump(2))
|
c.emit(jump(2))
|
||||||
c.p.code[jmp] = jne(len(c.p.code) - jmp)
|
c.p.code[jmp] = jneP(len(c.p.code) - jmp)
|
||||||
c.emit(clearResult)
|
c.emit(clearResult)
|
||||||
} else {
|
} else {
|
||||||
c.p.code[jmp] = jne(len(c.p.code) - jmp)
|
c.p.code[jmp] = jneP(len(c.p.code) - jmp)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1081,9 +1081,9 @@ func (c *compiler) compileSwitchStatement(v *ast.SwitchStatement, needResult boo
|
|||||||
c.compileExpression(s.Test).emitGetter(true)
|
c.compileExpression(s.Test).emitGetter(true)
|
||||||
c.emit(op_strict_eq)
|
c.emit(op_strict_eq)
|
||||||
if db != nil {
|
if db != nil {
|
||||||
c.emit(jne(2))
|
c.emit(jneP(2))
|
||||||
} else {
|
} else {
|
||||||
c.emit(jne(3), pop)
|
c.emit(jneP(3), pop)
|
||||||
}
|
}
|
||||||
jumps[i] = len(c.p.code)
|
jumps[i] = len(c.p.code)
|
||||||
c.emit(nil)
|
c.emit(nil)
|
||||||
|
|||||||
6055
goja/compiler_test.go
Normal file
6055
goja/compiler_test.go
Normal file
File diff suppressed because it is too large
Load Diff
623
goja/date_test.go
Normal file
623
goja/date_test.go
Normal file
@ -0,0 +1,623 @@
|
|||||||
|
package goja
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestDateUTC(t *testing.T) {
|
||||||
|
const SCRIPT = `
|
||||||
|
assert.sameValue(Date.UTC(1970, 0), 0, '1970, 0');
|
||||||
|
assert.sameValue(Date.UTC(2016, 0), 1451606400000, '2016, 0');
|
||||||
|
assert.sameValue(Date.UTC(2016, 6), 1467331200000, '2016, 6');
|
||||||
|
|
||||||
|
assert.sameValue(Date.UTC(2016, 6, 1), 1467331200000, '2016, 6, 1');
|
||||||
|
assert.sameValue(Date.UTC(2016, 6, 5), 1467676800000, '2016, 6, 5');
|
||||||
|
|
||||||
|
assert.sameValue(Date.UTC(2016, 6, 5, 0), 1467676800000, '2016, 6, 5, 0');
|
||||||
|
assert.sameValue(Date.UTC(2016, 6, 5, 15), 1467730800000, '2016, 6, 5, 15');
|
||||||
|
|
||||||
|
assert.sameValue(
|
||||||
|
Date.UTC(2016, 6, 5, 15, 0), 1467730800000, '2016, 6, 5, 15, 0'
|
||||||
|
);
|
||||||
|
assert.sameValue(
|
||||||
|
Date.UTC(2016, 6, 5, 15, 34), 1467732840000, '2016, 6, 5, 15, 34'
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.sameValue(
|
||||||
|
Date.UTC(2016, 6, 5, 15, 34, 0), 1467732840000, '2016, 6, 5, 15, 34, 0'
|
||||||
|
);
|
||||||
|
assert.sameValue(
|
||||||
|
Date.UTC(2016, 6, 5, 15, 34, 45), 1467732885000, '2016, 6, 5, 15, 34, 45'
|
||||||
|
);
|
||||||
|
|
||||||
|
`
|
||||||
|
|
||||||
|
testScriptWithTestLib(SCRIPT, _undefined, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewDate(t *testing.T) {
|
||||||
|
const SCRIPT = `
|
||||||
|
var d1 = new Date("2016-09-01T12:34:56Z");
|
||||||
|
d1.getUTCHours() === 12;
|
||||||
|
|
||||||
|
`
|
||||||
|
testScript(SCRIPT, valueTrue, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewDate0(t *testing.T) {
|
||||||
|
const SCRIPT = `
|
||||||
|
(new Date(0)).toUTCString();
|
||||||
|
|
||||||
|
`
|
||||||
|
testScript(SCRIPT, asciiString("Thu, 01 Jan 1970 00:00:00 GMT"), t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSetHour(t *testing.T) {
|
||||||
|
l := time.Local
|
||||||
|
defer func() {
|
||||||
|
time.Local = l
|
||||||
|
}()
|
||||||
|
var err error
|
||||||
|
time.Local, err = time.LoadLocation("America/New_York")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
const SCRIPT = `
|
||||||
|
var d = new Date(2016, 8, 1, 12, 23, 45)
|
||||||
|
assert.sameValue(d.getHours(), 12);
|
||||||
|
assert.sameValue(d.getUTCHours(), 16);
|
||||||
|
|
||||||
|
d.setHours(13);
|
||||||
|
assert.sameValue(d.getHours(), 13);
|
||||||
|
assert.sameValue(d.getMinutes(), 23);
|
||||||
|
assert.sameValue(d.getSeconds(), 45);
|
||||||
|
|
||||||
|
d.setUTCHours(13);
|
||||||
|
assert.sameValue(d.getHours(), 9);
|
||||||
|
assert.sameValue(d.getMinutes(), 23);
|
||||||
|
assert.sameValue(d.getSeconds(), 45);
|
||||||
|
|
||||||
|
`
|
||||||
|
testScriptWithTestLib(SCRIPT, _undefined, t)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSetMinute(t *testing.T) {
|
||||||
|
l := time.Local
|
||||||
|
defer func() {
|
||||||
|
time.Local = l
|
||||||
|
}()
|
||||||
|
time.Local = time.FixedZone("Asia/Delhi", 5*60*60+30*60)
|
||||||
|
|
||||||
|
const SCRIPT = `
|
||||||
|
var d = new Date(2016, 8, 1, 12, 23, 45)
|
||||||
|
assert.sameValue(d.getHours(), 12);
|
||||||
|
assert.sameValue(d.getUTCHours(), 6);
|
||||||
|
assert.sameValue(d.getMinutes(), 23);
|
||||||
|
assert.sameValue(d.getUTCMinutes(), 53);
|
||||||
|
|
||||||
|
d.setMinutes(55);
|
||||||
|
assert.sameValue(d.getMinutes(), 55);
|
||||||
|
assert.sameValue(d.getSeconds(), 45);
|
||||||
|
|
||||||
|
d.setUTCMinutes(52);
|
||||||
|
assert.sameValue(d.getMinutes(), 22);
|
||||||
|
assert.sameValue(d.getHours(), 13);
|
||||||
|
|
||||||
|
`
|
||||||
|
testScriptWithTestLib(SCRIPT, _undefined, t)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTimezoneOffset(t *testing.T) {
|
||||||
|
const SCRIPT = `
|
||||||
|
var d = new Date(0);
|
||||||
|
d.getTimezoneOffset();
|
||||||
|
`
|
||||||
|
|
||||||
|
l := time.Local
|
||||||
|
defer func() {
|
||||||
|
time.Local = l
|
||||||
|
}()
|
||||||
|
var err error
|
||||||
|
time.Local, err = time.LoadLocation("Europe/London")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
testScript(SCRIPT, intToValue(-60), t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDateValueOf(t *testing.T) {
|
||||||
|
const SCRIPT = `
|
||||||
|
var d9 = new Date(1.23e15);
|
||||||
|
d9.valueOf();
|
||||||
|
`
|
||||||
|
|
||||||
|
testScript(SCRIPT, intToValue(1.23e15), t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDateSetters(t *testing.T) {
|
||||||
|
const SCRIPT = `
|
||||||
|
assert.sameValue((new Date(0)).setMilliseconds(2345), 2345, "setMilliseconds(2345)");
|
||||||
|
assert.sameValue(new Date(1000).setMilliseconds(23450000000000), 23450000001000, "setMilliseconds(23450000000000)");
|
||||||
|
assert.sameValue((new Date(0)).setUTCMilliseconds(2345), 2345, "setUTCMilliseconds()");
|
||||||
|
assert.sameValue((new Date(0)).setSeconds(12), 12000, "setSeconds()");
|
||||||
|
assert.sameValue((new Date(0)).setUTCSeconds(12), 12000, "setUTCSeconds()");
|
||||||
|
assert.sameValue((new Date(0)).setMinutes(12), 12 * 60 * 1000, "setMinutes()");
|
||||||
|
assert.sameValue((new Date(0)).setUTCMinutes(12), 12 * 60 * 1000, "setUTCMinutes()");
|
||||||
|
assert.sameValue((new Date("2016-06-01")).setHours(1), 1464739200000, "setHours()");
|
||||||
|
assert.sameValue((new Date("2016-06-01")).setUTCHours(1), 1464742800000, "setUTCHours()");
|
||||||
|
assert.sameValue((new Date(0)).setDate(2), 86400000, "setDate()");
|
||||||
|
assert.sameValue((new Date(0)).setUTCDate(2), 86400000, "setUTCDate()");
|
||||||
|
assert.sameValue((new Date(0)).setMonth(2), 5097600000, "setMonth()");
|
||||||
|
assert.sameValue((new Date(0)).setUTCMonth(2), 5097600000, "setUTCMonth()");
|
||||||
|
assert.sameValue((new Date(0)).setFullYear(1971), 31536000000, "setFullYear()");
|
||||||
|
assert.sameValue((new Date(0)).setFullYear(1971, 2, 3), 36806400000, "setFullYear(Y,M,D)");
|
||||||
|
assert.sameValue((new Date(0)).setUTCFullYear(1971), 31536000000, "setUTCFullYear()");
|
||||||
|
assert.sameValue((new Date(0)).setUTCFullYear(1971, 2, 3), 36806400000, "setUTCFullYear(Y,M,D)");
|
||||||
|
|
||||||
|
var d = new Date();
|
||||||
|
d.setTime(1151877845000);
|
||||||
|
assert.sameValue(d.getHours(), 23, "d.getHours()");
|
||||||
|
`
|
||||||
|
|
||||||
|
l := time.Local
|
||||||
|
defer func() {
|
||||||
|
time.Local = l
|
||||||
|
}()
|
||||||
|
var err error
|
||||||
|
time.Local, err = time.LoadLocation("Europe/London")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
testScriptWithTestLib(SCRIPT, _undefined, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDateParse(t *testing.T) {
|
||||||
|
const SCRIPT = `
|
||||||
|
var zero = new Date(0);
|
||||||
|
|
||||||
|
assert.sameValue(zero.valueOf(), Date.parse(zero.toString()),
|
||||||
|
"Date.parse(zeroDate.toString())");
|
||||||
|
assert.sameValue(zero.valueOf(), Date.parse(zero.toUTCString()),
|
||||||
|
"Date.parse(zeroDate.toUTCString())");
|
||||||
|
assert.sameValue(zero.valueOf(), Date.parse(zero.toISOString()),
|
||||||
|
"Date.parse(zeroDate.toISOString())");
|
||||||
|
|
||||||
|
function testParse(str, expected) {
|
||||||
|
assert.sameValue(Date.parse(str), expected, str);
|
||||||
|
}
|
||||||
|
|
||||||
|
testParse("Mon, 02 Jan 2006 15:04:05 MST", 1136239445000);
|
||||||
|
testParse("Tue, 22 Jun 2021 13:54:40 GMT", 1624370080000);
|
||||||
|
testParse("Tuesday, 22 Jun 2021 13:54:40 GMT", 1624370080000);
|
||||||
|
testParse("Mon, 02 Jan 2006 15:04:05 GMT-07:00 (MST)", 1136239445000);
|
||||||
|
testParse("Mon, 02 Jan 2006 15:04:05 -07:00 (MST)", 1136239445000);
|
||||||
|
testParse("Monday, 02 Jan 2006 15:04:05 -0700 (MST)", 1136239445000);
|
||||||
|
testParse("Mon Jan 02 2006 15:04:05 GMT-0700 (GMT Standard Time)", 1136239445000);
|
||||||
|
testParse("Mon Jan 2 15:04:05 MST 2006", 1136239445000);
|
||||||
|
testParse("Mon Jan 02 15:04:05 MST 2006", 1136239445000);
|
||||||
|
testParse("Mon Jan 2 15:04:05 2006", 1136232245000);
|
||||||
|
testParse("Mon Jan 02 15:04:05 -0700 2006", 1136239445000);
|
||||||
|
testParse("Mon Jan 02 3:4 PM -0700 2006", 1136239440000);
|
||||||
|
|
||||||
|
testParse("December 04, 1986", 534056400000);
|
||||||
|
testParse("Dec 04, 1986", 534056400000);
|
||||||
|
testParse("Dec 4, 1986", 534056400000);
|
||||||
|
|
||||||
|
testParse("2006-01-02T15:04:05.000Z", 1136214245000);
|
||||||
|
testParse("2006-06-02T15:04:05.000", 1149275045000);
|
||||||
|
testParse("2006-01-02T15:04:05", 1136232245000);
|
||||||
|
testParse("2006-01-02 15:04:05.123", 1136232245123);
|
||||||
|
testParse("2006-01-02", 1136160000000);
|
||||||
|
testParse("2006T15:04-0700", 1136153040000);
|
||||||
|
testParse("2006T15:04+07:00", 1136102640000);
|
||||||
|
testParse("2006T15:04Z", 1136127840000);
|
||||||
|
testParse("2019-01-01T12:00:00.52Z", 1546344000520);
|
||||||
|
testParse("2019-01T12:00:00.52Z", 1546344000520);
|
||||||
|
testParse("+002019-01-01T12:00:00.52Z", 1546344000520);
|
||||||
|
|
||||||
|
var d = new Date("Mon, 02 Jan 2006 15:04:05 MST");
|
||||||
|
|
||||||
|
assert.sameValue(d.getUTCHours(), 22,
|
||||||
|
"new Date(\"Mon, 02 Jan 2006 15:04:05 MST\").getUTCHours()");
|
||||||
|
|
||||||
|
assert.sameValue(d.getHours(), 17,
|
||||||
|
"new Date(\"Mon, 02 Jan 2006 15:04:05 MST\").getHours()");
|
||||||
|
|
||||||
|
assert.sameValue(Date.parse("Mon, 02 Jan 2006 15:04:05 zzz"), NaN,
|
||||||
|
"Date.parse(\"Mon, 02 Jan 2006 15:04:05 zzz\")");
|
||||||
|
|
||||||
|
assert.sameValue(Date.parse("Mon, 02 Jan 2006 15:04:05 ZZZ"), NaN,
|
||||||
|
"Date.parse(\"Mon, 02 Jan 2006 15:04:05 ZZZ\")");
|
||||||
|
|
||||||
|
var minDateStr = "-271821-04-20T00:00:00.000Z";
|
||||||
|
var minDate = new Date(-8640000000000000);
|
||||||
|
|
||||||
|
assert.sameValue(minDate.toISOString(), minDateStr, "minDateStr");
|
||||||
|
assert.sameValue(Date.parse(minDateStr), minDate.valueOf(), "parse minDateStr");
|
||||||
|
|
||||||
|
var maxDateStr = "+275760-09-13T00:00:00.000Z";
|
||||||
|
var maxDate = new Date(8640000000000000);
|
||||||
|
|
||||||
|
assert.sameValue(maxDate.toISOString(), maxDateStr, "maxDateStr");
|
||||||
|
assert.sameValue(Date.parse(maxDateStr), maxDate.valueOf(), "parse maxDateStr");
|
||||||
|
|
||||||
|
var belowRange = "-271821-04-19T23:59:59.999Z";
|
||||||
|
var aboveRange = "+275760-09-13T00:00:00.001Z";
|
||||||
|
|
||||||
|
assert.sameValue(Date.parse(belowRange), NaN, "parse below minimum time value");
|
||||||
|
assert.sameValue(Date.parse(aboveRange), NaN, "parse above maximum time value");
|
||||||
|
`
|
||||||
|
|
||||||
|
l := time.Local
|
||||||
|
defer func() {
|
||||||
|
time.Local = l
|
||||||
|
}()
|
||||||
|
var err error
|
||||||
|
time.Local, err = time.LoadLocation("America/New_York")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
testScriptWithTestLib(SCRIPT, _undefined, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDateMaxValues(t *testing.T) {
|
||||||
|
const SCRIPT = `
|
||||||
|
assert.sameValue((new Date(0)).setUTCMilliseconds(8.64e15), 8.64e15);
|
||||||
|
assert.sameValue((new Date(0)).setUTCSeconds(8640000000000), 8.64e15);
|
||||||
|
assert.sameValue((new Date(0)).setUTCMilliseconds(-8.64e15), -8.64e15);
|
||||||
|
assert.sameValue((new Date(0)).setUTCSeconds(-8640000000000), -8.64e15);
|
||||||
|
`
|
||||||
|
testScriptWithTestLib(SCRIPT, _undefined, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDateExport(t *testing.T) {
|
||||||
|
vm := New()
|
||||||
|
res, err := vm.RunString(`new Date(1000)`)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
exp := res.Export()
|
||||||
|
if d, ok := exp.(time.Time); ok {
|
||||||
|
if d.UnixNano()/1e6 != 1000 {
|
||||||
|
t.Fatalf("Invalid exported date: %v", d)
|
||||||
|
}
|
||||||
|
if loc := d.Location(); loc != time.Local {
|
||||||
|
t.Fatalf("Invalid timezone: %v", loc)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
t.Fatalf("Invalid export type: %T", exp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDateToJSON(t *testing.T) {
|
||||||
|
const SCRIPT = `
|
||||||
|
Date.prototype.toJSON.call({ toISOString: function () { return 1; } })
|
||||||
|
`
|
||||||
|
testScript(SCRIPT, intToValue(1), t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDateExportType(t *testing.T) {
|
||||||
|
vm := New()
|
||||||
|
v, err := vm.RunString(`new Date()`)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if typ := v.ExportType(); typ != typeTime {
|
||||||
|
t.Fatal(typ)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDateParseV8(t *testing.T) {
|
||||||
|
// Taken from https://chromium.googlesource.com/v8/v8/+/refs/heads/main/test/mjsunit/date-parse.js
|
||||||
|
const SCRIPT = `
|
||||||
|
const assertEquals = assert.sameValue,
|
||||||
|
assertTrue = assert;
|
||||||
|
|
||||||
|
// Copyright 2008 the V8 project authors. All rights reserved.
|
||||||
|
// Redistribution and use in source and binary forms, with or without
|
||||||
|
// modification, are permitted provided that the following conditions are
|
||||||
|
// met:
|
||||||
|
//
|
||||||
|
// * Redistributions of source code must retain the above copyright
|
||||||
|
// notice, this list of conditions and the following disclaimer.
|
||||||
|
// * Redistributions in binary form must reproduce the above
|
||||||
|
// copyright notice, this list of conditions and the following
|
||||||
|
// disclaimer in the documentation and/or other materials provided
|
||||||
|
// with the distribution.
|
||||||
|
// * Neither the name of Google Inc. nor the names of its
|
||||||
|
// contributors may be used to endorse or promote products derived
|
||||||
|
// from this software without specific prior written permission.
|
||||||
|
//
|
||||||
|
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
// Test that we can parse dates in all the different formats that we
|
||||||
|
// have to support.
|
||||||
|
//
|
||||||
|
// These formats are all supported by KJS but a lot of them are not
|
||||||
|
// supported by Spidermonkey.
|
||||||
|
function testDateParse(string) {
|
||||||
|
var d = Date.parse(string);
|
||||||
|
assertEquals(946713600000, d, "parse: " + string);
|
||||||
|
};
|
||||||
|
// For local time we just test that parsing returns non-NaN positive
|
||||||
|
// number of milliseconds to make it timezone independent.
|
||||||
|
function testDateParseLocalTime(string) {
|
||||||
|
var d = Date.parse("parse-local-time:" + string);
|
||||||
|
assertTrue(!isNaN(d), "parse-local-time: " + string + " is NaN.");
|
||||||
|
assertTrue(d > 0, "parse-local-time: " + string + " <= 0.");
|
||||||
|
};
|
||||||
|
function testDateParseMisc(array) {
|
||||||
|
assertEquals(2, array.length, "array [" + array + "] length != 2.");
|
||||||
|
var string = array[0];
|
||||||
|
var expected = array[1];
|
||||||
|
var d = Date.parse(string);
|
||||||
|
assertEquals(expected, d, "parse-misc: " + string);
|
||||||
|
}
|
||||||
|
//
|
||||||
|
// Test all the formats in UT timezone.
|
||||||
|
//
|
||||||
|
var testCasesUT = [
|
||||||
|
'Sat, 01-Jan-2000 08:00:00 UT',
|
||||||
|
'Sat, 01 Jan 2000 08:00:00 UT',
|
||||||
|
'Jan 01 2000 08:00:00 UT',
|
||||||
|
'Jan 01 08:00:00 UT 2000',
|
||||||
|
'Saturday, 01-Jan-00 08:00:00 UT',
|
||||||
|
'01 Jan 00 08:00 +0000',
|
||||||
|
// Ignore weekdays.
|
||||||
|
'Mon, 01 Jan 2000 08:00:00 UT',
|
||||||
|
'Tue, 01 Jan 2000 08:00:00 UT',
|
||||||
|
// Ignore prefix that is not part of a date.
|
||||||
|
'[Saturday] Jan 01 08:00:00 UT 2000',
|
||||||
|
'Ignore all of this stuff because it is annoying 01 Jan 2000 08:00:00 UT',
|
||||||
|
'[Saturday] Jan 01 2000 08:00:00 UT',
|
||||||
|
'All of this stuff is really annoying, so it will be ignored Jan 01 2000 08:00:00 UT',
|
||||||
|
// If the three first letters of the month is a
|
||||||
|
// month name we are happy - ignore the rest.
|
||||||
|
'Sat, 01-Janisamonth-2000 08:00:00 UT',
|
||||||
|
'Sat, 01 Janisamonth 2000 08:00:00 UT',
|
||||||
|
'Janisamonth 01 2000 08:00:00 UT',
|
||||||
|
'Janisamonth 01 08:00:00 UT 2000',
|
||||||
|
'Saturday, 01-Janisamonth-00 08:00:00 UT',
|
||||||
|
'01 Janisamonth 00 08:00 +0000',
|
||||||
|
// Allow missing space between month and day.
|
||||||
|
'Janisamonthandtherestisignored01 2000 08:00:00 UT',
|
||||||
|
'Jan01 2000 08:00:00 UT',
|
||||||
|
// Allow year/month/day format.
|
||||||
|
'Sat, 2000/01/01 08:00:00 UT',
|
||||||
|
// Allow month/day/year format.
|
||||||
|
'Sat, 01/01/2000 08:00:00 UT',
|
||||||
|
// Allow month/day year format.
|
||||||
|
'Sat, 01/01 2000 08:00:00 UT',
|
||||||
|
// Allow comma instead of space after day, month and year.
|
||||||
|
'Sat, 01,Jan,2000,08:00:00 UT',
|
||||||
|
// Seconds are optional.
|
||||||
|
'Sat, 01-Jan-2000 08:00 UT',
|
||||||
|
'Sat, 01 Jan 2000 08:00 UT',
|
||||||
|
'Jan 01 2000 08:00 UT',
|
||||||
|
'Jan 01 08:00 UT 2000',
|
||||||
|
'Saturday, 01-Jan-00 08:00 UT',
|
||||||
|
'01 Jan 00 08:00 +0000',
|
||||||
|
// Allow AM/PM after the time.
|
||||||
|
'Sat, 01-Jan-2000 08:00 AM UT',
|
||||||
|
'Sat, 01 Jan 2000 08:00 AM UT',
|
||||||
|
'Jan 01 2000 08:00 AM UT',
|
||||||
|
'Jan 01 08:00 AM UT 2000',
|
||||||
|
'Saturday, 01-Jan-00 08:00 AM UT',
|
||||||
|
'01 Jan 00 08:00 AM +0000',
|
||||||
|
// White space and stuff in parenthesis is
|
||||||
|
// apparently allowed in most places where white
|
||||||
|
// space is allowed.
|
||||||
|
' Sat, 01-Jan-2000 08:00:00 UT ',
|
||||||
|
' Sat, 01 Jan 2000 08:00:00 UT ',
|
||||||
|
' Saturday, 01-Jan-00 08:00:00 UT ',
|
||||||
|
' 01 Jan 00 08:00 +0000 ',
|
||||||
|
' ()(Sat, 01-Jan-2000) Sat, 01-Jan-2000 08:00:00 UT ',
|
||||||
|
' Sat()(Sat, 01-Jan-2000)01 Jan 2000 08:00:00 UT ',
|
||||||
|
' Sat,(02)01 Jan 2000 08:00:00 UT ',
|
||||||
|
' Sat, 01(02)Jan 2000 08:00:00 UT ',
|
||||||
|
' Sat, 01 Jan 2000 (2001)08:00:00 UT ',
|
||||||
|
' Sat, 01 Jan 2000 (01)08:00:00 UT ',
|
||||||
|
' Sat, 01 Jan 2000 (01:00:00)08:00:00 UT ',
|
||||||
|
' Sat, 01 Jan 2000 08:00:00 (CDT)UT ',
|
||||||
|
' Sat, 01 Jan 2000 08:00:00 UT((((CDT))))',
|
||||||
|
' Saturday, 01-Jan-00 ()(((asfd)))(Sat, 01-Jan-2000)08:00:00 UT ',
|
||||||
|
' 01 Jan 00 08:00 ()(((asdf)))(Sat, 01-Jan-2000)+0000 ',
|
||||||
|
' 01 Jan 00 08:00 +0000()((asfd)(Sat, 01-Jan-2000)) '];
|
||||||
|
//
|
||||||
|
// Test that we do the right correction for different time zones.
|
||||||
|
// I'll assume that we can handle the same formats as for UT and only
|
||||||
|
// test a few formats for each of the timezones.
|
||||||
|
//
|
||||||
|
// GMT = UT
|
||||||
|
var testCasesGMT = [
|
||||||
|
'Sat, 01-Jan-2000 08:00:00 GMT',
|
||||||
|
'Sat, 01-Jan-2000 08:00:00 GMT+0',
|
||||||
|
'Sat, 01-Jan-2000 08:00:00 GMT+00',
|
||||||
|
'Sat, 01-Jan-2000 08:00:00 GMT+000',
|
||||||
|
'Sat, 01-Jan-2000 08:00:00 GMT+0000',
|
||||||
|
'Sat, 01-Jan-2000 08:00:00 GMT+00:00', // Interestingly, KJS cannot handle this.
|
||||||
|
'Sat, 01 Jan 2000 08:00:00 GMT',
|
||||||
|
'Saturday, 01-Jan-00 08:00:00 GMT',
|
||||||
|
'01 Jan 00 08:00 -0000',
|
||||||
|
'01 Jan 00 08:00 +0000'];
|
||||||
|
// EST = UT minus 5 hours.
|
||||||
|
var testCasesEST = [
|
||||||
|
'Sat, 01-Jan-2000 03:00:00 UTC-0500',
|
||||||
|
'Sat, 01-Jan-2000 03:00:00 UTC-05:00', // Interestingly, KJS cannot handle this.
|
||||||
|
'Sat, 01-Jan-2000 03:00:00 EST',
|
||||||
|
'Sat, 01 Jan 2000 03:00:00 EST',
|
||||||
|
'Saturday, 01-Jan-00 03:00:00 EST',
|
||||||
|
'01 Jan 00 03:00 -0500'];
|
||||||
|
// EDT = UT minus 4 hours.
|
||||||
|
var testCasesEDT = [
|
||||||
|
'Sat, 01-Jan-2000 04:00:00 EDT',
|
||||||
|
'Sat, 01 Jan 2000 04:00:00 EDT',
|
||||||
|
'Saturday, 01-Jan-00 04:00:00 EDT',
|
||||||
|
'01 Jan 00 04:00 -0400'];
|
||||||
|
// CST = UT minus 6 hours.
|
||||||
|
var testCasesCST = [
|
||||||
|
'Sat, 01-Jan-2000 02:00:00 CST',
|
||||||
|
'Sat, 01 Jan 2000 02:00:00 CST',
|
||||||
|
'Saturday, 01-Jan-00 02:00:00 CST',
|
||||||
|
'01 Jan 00 02:00 -0600'];
|
||||||
|
// CDT = UT minus 5 hours.
|
||||||
|
var testCasesCDT = [
|
||||||
|
'Sat, 01-Jan-2000 03:00:00 CDT',
|
||||||
|
'Sat, 01 Jan 2000 03:00:00 CDT',
|
||||||
|
'Saturday, 01-Jan-00 03:00:00 CDT',
|
||||||
|
'01 Jan 00 03:00 -0500'];
|
||||||
|
// MST = UT minus 7 hours.
|
||||||
|
var testCasesMST = [
|
||||||
|
'Sat, 01-Jan-2000 01:00:00 MST',
|
||||||
|
'Sat, 01 Jan 2000 01:00:00 MST',
|
||||||
|
'Saturday, 01-Jan-00 01:00:00 MST',
|
||||||
|
'01 Jan 00 01:00 -0700'];
|
||||||
|
// MDT = UT minus 6 hours.
|
||||||
|
var testCasesMDT = [
|
||||||
|
'Sat, 01-Jan-2000 02:00:00 MDT',
|
||||||
|
'Sat, 01 Jan 2000 02:00:00 MDT',
|
||||||
|
'Saturday, 01-Jan-00 02:00:00 MDT',
|
||||||
|
'01 Jan 00 02:00 -0600'];
|
||||||
|
// PST = UT minus 8 hours.
|
||||||
|
var testCasesPST = [
|
||||||
|
'Sat, 01-Jan-2000 00:00:00 PST',
|
||||||
|
'Sat, 01 Jan 2000 00:00:00 PST',
|
||||||
|
'Saturday, 01-Jan-00 00:00:00 PST',
|
||||||
|
'01 Jan 00 00:00 -0800',
|
||||||
|
// Allow missing time.
|
||||||
|
'Sat, 01-Jan-2000 PST'];
|
||||||
|
// PDT = UT minus 7 hours.
|
||||||
|
var testCasesPDT = [
|
||||||
|
'Sat, 01-Jan-2000 01:00:00 PDT',
|
||||||
|
'Sat, 01 Jan 2000 01:00:00 PDT',
|
||||||
|
'Saturday, 01-Jan-00 01:00:00 PDT',
|
||||||
|
'01 Jan 00 01:00 -0700'];
|
||||||
|
// Local time cases.
|
||||||
|
var testCasesLocalTime = [
|
||||||
|
// Allow timezone omission.
|
||||||
|
'Sat, 01-Jan-2000 08:00:00',
|
||||||
|
'Sat, 01 Jan 2000 08:00:00',
|
||||||
|
'Jan 01 2000 08:00:00',
|
||||||
|
'Jan 01 08:00:00 2000',
|
||||||
|
'Saturday, 01-Jan-00 08:00:00',
|
||||||
|
'01 Jan 00 08:00'];
|
||||||
|
// Misc. test cases that result in a different time value.
|
||||||
|
var testCasesMisc = [
|
||||||
|
// Special handling for years in the [0, 100) range.
|
||||||
|
['Sat, 01 Jan 0 08:00:00 UT', 946713600000], // year 2000
|
||||||
|
['Sat, 01 Jan 49 08:00:00 UT', 2493100800000], // year 2049
|
||||||
|
['Sat, 01 Jan 50 08:00:00 UT', -631123200000], // year 1950
|
||||||
|
['Sat, 01 Jan 99 08:00:00 UT', 915177600000], // year 1999
|
||||||
|
['Sat, 01 Jan 100 08:00:00 UT', -59011430400000], // year 100
|
||||||
|
// Test PM after time.
|
||||||
|
['Sat, 01-Jan-2000 08:00 PM UT', 946756800000],
|
||||||
|
['Sat, 01 Jan 2000 08:00 PM UT', 946756800000],
|
||||||
|
['Jan 01 2000 08:00 PM UT', 946756800000],
|
||||||
|
['Jan 01 08:00 PM UT 2000', 946756800000],
|
||||||
|
['Saturday, 01-Jan-00 08:00 PM UT', 946756800000],
|
||||||
|
['01 Jan 00 08:00 PM +0000', 946756800000]];
|
||||||
|
// Test different version of the ES5 date time string format.
|
||||||
|
var testCasesES5Misc = [
|
||||||
|
['2000-01-01T08:00:00.000Z', 946713600000],
|
||||||
|
['2000-01-01T08:00:00Z', 946713600000],
|
||||||
|
['2000-01-01T08:00Z', 946713600000],
|
||||||
|
['2000-01T08:00:00.000Z', 946713600000],
|
||||||
|
['2000T08:00:00.000Z', 946713600000],
|
||||||
|
['2000T08:00Z', 946713600000],
|
||||||
|
['2000-01T00:00:00.000-08:00', 946713600000],
|
||||||
|
['2000-01T08:00:00.001Z', 946713600001],
|
||||||
|
['2000-01T08:00:00.099Z', 946713600099],
|
||||||
|
['2000-01T08:00:00.999Z', 946713600999],
|
||||||
|
['2000-01T00:00:00.001-08:00', 946713600001],
|
||||||
|
['2000-01-01T24:00Z', 946771200000],
|
||||||
|
['2000-01-01T24:00:00Z', 946771200000],
|
||||||
|
['2000-01-01T24:00:00.000Z', 946771200000],
|
||||||
|
['2000-01-01T24:00:00.000Z', 946771200000]];
|
||||||
|
var testCasesES5MiscNegative = [
|
||||||
|
'2000-01-01TZ',
|
||||||
|
'2000-01-01T60Z',
|
||||||
|
'2000-01-01T60:60Z',
|
||||||
|
'2000-01-0108:00Z',
|
||||||
|
'2000-01-01T08Z',
|
||||||
|
'2000-01-01T24:01',
|
||||||
|
'2000-01-01T24:00:01',
|
||||||
|
'2000-01-01T24:00:00.001',
|
||||||
|
'2000-01-01T24:00:00.999Z'];
|
||||||
|
// TODO(littledan): This is an hack that could break in historically
|
||||||
|
// changing timezones that happened on this day, but allows us to
|
||||||
|
// check the date value for local times.
|
||||||
|
var localOffset = new Date('2000-01-01').getTimezoneOffset()*1000*60;
|
||||||
|
// Sanity check which is even more of a hack: in the timezones where
|
||||||
|
// these tests are likely to be run, the offset is nonzero because
|
||||||
|
// dates which don't include Z are in the local timezone.
|
||||||
|
if (this.Intl &&
|
||||||
|
["America/Los_Angeles", "Europe/Berlin", "Europe/Madrid"].indexOf(
|
||||||
|
Intl.DateTimeFormat().resolvedOptions().timeZone) != -1) {
|
||||||
|
assertTrue(localOffset != 0);
|
||||||
|
}
|
||||||
|
var testCasesES2016TZ = [
|
||||||
|
// If the timezone is absent and time is present, use local time
|
||||||
|
['2000-01-02T00:00', 946771200000 + localOffset],
|
||||||
|
['2000-01-02T00:00:00', 946771200000 + localOffset],
|
||||||
|
['2000-01-02T00:00:00.000', 946771200000 + localOffset],
|
||||||
|
// If timezone is absent and time is absent, use UTC
|
||||||
|
['2000-01-02', 946771200000],
|
||||||
|
['2000-01-02', 946771200000],
|
||||||
|
['2000-01-02', 946771200000],
|
||||||
|
];
|
||||||
|
// Run all the tests.
|
||||||
|
testCasesUT.forEach(testDateParse);
|
||||||
|
testCasesGMT.forEach(testDateParse);
|
||||||
|
testCasesEST.forEach(testDateParse);
|
||||||
|
testCasesEDT.forEach(testDateParse);
|
||||||
|
testCasesCST.forEach(testDateParse);
|
||||||
|
testCasesCDT.forEach(testDateParse);
|
||||||
|
testCasesMST.forEach(testDateParse);
|
||||||
|
testCasesMDT.forEach(testDateParse);
|
||||||
|
testCasesPST.forEach(testDateParse);
|
||||||
|
testCasesPDT.forEach(testDateParse);
|
||||||
|
testCasesLocalTime.forEach(testDateParseLocalTime);
|
||||||
|
testCasesMisc.forEach(testDateParseMisc);
|
||||||
|
// ES5 date time string format compliance.
|
||||||
|
testCasesES5Misc.forEach(testDateParseMisc);
|
||||||
|
testCasesES5MiscNegative.forEach(function (s) {
|
||||||
|
assertTrue(isNaN(Date.parse(s)), s + " is not NaN.");
|
||||||
|
});
|
||||||
|
testCasesES2016TZ.forEach(testDateParseMisc);
|
||||||
|
// Test that we can parse our own date format.
|
||||||
|
// (Dates from 1970 to ~2070 with 150h steps.)
|
||||||
|
for (var i = 0; i < 24 * 365 * 100; i += 150) {
|
||||||
|
var ms = i * (3600 * 1000);
|
||||||
|
var s = (new Date(ms)).toString();
|
||||||
|
assertEquals(ms, Date.parse(s), "parse own: " + s);
|
||||||
|
}
|
||||||
|
// Negative tests.
|
||||||
|
var testCasesNegative = [
|
||||||
|
'May 25 2008 1:30 (PM)) UTC', // Bad unmatched ')' after number.
|
||||||
|
'May 25 2008 1:30( )AM (PM)', //
|
||||||
|
'a1', // Issue 126448, 53209.
|
||||||
|
'nasfdjklsfjoaifg1',
|
||||||
|
'x_2',
|
||||||
|
'May 25 2008 AAA (GMT)']; // Unknown word after number.
|
||||||
|
testCasesNegative.forEach(function (s) {
|
||||||
|
assertTrue(isNaN(Date.parse(s)), s + " is not NaN.");
|
||||||
|
});
|
||||||
|
`
|
||||||
|
testScriptWithTestLib(SCRIPT, _undefined, t)
|
||||||
|
}
|
||||||
3
goja/extract_failed_tests.sh
Executable file
3
goja/extract_failed_tests.sh
Executable file
@ -0,0 +1,3 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
sed -En 's/^.*FAIL: TestTC39\/tc39\/(test\/.*.js).*$/"\1": true,/p'
|
||||||
110
goja/file/README.markdown
Normal file
110
goja/file/README.markdown
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
# file
|
||||||
|
--
|
||||||
|
import "apigo.cc/gojs/goja/file"
|
||||||
|
|
||||||
|
Package file encapsulates the file abstractions used by the ast & parser.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
#### type File
|
||||||
|
|
||||||
|
```go
|
||||||
|
type File struct {
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
#### func NewFile
|
||||||
|
|
||||||
|
```go
|
||||||
|
func NewFile(filename, src string, base int) *File
|
||||||
|
```
|
||||||
|
|
||||||
|
#### func (*File) Base
|
||||||
|
|
||||||
|
```go
|
||||||
|
func (fl *File) Base() int
|
||||||
|
```
|
||||||
|
|
||||||
|
#### func (*File) Name
|
||||||
|
|
||||||
|
```go
|
||||||
|
func (fl *File) Name() string
|
||||||
|
```
|
||||||
|
|
||||||
|
#### func (*File) Source
|
||||||
|
|
||||||
|
```go
|
||||||
|
func (fl *File) Source() string
|
||||||
|
```
|
||||||
|
|
||||||
|
#### type FileSet
|
||||||
|
|
||||||
|
```go
|
||||||
|
type FileSet struct {
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
A FileSet represents a set of source files.
|
||||||
|
|
||||||
|
#### func (*FileSet) AddFile
|
||||||
|
|
||||||
|
```go
|
||||||
|
func (self *FileSet) AddFile(filename, src string) int
|
||||||
|
```
|
||||||
|
AddFile adds a new file with the given filename and src.
|
||||||
|
|
||||||
|
This an internal method, but exported for cross-package use.
|
||||||
|
|
||||||
|
#### func (*FileSet) File
|
||||||
|
|
||||||
|
```go
|
||||||
|
func (self *FileSet) File(idx Idx) *File
|
||||||
|
```
|
||||||
|
|
||||||
|
#### func (*FileSet) Position
|
||||||
|
|
||||||
|
```go
|
||||||
|
func (self *FileSet) Position(idx Idx) *Position
|
||||||
|
```
|
||||||
|
Position converts an Idx in the FileSet into a Position.
|
||||||
|
|
||||||
|
#### type Idx
|
||||||
|
|
||||||
|
```go
|
||||||
|
type Idx int
|
||||||
|
```
|
||||||
|
|
||||||
|
Idx is a compact encoding of a source position within a file set. It can be
|
||||||
|
converted into a Position for a more convenient, but much larger,
|
||||||
|
representation.
|
||||||
|
|
||||||
|
#### type Position
|
||||||
|
|
||||||
|
```go
|
||||||
|
type Position struct {
|
||||||
|
Filename string // The filename where the error occurred, if any
|
||||||
|
Offset int // The src offset
|
||||||
|
Line int // The line number, starting at 1
|
||||||
|
Column int // The column number, starting at 1 (The character count)
|
||||||
|
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Position describes an arbitrary source position including the filename, line,
|
||||||
|
and column location.
|
||||||
|
|
||||||
|
#### func (*Position) String
|
||||||
|
|
||||||
|
```go
|
||||||
|
func (self *Position) String() string
|
||||||
|
```
|
||||||
|
String returns a string in one of several forms:
|
||||||
|
|
||||||
|
file:line:column A valid position with filename
|
||||||
|
line:column A valid position without filename
|
||||||
|
file An invalid position with filename
|
||||||
|
- An invalid position without filename
|
||||||
|
|
||||||
|
--
|
||||||
|
**godocdown** http://github.com/robertkrimen/godocdown
|
||||||
@ -5,6 +5,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"net/url"
|
"net/url"
|
||||||
"path"
|
"path"
|
||||||
|
"path/filepath"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
@ -182,9 +183,10 @@ func (fl *File) Position(offset int) Position {
|
|||||||
|
|
||||||
func ResolveSourcemapURL(basename, source string) *url.URL {
|
func ResolveSourcemapURL(basename, source string) *url.URL {
|
||||||
// if the url is absolute(has scheme) there is nothing to do
|
// if the url is absolute(has scheme) there is nothing to do
|
||||||
smURL, err := url.Parse(strings.TrimSpace(source))
|
smURL, err := url.Parse(filepath.ToSlash(strings.TrimSpace(source)))
|
||||||
if err == nil && !smURL.IsAbs() {
|
if err == nil && !smURL.IsAbs() {
|
||||||
baseURL, err1 := url.Parse(strings.TrimSpace(basename))
|
basename = filepath.ToSlash(strings.TrimSpace(basename))
|
||||||
|
baseURL, err1 := url.Parse(basename)
|
||||||
if err1 == nil && path.IsAbs(baseURL.Path) {
|
if err1 == nil && path.IsAbs(baseURL.Path) {
|
||||||
smURL = baseURL.ResolveReference(smURL)
|
smURL = baseURL.ResolveReference(smURL)
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
76
goja/file/file_test.go
Normal file
76
goja/file/file_test.go
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
package file
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestPosition(t *testing.T) {
|
||||||
|
const SRC = `line1
|
||||||
|
line2
|
||||||
|
line3`
|
||||||
|
f := NewFile("", SRC, 0)
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
offset int
|
||||||
|
line int
|
||||||
|
col int
|
||||||
|
}{
|
||||||
|
{0, 1, 1},
|
||||||
|
{2, 1, 3},
|
||||||
|
{2, 1, 3},
|
||||||
|
{6, 2, 1},
|
||||||
|
{7, 2, 2},
|
||||||
|
{12, 3, 1},
|
||||||
|
{12, 3, 1},
|
||||||
|
{13, 3, 2},
|
||||||
|
{13, 3, 2},
|
||||||
|
{16, 3, 5},
|
||||||
|
{17, 3, 6},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, test := range tests {
|
||||||
|
if p := f.Position(test.offset); p.Line != test.line || p.Column != test.col {
|
||||||
|
t.Fatalf("%d. Line: %d, col: %d", i, p.Line, p.Column)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFileConcurrency(t *testing.T) {
|
||||||
|
const SRC = `line1
|
||||||
|
line2
|
||||||
|
line3`
|
||||||
|
f := NewFile("", SRC, 0)
|
||||||
|
go func() {
|
||||||
|
f.Position(12)
|
||||||
|
}()
|
||||||
|
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"},
|
||||||
|
{"\ntest.js", "base123.js", "test.js"},
|
||||||
|
{"\rtest2.js\t\n ", "base123.js", "test2.js"},
|
||||||
|
{"z:/file.map", "a.js", "z:/file.map"},
|
||||||
|
// 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
21
goja/ftoa/LICENSE_LUCENE
Normal file
21
goja/ftoa/LICENSE_LUCENE
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
Copyright (C) 1998, 1999 by Lucent Technologies
|
||||||
|
All Rights Reserved
|
||||||
|
|
||||||
|
Permission to use, copy, modify, and distribute this software and
|
||||||
|
its documentation for any purpose and without fee is hereby
|
||||||
|
granted, provided that the above copyright notice appear in all
|
||||||
|
copies and that both that the copyright notice and this
|
||||||
|
permission notice and warranty disclaimer appear in supporting
|
||||||
|
documentation, and that the name of Lucent or any of its entities
|
||||||
|
not be used in advertising or publicity pertaining to
|
||||||
|
distribution of the software without specific, written prior
|
||||||
|
permission.
|
||||||
|
|
||||||
|
LUCENT DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
|
||||||
|
INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS.
|
||||||
|
IN NO EVENT SHALL LUCENT OR ANY OF ITS ENTITIES BE LIABLE FOR ANY
|
||||||
|
SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER
|
||||||
|
IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
|
||||||
|
ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
|
||||||
|
THIS SOFTWARE.
|
||||||
9
goja/ftoa/ftobasestr_test.go
Normal file
9
goja/ftoa/ftobasestr_test.go
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
package ftoa
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
func TestFToBaseStr(t *testing.T) {
|
||||||
|
if s := FToBaseStr(0.8466400793967279, 36); s != "0.uh8u81s3fz" {
|
||||||
|
t.Fatal(s)
|
||||||
|
}
|
||||||
|
}
|
||||||
92
goja/ftoa/ftostr_test.go
Normal file
92
goja/ftoa/ftostr_test.go
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
package ftoa
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math"
|
||||||
|
"strconv"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func _testFToStr(num float64, mode FToStrMode, precision int, expected string, t *testing.T) {
|
||||||
|
buf := FToStr(num, mode, precision, nil)
|
||||||
|
if s := string(buf); s != expected {
|
||||||
|
t.Fatalf("expected: '%s', actual: '%s", expected, s)
|
||||||
|
}
|
||||||
|
if !math.IsNaN(num) && num != 0 && !math.Signbit(num) {
|
||||||
|
_testFToStr(-num, mode, precision, "-"+expected, t)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testFToStr(num float64, mode FToStrMode, precision int, expected string, t *testing.T) {
|
||||||
|
t.Run("", func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
_testFToStr(num, mode, precision, expected, t)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDtostr(t *testing.T) {
|
||||||
|
testFToStr(0, ModeStandard, 0, "0", t)
|
||||||
|
testFToStr(1, ModeStandard, 0, "1", t)
|
||||||
|
testFToStr(9007199254740991, ModeStandard, 0, "9007199254740991", t)
|
||||||
|
testFToStr(math.MaxInt64, ModeStandardExponential, 0, "9.223372036854776e+18", t)
|
||||||
|
testFToStr(1e-5, ModeFixed, 1, "0.0", t)
|
||||||
|
testFToStr(8.85, ModeExponential, 2, "8.8e+0", t)
|
||||||
|
testFToStr(885, ModeExponential, 2, "8.9e+2", t)
|
||||||
|
testFToStr(25, ModeExponential, 1, "3e+1", t)
|
||||||
|
testFToStr(1e-6, ModeFixed, 7, "0.0000010", t)
|
||||||
|
testFToStr(math.Pi, ModeStandardExponential, 0, "3.141592653589793e+0", t)
|
||||||
|
testFToStr(math.Inf(1), ModeStandard, 0, "Infinity", t)
|
||||||
|
testFToStr(math.NaN(), ModeStandard, 0, "NaN", t)
|
||||||
|
testFToStr(math.SmallestNonzeroFloat64, ModeExponential, 40, "4.940656458412465441765687928682213723651e-324", t)
|
||||||
|
testFToStr(3.5844466002796428e+298, ModeStandard, 0, "3.5844466002796428e+298", t)
|
||||||
|
testFToStr(math.Float64frombits(0x0010000000000000), ModeStandard, 0, "2.2250738585072014e-308", t) // smallest normal
|
||||||
|
testFToStr(math.Float64frombits(0x000FFFFFFFFFFFFF), ModeStandard, 0, "2.225073858507201e-308", t) // largest denormal
|
||||||
|
testFToStr(4294967272.0, ModePrecision, 14, "4294967272.0000", t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkDtostrSmall(b *testing.B) {
|
||||||
|
var buf [128]byte
|
||||||
|
b.ReportAllocs()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
FToStr(math.Pi, ModeStandardExponential, 0, buf[:0])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkDtostrShort(b *testing.B) {
|
||||||
|
var buf [128]byte
|
||||||
|
b.ReportAllocs()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
FToStr(3.1415, ModeStandard, 0, buf[:0])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkDtostrFixed(b *testing.B) {
|
||||||
|
var buf [128]byte
|
||||||
|
b.ReportAllocs()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
FToStr(math.Pi, ModeFixed, 4, buf[:0])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkDtostrBig(b *testing.B) {
|
||||||
|
var buf [128]byte
|
||||||
|
b.ReportAllocs()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
FToStr(math.SmallestNonzeroFloat64, ModeExponential, 40, buf[:0])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkAppendFloatBig(b *testing.B) {
|
||||||
|
var buf [128]byte
|
||||||
|
b.ReportAllocs()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
strconv.AppendFloat(buf[:0], math.SmallestNonzeroFloat64, 'e', 40, 64)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkAppendFloatSmall(b *testing.B) {
|
||||||
|
var buf [128]byte
|
||||||
|
b.ReportAllocs()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
strconv.AppendFloat(buf[:0], math.Pi, 'e', -1, 64)
|
||||||
|
}
|
||||||
|
}
|
||||||
26
goja/ftoa/internal/fast/LICENSE_V8
Normal file
26
goja/ftoa/internal/fast/LICENSE_V8
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
Copyright 2014, the V8 project authors. All rights reserved.
|
||||||
|
Redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are
|
||||||
|
met:
|
||||||
|
|
||||||
|
* Redistributions of source code must retain the above copyright
|
||||||
|
notice, this list of conditions and the following disclaimer.
|
||||||
|
* Redistributions in binary form must reproduce the above
|
||||||
|
copyright notice, this list of conditions and the following
|
||||||
|
disclaimer in the documentation and/or other materials provided
|
||||||
|
with the distribution.
|
||||||
|
* Neither the name of Google Inc. nor the names of its
|
||||||
|
contributors may be used to endorse or promote products derived
|
||||||
|
from this software without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
10
goja/func.go
10
goja/func.go
@ -293,8 +293,16 @@ func (f *classFuncObject) vmCall(vm *vm, n int) {
|
|||||||
f.Call(FunctionCall{})
|
f.Call(FunctionCall{})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (f *classFuncObject) exportType() reflect.Type {
|
||||||
|
return reflectTypeCtor
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *classFuncObject) Construct(ccall ConstructorCall) *Object {
|
||||||
|
return f.construct(ccall.Arguments, ccall.NewTarget)
|
||||||
|
}
|
||||||
|
|
||||||
func (f *classFuncObject) export(*objectExportCtx) interface{} {
|
func (f *classFuncObject) export(*objectExportCtx) interface{} {
|
||||||
return f.Call
|
return f.Construct
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *classFuncObject) createInstance(args []Value, newTarget *Object) (instance *Object) {
|
func (f *classFuncObject) createInstance(args []Value, newTarget *Object) (instance *Object) {
|
||||||
|
|||||||
305
goja/func_test.go
Normal file
305
goja/func_test.go
Normal file
@ -0,0 +1,305 @@
|
|||||||
|
package goja
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestFuncProto(t *testing.T) {
|
||||||
|
const SCRIPT = `
|
||||||
|
"use strict";
|
||||||
|
function A() {}
|
||||||
|
A.__proto__ = Object;
|
||||||
|
A.prototype = {};
|
||||||
|
|
||||||
|
function B() {}
|
||||||
|
B.__proto__ = Object.create(null);
|
||||||
|
var thrown = false;
|
||||||
|
try {
|
||||||
|
delete B.prototype;
|
||||||
|
} catch (e) {
|
||||||
|
thrown = e instanceof TypeError;
|
||||||
|
}
|
||||||
|
thrown;
|
||||||
|
`
|
||||||
|
testScript(SCRIPT, valueTrue, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFuncPrototypeRedefine(t *testing.T) {
|
||||||
|
const SCRIPT = `
|
||||||
|
let thrown = false;
|
||||||
|
try {
|
||||||
|
Object.defineProperty(function() {}, "prototype", {
|
||||||
|
set: function(_value) {},
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
if (e instanceof TypeError) {
|
||||||
|
thrown = true;
|
||||||
|
} else {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
thrown;
|
||||||
|
`
|
||||||
|
|
||||||
|
testScript(SCRIPT, valueTrue, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFuncExport(t *testing.T) {
|
||||||
|
vm := New()
|
||||||
|
typ := reflect.TypeOf((func(FunctionCall) Value)(nil))
|
||||||
|
|
||||||
|
f := func(expr string, t *testing.T) {
|
||||||
|
v, err := vm.RunString(expr)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if actualTyp := v.ExportType(); actualTyp != typ {
|
||||||
|
t.Fatalf("Invalid export type: %v", actualTyp)
|
||||||
|
}
|
||||||
|
ev := v.Export()
|
||||||
|
if actualTyp := reflect.TypeOf(ev); actualTyp != typ {
|
||||||
|
t.Fatalf("Invalid export value: %v", ev)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run("regular function", func(t *testing.T) {
|
||||||
|
f("(function() {})", t)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("arrow function", func(t *testing.T) {
|
||||||
|
f("(()=>{})", t)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("method", func(t *testing.T) {
|
||||||
|
f("({m() {}}).m", t)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFuncWrapUnwrap(t *testing.T) {
|
||||||
|
vm := New()
|
||||||
|
f := func(a int, b string) bool {
|
||||||
|
return a > 0 && b != ""
|
||||||
|
}
|
||||||
|
var f1 func(int, string) bool
|
||||||
|
v := vm.ToValue(f)
|
||||||
|
if et := v.ExportType(); et != reflect.TypeOf(f1) {
|
||||||
|
t.Fatal(et)
|
||||||
|
}
|
||||||
|
err := vm.ExportTo(v, &f1)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if !f1(1, "a") {
|
||||||
|
t.Fatal("not true")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWrappedFunc(t *testing.T) {
|
||||||
|
vm := New()
|
||||||
|
f := func(a int, b string) bool {
|
||||||
|
return a > 0 && b != ""
|
||||||
|
}
|
||||||
|
vm.Set("f", f)
|
||||||
|
const SCRIPT = `
|
||||||
|
assert.sameValue(typeof f, "function");
|
||||||
|
const s = f.toString()
|
||||||
|
assert(s.endsWith("TestWrappedFunc.func1() { [native code] }"), s);
|
||||||
|
assert(f(1, "a"));
|
||||||
|
assert(!f(0, ""));
|
||||||
|
`
|
||||||
|
vm.testScriptWithTestLib(SCRIPT, _undefined, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWrappedFuncErrorPassthrough(t *testing.T) {
|
||||||
|
vm := New()
|
||||||
|
e := errors.New("test")
|
||||||
|
f := func(a int) error {
|
||||||
|
if a > 0 {
|
||||||
|
return e
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var f1 func(a int64) error
|
||||||
|
err := vm.ExportTo(vm.ToValue(f), &f1)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if err := f1(1); err != e {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleAssertConstructor() {
|
||||||
|
vm := New()
|
||||||
|
res, err := vm.RunString(`
|
||||||
|
(class C {
|
||||||
|
constructor(x) {
|
||||||
|
this.x = x;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
`)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
if ctor, ok := AssertConstructor(res); ok {
|
||||||
|
obj, err := ctor(nil, vm.ToValue("Test"))
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
fmt.Print(obj.Get("x"))
|
||||||
|
} else {
|
||||||
|
panic("Not a constructor")
|
||||||
|
}
|
||||||
|
// Output: Test
|
||||||
|
}
|
||||||
|
|
||||||
|
type testAsyncCtx struct {
|
||||||
|
group string
|
||||||
|
refCount int
|
||||||
|
}
|
||||||
|
|
||||||
|
type testAsyncContextTracker struct {
|
||||||
|
ctx *testAsyncCtx
|
||||||
|
logFunc func(...interface{})
|
||||||
|
resumed bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *testAsyncContextTracker) Grab() interface{} {
|
||||||
|
ctx := s.ctx
|
||||||
|
if ctx != nil {
|
||||||
|
s.logFunc("Grab", ctx.group)
|
||||||
|
ctx.refCount++
|
||||||
|
}
|
||||||
|
return ctx
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *testAsyncContextTracker) Resumed(trackingObj interface{}) {
|
||||||
|
s.logFunc("Resumed", trackingObj)
|
||||||
|
if s.resumed {
|
||||||
|
panic("Nested Resumed() calls")
|
||||||
|
}
|
||||||
|
s.ctx = trackingObj.(*testAsyncCtx)
|
||||||
|
s.resumed = true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *testAsyncContextTracker) releaseCtx() {
|
||||||
|
s.ctx.refCount--
|
||||||
|
if s.ctx.refCount < 0 {
|
||||||
|
panic("refCount < 0")
|
||||||
|
}
|
||||||
|
if s.ctx.refCount == 0 {
|
||||||
|
s.logFunc(s.ctx.group, "is finished")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *testAsyncContextTracker) Exited() {
|
||||||
|
s.logFunc("Exited")
|
||||||
|
if s.ctx != nil {
|
||||||
|
s.releaseCtx()
|
||||||
|
s.ctx = nil
|
||||||
|
}
|
||||||
|
s.resumed = false
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAsyncContextTracker(t *testing.T) {
|
||||||
|
r := New()
|
||||||
|
var tracker testAsyncContextTracker
|
||||||
|
tracker.logFunc = t.Log
|
||||||
|
|
||||||
|
group := func(name string, asyncFunc func(FunctionCall) Value) Value {
|
||||||
|
prevCtx := tracker.ctx
|
||||||
|
defer func() {
|
||||||
|
t.Log("Returned", name)
|
||||||
|
tracker.releaseCtx()
|
||||||
|
tracker.ctx = prevCtx
|
||||||
|
}()
|
||||||
|
tracker.ctx = &testAsyncCtx{
|
||||||
|
group: name,
|
||||||
|
refCount: 1,
|
||||||
|
}
|
||||||
|
t.Log("Set", name)
|
||||||
|
return asyncFunc(FunctionCall{})
|
||||||
|
}
|
||||||
|
r.SetAsyncContextTracker(&tracker)
|
||||||
|
r.Set("group", group)
|
||||||
|
r.Set("check", func(expectedGroup, msg string) {
|
||||||
|
var groupName string
|
||||||
|
if tracker.ctx != nil {
|
||||||
|
groupName = tracker.ctx.group
|
||||||
|
}
|
||||||
|
if groupName != expectedGroup {
|
||||||
|
t.Fatalf("Unexpected group (%q), expected %q in %s", groupName, expectedGroup, msg)
|
||||||
|
}
|
||||||
|
t.Log("In", msg)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("", func(t *testing.T) {
|
||||||
|
_, err := r.RunString(`
|
||||||
|
group("1", async () => {
|
||||||
|
check("1", "line A");
|
||||||
|
await 3;
|
||||||
|
check("1", "line B");
|
||||||
|
group("2", async () => {
|
||||||
|
check("2", "line C");
|
||||||
|
await 4;
|
||||||
|
check("2", "line D");
|
||||||
|
})
|
||||||
|
}).then(() => {
|
||||||
|
check("", "line E");
|
||||||
|
})
|
||||||
|
`)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("", func(t *testing.T) {
|
||||||
|
_, err := r.RunString(`
|
||||||
|
group("some", async () => {
|
||||||
|
check("some", "line A");
|
||||||
|
(async () => {
|
||||||
|
check("some", "line B");
|
||||||
|
await 1;
|
||||||
|
check("some", "line C");
|
||||||
|
await 2;
|
||||||
|
check("some", "line D");
|
||||||
|
})();
|
||||||
|
check("some", "line E");
|
||||||
|
});
|
||||||
|
`)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("", func(t *testing.T) {
|
||||||
|
_, err := r.RunString(`
|
||||||
|
group("Main", async () => {
|
||||||
|
check("Main", "0.1");
|
||||||
|
await Promise.all([
|
||||||
|
group("A", async () => {
|
||||||
|
check("A", "1.1");
|
||||||
|
await 1;
|
||||||
|
check("A", "1.2");
|
||||||
|
}),
|
||||||
|
(async () => {
|
||||||
|
check("Main", "3.1");
|
||||||
|
})(),
|
||||||
|
group("B", async () => {
|
||||||
|
check("B", "2.1");
|
||||||
|
await 2;
|
||||||
|
check("B", "2.2");
|
||||||
|
})
|
||||||
|
]);
|
||||||
|
check("Main", "0.2");
|
||||||
|
});
|
||||||
|
`)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
201
goja/map_test.go
Normal file
201
goja/map_test.go
Normal file
@ -0,0 +1,201 @@
|
|||||||
|
package goja
|
||||||
|
|
||||||
|
import (
|
||||||
|
"hash/maphash"
|
||||||
|
"math"
|
||||||
|
"math/big"
|
||||||
|
"strconv"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func testMapHashVal(v1, v2 Value, expected bool, t *testing.T) {
|
||||||
|
var h maphash.Hash
|
||||||
|
actual := v1.hash(&h) == v2.hash(&h)
|
||||||
|
if actual != expected {
|
||||||
|
t.Fatalf("testMapHashVal failed for %v, %v", v1, v2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMapHash(t *testing.T) {
|
||||||
|
testMapHashVal(_NaN, _NaN, true, t)
|
||||||
|
testMapHashVal(valueTrue, valueFalse, false, t)
|
||||||
|
testMapHashVal(valueTrue, valueTrue, true, t)
|
||||||
|
testMapHashVal(intToValue(0), _negativeZero, true, t)
|
||||||
|
testMapHashVal(asciiString("Test"), asciiString("Test"), true, t)
|
||||||
|
testMapHashVal(newStringValue("Тест"), newStringValue("Тест"), true, t)
|
||||||
|
testMapHashVal(floatToValue(1.2345), floatToValue(1.2345), true, t)
|
||||||
|
testMapHashVal(SymIterator, SymToStringTag, false, t)
|
||||||
|
testMapHashVal(SymIterator, SymIterator, true, t)
|
||||||
|
testMapHashVal((*valueBigInt)(big.NewInt(1)), (*valueBigInt)(big.NewInt(-1)), false, t)
|
||||||
|
testMapHashVal((*valueBigInt)(big.NewInt(1)), (*valueBigInt)(big.NewInt(1)), true, t)
|
||||||
|
|
||||||
|
// The following tests introduce indeterministic behaviour
|
||||||
|
//testMapHashVal(asciiString("Test"), asciiString("Test1"), false, t)
|
||||||
|
//testMapHashVal(newStringValue("Тест"), asciiString("Test"), false, t)
|
||||||
|
//testMapHashVal(newStringValue("Тест"), newStringValue("Тест1"), false, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestOrderedMap(t *testing.T) {
|
||||||
|
m := newOrderedMap(&maphash.Hash{})
|
||||||
|
for i := int64(0); i < 50; i++ {
|
||||||
|
m.set(intToValue(i), asciiString(strconv.FormatInt(i, 10)))
|
||||||
|
}
|
||||||
|
if m.size != 50 {
|
||||||
|
t.Fatalf("Unexpected size: %d", m.size)
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := int64(0); i < 50; i++ {
|
||||||
|
expected := asciiString(strconv.FormatInt(i, 10))
|
||||||
|
actual := m.get(intToValue(i))
|
||||||
|
if !expected.SameAs(actual) {
|
||||||
|
t.Fatalf("Wrong value for %d", i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := int64(0); i < 50; i += 2 {
|
||||||
|
if !m.remove(intToValue(i)) {
|
||||||
|
t.Fatalf("remove(%d) return false", i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if m.size != 25 {
|
||||||
|
t.Fatalf("Unexpected size: %d", m.size)
|
||||||
|
}
|
||||||
|
|
||||||
|
iter := m.newIter()
|
||||||
|
count := 0
|
||||||
|
for {
|
||||||
|
entry := iter.next()
|
||||||
|
if entry == nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
m.remove(entry.key)
|
||||||
|
count++
|
||||||
|
}
|
||||||
|
|
||||||
|
if count != 25 {
|
||||||
|
t.Fatalf("Unexpected iter count: %d", count)
|
||||||
|
}
|
||||||
|
|
||||||
|
if m.size != 0 {
|
||||||
|
t.Fatalf("Unexpected size: %d", m.size)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestOrderedMapCollision(t *testing.T) {
|
||||||
|
m := newOrderedMap(&maphash.Hash{})
|
||||||
|
n1 := uint64(123456789)
|
||||||
|
n2 := math.Float64frombits(n1)
|
||||||
|
n1Key := intToValue(int64(n1))
|
||||||
|
n2Key := floatToValue(n2)
|
||||||
|
m.set(n1Key, asciiString("n1"))
|
||||||
|
m.set(n2Key, asciiString("n2"))
|
||||||
|
if m.size == len(m.hashTable) {
|
||||||
|
t.Fatal("Expected a collision but there wasn't one")
|
||||||
|
}
|
||||||
|
if n2Val := m.get(n2Key); !asciiString("n2").SameAs(n2Val) {
|
||||||
|
t.Fatalf("unexpected n2Val: %v", n2Val)
|
||||||
|
}
|
||||||
|
if n1Val := m.get(n1Key); !asciiString("n1").SameAs(n1Val) {
|
||||||
|
t.Fatalf("unexpected nVal: %v", n1Val)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !m.remove(n1Key) {
|
||||||
|
t.Fatal("removing n1Key returned false")
|
||||||
|
}
|
||||||
|
if n2Val := m.get(n2Key); !asciiString("n2").SameAs(n2Val) {
|
||||||
|
t.Fatalf("2: unexpected n2Val: %v", n2Val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestOrderedMapIter(t *testing.T) {
|
||||||
|
m := newOrderedMap(&maphash.Hash{})
|
||||||
|
iter := m.newIter()
|
||||||
|
ent := iter.next()
|
||||||
|
if ent != nil {
|
||||||
|
t.Fatal("entry should be nil")
|
||||||
|
}
|
||||||
|
iter1 := m.newIter()
|
||||||
|
m.set(intToValue(1), valueTrue)
|
||||||
|
ent = iter.next()
|
||||||
|
if ent != nil {
|
||||||
|
t.Fatal("2: entry should be nil")
|
||||||
|
}
|
||||||
|
ent = iter1.next()
|
||||||
|
if ent == nil {
|
||||||
|
t.Fatal("entry is nil")
|
||||||
|
}
|
||||||
|
if !intToValue(1).SameAs(ent.key) {
|
||||||
|
t.Fatal("unexpected key")
|
||||||
|
}
|
||||||
|
if !valueTrue.SameAs(ent.value) {
|
||||||
|
t.Fatal("unexpected value")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestOrderedMapIterVisitAfterReAdd(t *testing.T) {
|
||||||
|
m := newOrderedMap(&maphash.Hash{})
|
||||||
|
one := intToValue(1)
|
||||||
|
two := intToValue(2)
|
||||||
|
|
||||||
|
m.set(one, valueTrue)
|
||||||
|
m.set(two, valueTrue)
|
||||||
|
iter := m.newIter()
|
||||||
|
entry := iter.next()
|
||||||
|
if !one.SameAs(entry.key) {
|
||||||
|
t.Fatalf("1: unexpected key: %v", entry.key)
|
||||||
|
}
|
||||||
|
if !m.remove(one) {
|
||||||
|
t.Fatal("remove returned false")
|
||||||
|
}
|
||||||
|
entry = iter.next()
|
||||||
|
if !two.SameAs(entry.key) {
|
||||||
|
t.Fatalf("2: unexpected key: %v", entry.key)
|
||||||
|
}
|
||||||
|
m.set(one, valueTrue)
|
||||||
|
entry = iter.next()
|
||||||
|
if entry == nil {
|
||||||
|
t.Fatal("entry is nil")
|
||||||
|
}
|
||||||
|
if !one.SameAs(entry.key) {
|
||||||
|
t.Fatalf("3: unexpected key: %v", entry.key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestOrderedMapIterAddAfterClear(t *testing.T) {
|
||||||
|
m := newOrderedMap(&maphash.Hash{})
|
||||||
|
one := intToValue(1)
|
||||||
|
m.set(one, valueTrue)
|
||||||
|
iter := m.newIter()
|
||||||
|
iter.next()
|
||||||
|
m.clear()
|
||||||
|
m.set(one, valueTrue)
|
||||||
|
entry := iter.next()
|
||||||
|
if entry == nil {
|
||||||
|
t.Fatal("entry is nil")
|
||||||
|
}
|
||||||
|
if entry.key != one {
|
||||||
|
t.Fatalf("unexpected key: %v", entry.key)
|
||||||
|
}
|
||||||
|
entry = iter.next()
|
||||||
|
if entry != nil {
|
||||||
|
t.Fatalf("entry is not nil: %v", entry)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestOrderedMapIterDeleteCurrent(t *testing.T) {
|
||||||
|
m := newOrderedMap(&maphash.Hash{})
|
||||||
|
one := intToValue(1)
|
||||||
|
two := intToValue(2)
|
||||||
|
iter := m.newIter()
|
||||||
|
m.set(one, valueTrue)
|
||||||
|
m.set(two, valueTrue)
|
||||||
|
entry := iter.next()
|
||||||
|
if entry.key != one {
|
||||||
|
t.Fatalf("unexpected key: %v", entry.key)
|
||||||
|
}
|
||||||
|
m.remove(one)
|
||||||
|
entry = iter.next()
|
||||||
|
if entry.key != two {
|
||||||
|
t.Fatalf("2: unexpected key: %v", entry.key)
|
||||||
|
}
|
||||||
|
}
|
||||||
420
goja/object_dynamic_test.go
Normal file
420
goja/object_dynamic_test.go
Normal file
@ -0,0 +1,420 @@
|
|||||||
|
package goja
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
type testDynObject struct {
|
||||||
|
r *Runtime
|
||||||
|
m map[string]Value
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *testDynObject) Get(key string) Value {
|
||||||
|
return t.m[key]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *testDynObject) Set(key string, val Value) bool {
|
||||||
|
t.m[key] = val
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *testDynObject) Has(key string) bool {
|
||||||
|
_, exists := t.m[key]
|
||||||
|
return exists
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *testDynObject) Delete(key string) bool {
|
||||||
|
delete(t.m, key)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *testDynObject) Keys() []string {
|
||||||
|
keys := make([]string, 0, len(t.m))
|
||||||
|
for k := range t.m {
|
||||||
|
keys = append(keys, k)
|
||||||
|
}
|
||||||
|
return keys
|
||||||
|
}
|
||||||
|
|
||||||
|
type testDynArray struct {
|
||||||
|
r *Runtime
|
||||||
|
a []Value
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *testDynArray) Len() int {
|
||||||
|
return len(t.a)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *testDynArray) Get(idx int) Value {
|
||||||
|
if idx < 0 {
|
||||||
|
idx += len(t.a)
|
||||||
|
}
|
||||||
|
if idx >= 0 && idx < len(t.a) {
|
||||||
|
return t.a[idx]
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *testDynArray) expand(newLen int) {
|
||||||
|
if newLen > cap(t.a) {
|
||||||
|
a := make([]Value, newLen)
|
||||||
|
copy(a, t.a)
|
||||||
|
t.a = a
|
||||||
|
} else {
|
||||||
|
t.a = t.a[:newLen]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *testDynArray) Set(idx int, val Value) bool {
|
||||||
|
if idx < 0 {
|
||||||
|
idx += len(t.a)
|
||||||
|
}
|
||||||
|
if idx < 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if idx >= len(t.a) {
|
||||||
|
t.expand(idx + 1)
|
||||||
|
}
|
||||||
|
t.a[idx] = val
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *testDynArray) SetLen(i int) bool {
|
||||||
|
if i > len(t.a) {
|
||||||
|
t.expand(i)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if i < 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if i < len(t.a) {
|
||||||
|
tail := t.a[i:len(t.a)]
|
||||||
|
for j := range tail {
|
||||||
|
tail[j] = nil
|
||||||
|
}
|
||||||
|
t.a = t.a[:i]
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDynamicObject(t *testing.T) {
|
||||||
|
vm := New()
|
||||||
|
dynObj := &testDynObject{
|
||||||
|
r: vm,
|
||||||
|
m: make(map[string]Value),
|
||||||
|
}
|
||||||
|
o := vm.NewDynamicObject(dynObj)
|
||||||
|
vm.Set("o", o)
|
||||||
|
vm.testScriptWithTestLibX(`
|
||||||
|
assert(o instanceof Object, "instanceof Object");
|
||||||
|
assert(o === o, "self equality");
|
||||||
|
assert(o !== {}, "non-equality");
|
||||||
|
|
||||||
|
o.test = 42;
|
||||||
|
assert("test" in o, "'test' in o");
|
||||||
|
assert(deepEqual(Object.getOwnPropertyDescriptor(o, "test"), {value: 42, writable: true, enumerable: true, configurable: true}), "prop desc");
|
||||||
|
|
||||||
|
assert.throws(TypeError, function() {
|
||||||
|
"use strict";
|
||||||
|
Object.defineProperty(o, "test1", {value: 0, writable: false, enumerable: false, configurable: true});
|
||||||
|
}, "define prop");
|
||||||
|
|
||||||
|
var keys = [];
|
||||||
|
for (var key in o) {
|
||||||
|
keys.push(key);
|
||||||
|
}
|
||||||
|
assert(compareArray(keys, ["test"]), "for-in");
|
||||||
|
|
||||||
|
assert(delete o.test, "delete");
|
||||||
|
assert(!("test" in o), "'test' in o after delete");
|
||||||
|
|
||||||
|
assert("__proto__" in o, "__proto__ in o");
|
||||||
|
assert.sameValue(o.__proto__, Object.prototype, "__proto__");
|
||||||
|
o.__proto__ = null;
|
||||||
|
assert(!("__proto__" in o), "__proto__ in o after setting to null");
|
||||||
|
`, _undefined, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDynamicObjectCustomProto(t *testing.T) {
|
||||||
|
vm := New()
|
||||||
|
m := make(map[string]Value)
|
||||||
|
dynObj := &testDynObject{
|
||||||
|
r: vm,
|
||||||
|
m: m,
|
||||||
|
}
|
||||||
|
o := vm.NewDynamicObject(dynObj)
|
||||||
|
vm.Set("o", o)
|
||||||
|
vm.testScriptWithTestLib(`
|
||||||
|
var proto = {
|
||||||
|
valueOf: function() {
|
||||||
|
return this.num;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
proto[Symbol.toStringTag] = "GoObject";
|
||||||
|
Object.setPrototypeOf(o, proto);
|
||||||
|
o.num = 41;
|
||||||
|
assert(o instanceof Object, "instanceof");
|
||||||
|
assert.sameValue(o+1, 42);
|
||||||
|
assert.sameValue(o.toString(), "[object GoObject]");
|
||||||
|
`, _undefined, t)
|
||||||
|
|
||||||
|
if v := m["num"]; v.Export() != int64(41) {
|
||||||
|
t.Fatal(v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDynamicArray(t *testing.T) {
|
||||||
|
vm := New()
|
||||||
|
dynObj := &testDynArray{
|
||||||
|
r: vm,
|
||||||
|
}
|
||||||
|
a := vm.NewDynamicArray(dynObj)
|
||||||
|
vm.Set("a", a)
|
||||||
|
vm.testScriptWithTestLibX(`
|
||||||
|
assert(a instanceof Array, "instanceof Array");
|
||||||
|
assert(a instanceof Object, "instanceof Object");
|
||||||
|
assert(a === a, "self equality");
|
||||||
|
assert(a !== [], "non-equality");
|
||||||
|
assert(Array.isArray(a), "isArray()");
|
||||||
|
assert("length" in a, "length in a");
|
||||||
|
assert.sameValue(a.length, 0, "len == 0");
|
||||||
|
assert.sameValue(a[0], undefined, "a[0] (1)");
|
||||||
|
|
||||||
|
a[0] = 0;
|
||||||
|
assert.sameValue(a[0], 0, "a[0] (2)");
|
||||||
|
assert.sameValue(a.length, 1, "length");
|
||||||
|
assert(deepEqual(Object.getOwnPropertyDescriptor(a, 0), {value: 0, writable: true, enumerable: true, configurable: true}), "prop desc");
|
||||||
|
assert(deepEqual(Object.getOwnPropertyDescriptor(a, "length"), {value: 1, writable: true, enumerable: false, configurable: false}), "length prop desc");
|
||||||
|
|
||||||
|
assert("__proto__" in a, "__proto__ in a");
|
||||||
|
assert.sameValue(a.__proto__, Array.prototype, "__proto__");
|
||||||
|
|
||||||
|
assert(compareArray(Object.keys(a), ["0"]), "Object.keys()");
|
||||||
|
assert(compareArray(Reflect.ownKeys(a), ["0", "length"]), "Reflect.ownKeys()");
|
||||||
|
|
||||||
|
a.length = 2;
|
||||||
|
assert.sameValue(a.length, 2, "length after grow");
|
||||||
|
assert.sameValue(a[1], undefined, "a[1]");
|
||||||
|
|
||||||
|
a[1] = 1;
|
||||||
|
assert.sameValue(a[1], 1, "a[1] after set");
|
||||||
|
a.length = 1;
|
||||||
|
assert.sameValue(a.length, 1, "length after shrink");
|
||||||
|
assert.sameValue(a[1], undefined, "a[1] after shrink");
|
||||||
|
a.length = 2;
|
||||||
|
assert.sameValue(a.length, 2, "length after shrink and grow");
|
||||||
|
assert.sameValue(a[1], undefined, "a[1] after grow");
|
||||||
|
|
||||||
|
a[0] = 3; a[1] = 1; a[2] = 2;
|
||||||
|
assert.sameValue(a.length, 3);
|
||||||
|
var keys = [];
|
||||||
|
for (var key in a) {
|
||||||
|
keys.push(key);
|
||||||
|
}
|
||||||
|
assert(compareArray(keys, ["0","1","2"]), "for-in");
|
||||||
|
|
||||||
|
var vals = [];
|
||||||
|
for (var val of a) {
|
||||||
|
vals.push(val);
|
||||||
|
}
|
||||||
|
assert(compareArray(vals, [3,1,2]), "for-of");
|
||||||
|
|
||||||
|
a.sort();
|
||||||
|
assert(compareArray(a, [1,2,3]), "sort: "+a);
|
||||||
|
|
||||||
|
assert.sameValue(a[-1], 3);
|
||||||
|
assert.sameValue(a[-4], undefined);
|
||||||
|
|
||||||
|
assert.throws(TypeError, function() {
|
||||||
|
"use strict";
|
||||||
|
delete a.length;
|
||||||
|
}, "delete length");
|
||||||
|
|
||||||
|
assert.throws(TypeError, function() {
|
||||||
|
"use strict";
|
||||||
|
a.test = true;
|
||||||
|
}, "set string prop");
|
||||||
|
|
||||||
|
assert.throws(TypeError, function() {
|
||||||
|
"use strict";
|
||||||
|
Object.defineProperty(a, 0, {value: 0, writable: false, enumerable: false, configurable: true});
|
||||||
|
}, "define prop");
|
||||||
|
|
||||||
|
`, _undefined, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
type testSharedDynObject struct {
|
||||||
|
sync.RWMutex
|
||||||
|
m map[string]Value
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *testSharedDynObject) Get(key string) Value {
|
||||||
|
t.RLock()
|
||||||
|
val := t.m[key]
|
||||||
|
t.RUnlock()
|
||||||
|
return val
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *testSharedDynObject) Set(key string, val Value) bool {
|
||||||
|
t.Lock()
|
||||||
|
t.m[key] = val
|
||||||
|
t.Unlock()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *testSharedDynObject) Has(key string) bool {
|
||||||
|
t.RLock()
|
||||||
|
_, exists := t.m[key]
|
||||||
|
t.RUnlock()
|
||||||
|
return exists
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *testSharedDynObject) Delete(key string) bool {
|
||||||
|
t.Lock()
|
||||||
|
delete(t.m, key)
|
||||||
|
t.Unlock()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *testSharedDynObject) Keys() []string {
|
||||||
|
t.RLock()
|
||||||
|
keys := make([]string, 0, len(t.m))
|
||||||
|
for k := range t.m {
|
||||||
|
keys = append(keys, k)
|
||||||
|
}
|
||||||
|
t.RUnlock()
|
||||||
|
return keys
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSharedDynamicObject(t *testing.T) {
|
||||||
|
dynObj := &testSharedDynObject{m: make(map[string]Value, 10000)}
|
||||||
|
o := NewSharedDynamicObject(dynObj)
|
||||||
|
ch := make(chan error, 1)
|
||||||
|
go func() {
|
||||||
|
vm := New()
|
||||||
|
vm.Set("o", o)
|
||||||
|
_, err := vm.RunString(`
|
||||||
|
for (let i = 0; i < 10000; i++) {
|
||||||
|
o[i] = i;
|
||||||
|
}
|
||||||
|
`)
|
||||||
|
ch <- err
|
||||||
|
}()
|
||||||
|
vm := New()
|
||||||
|
vm.Set("o", o)
|
||||||
|
_, err := vm.RunString(`
|
||||||
|
for (let i = 0; i < 10000; i++) {
|
||||||
|
o[i] = i+1;
|
||||||
|
}
|
||||||
|
`)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = <-ch
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type testSharedDynArray struct {
|
||||||
|
sync.RWMutex
|
||||||
|
a []Value
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *testSharedDynArray) Len() int {
|
||||||
|
t.RLock()
|
||||||
|
l := len(t.a)
|
||||||
|
t.RUnlock()
|
||||||
|
return l
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *testSharedDynArray) Get(idx int) Value {
|
||||||
|
t.RLock()
|
||||||
|
defer t.RUnlock()
|
||||||
|
if idx < 0 {
|
||||||
|
idx += len(t.a)
|
||||||
|
}
|
||||||
|
if idx >= 0 && idx < len(t.a) {
|
||||||
|
return t.a[idx]
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *testSharedDynArray) expand(newLen int) {
|
||||||
|
if newLen > cap(t.a) {
|
||||||
|
a := make([]Value, newLen)
|
||||||
|
copy(a, t.a)
|
||||||
|
t.a = a
|
||||||
|
} else {
|
||||||
|
t.a = t.a[:newLen]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *testSharedDynArray) Set(idx int, val Value) bool {
|
||||||
|
t.Lock()
|
||||||
|
defer t.Unlock()
|
||||||
|
if idx < 0 {
|
||||||
|
idx += len(t.a)
|
||||||
|
}
|
||||||
|
if idx < 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if idx >= len(t.a) {
|
||||||
|
t.expand(idx + 1)
|
||||||
|
}
|
||||||
|
t.a[idx] = val
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *testSharedDynArray) SetLen(i int) bool {
|
||||||
|
t.Lock()
|
||||||
|
defer t.Unlock()
|
||||||
|
if i > len(t.a) {
|
||||||
|
t.expand(i)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if i < 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if i < len(t.a) {
|
||||||
|
tail := t.a[i:len(t.a)]
|
||||||
|
for j := range tail {
|
||||||
|
tail[j] = nil
|
||||||
|
}
|
||||||
|
t.a = t.a[:i]
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSharedDynamicArray(t *testing.T) {
|
||||||
|
dynObj := &testSharedDynArray{a: make([]Value, 10000)}
|
||||||
|
o := NewSharedDynamicArray(dynObj)
|
||||||
|
ch := make(chan error, 1)
|
||||||
|
go func() {
|
||||||
|
vm := New()
|
||||||
|
vm.Set("o", o)
|
||||||
|
_, err := vm.RunString(`
|
||||||
|
for (let i = 0; i < 10000; i++) {
|
||||||
|
o[i] = i;
|
||||||
|
}
|
||||||
|
`)
|
||||||
|
ch <- err
|
||||||
|
}()
|
||||||
|
vm := New()
|
||||||
|
vm.Set("o", o)
|
||||||
|
_, err := vm.RunString(`
|
||||||
|
for (let i = 0; i < 10000; i++) {
|
||||||
|
o[i] = i+1;
|
||||||
|
}
|
||||||
|
`)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = <-ch
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
304
goja/object_goarray_reflect_test.go
Normal file
304
goja/object_goarray_reflect_test.go
Normal file
@ -0,0 +1,304 @@
|
|||||||
|
package goja
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGoReflectArray(t *testing.T) {
|
||||||
|
vm := New()
|
||||||
|
vm.Set("a", [...]int{1, 2, 3})
|
||||||
|
_, err := vm.RunString(`
|
||||||
|
if (!Array.isArray(a)) {
|
||||||
|
throw new Error("isArray() returned false");
|
||||||
|
}
|
||||||
|
if (a[0] !== 1 || a[1] !== 2 || a[2] !== 3) {
|
||||||
|
throw new Error("Array contents is incorrect");
|
||||||
|
}
|
||||||
|
if (!a.hasOwnProperty("length")) {
|
||||||
|
throw new Error("hasOwnProperty() returned false");
|
||||||
|
}
|
||||||
|
let desc = Object.getOwnPropertyDescriptor(a, "length");
|
||||||
|
if (desc.value !== 3 || desc.writable || desc.enumerable || desc.configurable) {
|
||||||
|
throw new Error("incorrect property descriptor: " + JSON.stringify(desc));
|
||||||
|
}
|
||||||
|
`)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGoReflectArraySort(t *testing.T) {
|
||||||
|
vm := New()
|
||||||
|
vm.Set("a", [...]int{3, 1, 2})
|
||||||
|
v, err := vm.RunString(`
|
||||||
|
a.sort();
|
||||||
|
if (a[0] !== 1 || a[1] !== 2 || a[2] !== 3) {
|
||||||
|
throw new Error(a.toString());
|
||||||
|
}
|
||||||
|
a;
|
||||||
|
`)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
res := v.Export()
|
||||||
|
if a, ok := res.([3]int); ok {
|
||||||
|
if a[0] != 1 || a[1] != 2 || a[2] != 3 {
|
||||||
|
t.Fatal(a)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
t.Fatalf("Wrong type: %T", res)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGoReflectArrayCopyOnChange(t *testing.T) {
|
||||||
|
vm := New()
|
||||||
|
|
||||||
|
v, err := vm.RunString(`
|
||||||
|
a => {
|
||||||
|
let tmp = a[0];
|
||||||
|
if (tmp !== a[0]) {
|
||||||
|
throw new Error("tmp !== a[0]");
|
||||||
|
}
|
||||||
|
|
||||||
|
a[0] = a[1];
|
||||||
|
if (tmp === a[0]) {
|
||||||
|
throw new Error("tmp === a[0]");
|
||||||
|
}
|
||||||
|
if (tmp.Test !== "1") {
|
||||||
|
throw new Error("tmp.Test: " + tmp.Test + " (" + typeof tmp.Test + ")");
|
||||||
|
}
|
||||||
|
if (a[0].Test !== "2") {
|
||||||
|
throw new Error("a[0].Test: " + a[0].Test);
|
||||||
|
}
|
||||||
|
|
||||||
|
a[0].Test = "3";
|
||||||
|
if (a[0].Test !== "3") {
|
||||||
|
throw new Error("a[0].Test (1): " + a[0].Test);
|
||||||
|
}
|
||||||
|
|
||||||
|
tmp = a[0];
|
||||||
|
tmp.Test = "4";
|
||||||
|
if (a[0].Test !== "4") {
|
||||||
|
throw new Error("a[0].Test (2): " + a[0].Test);
|
||||||
|
}
|
||||||
|
|
||||||
|
delete a[0];
|
||||||
|
if (a[0] && a[0].Test !== "") {
|
||||||
|
throw new Error("a[0].Test (3): " + a[0].Test);
|
||||||
|
}
|
||||||
|
if (tmp.Test !== "4") {
|
||||||
|
throw new Error("tmp.Test (1): " + tmp.Test);
|
||||||
|
}
|
||||||
|
|
||||||
|
a[1] = tmp;
|
||||||
|
if (a[1].Test !== "4") {
|
||||||
|
throw new Error("a[1].Test: " + a[1].Test);
|
||||||
|
}
|
||||||
|
|
||||||
|
// grow
|
||||||
|
tmp = a[1];
|
||||||
|
a.push(null);
|
||||||
|
if (a.length !== 3) {
|
||||||
|
throw new Error("a.length after push: " + a.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
tmp.Test = "5";
|
||||||
|
if (a[1].Test !== "5") {
|
||||||
|
throw new Error("a[1].Test (1): " + a[1].Test);
|
||||||
|
}
|
||||||
|
|
||||||
|
// shrink
|
||||||
|
a.length = 1;
|
||||||
|
if (a.length !== 1) {
|
||||||
|
throw new Error("a.length after shrink: " + a.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tmp.Test !== "5") {
|
||||||
|
throw new Error("tmp.Test (shrink): " + tmp.Test);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn, ok := AssertFunction(v)
|
||||||
|
if !ok {
|
||||||
|
t.Fatal("Not a function")
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run("[]struct", func(t *testing.T) {
|
||||||
|
a := []struct {
|
||||||
|
Test string
|
||||||
|
}{{"1"}, {"2"}}
|
||||||
|
_, err := fn(nil, vm.ToValue(a))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if a[0].Test != "" {
|
||||||
|
t.Fatalf("a[0]: %#v", a[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
if a[1].Test != "4" {
|
||||||
|
t.Fatalf("a0[1]: %#v", a[1])
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// The copy-on-change mechanism doesn't apply to the types below because the contained values are references.
|
||||||
|
// These tests are here for completeness and to prove that the behaviour is consistent.
|
||||||
|
|
||||||
|
t.Run("[]I", func(t *testing.T) {
|
||||||
|
type I interface {
|
||||||
|
Get() string
|
||||||
|
}
|
||||||
|
|
||||||
|
a := []I{&testGoReflectMethod_O{Test: "1"}, &testGoReflectMethod_O{Test: "2"}}
|
||||||
|
|
||||||
|
_, err = fn(nil, vm.ToValue(a))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("[]interface{}", func(t *testing.T) {
|
||||||
|
a := []interface{}{&testGoReflectMethod_O{Test: "1"}, &testGoReflectMethod_O{Test: "2"}}
|
||||||
|
|
||||||
|
_, err = fn(nil, vm.ToValue(a))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCopyOnChangeReflectSlice(t *testing.T) {
|
||||||
|
vm := New()
|
||||||
|
v, err := vm.RunString(`
|
||||||
|
s => {
|
||||||
|
s.A.push(1);
|
||||||
|
if (s.A.length !== 1) {
|
||||||
|
throw new Error("s.A.length: " + s.A.length);
|
||||||
|
}
|
||||||
|
if (s.A[0] !== 1) {
|
||||||
|
throw new Error("s.A[0]: " + s.A[0]);
|
||||||
|
}
|
||||||
|
let tmp = s.A;
|
||||||
|
if (tmp !== s.A) {
|
||||||
|
throw new Error("tmp !== s.A");
|
||||||
|
}
|
||||||
|
s.A = [2];
|
||||||
|
if (tmp === s.A) {
|
||||||
|
throw new Error("tmp === s.A");
|
||||||
|
}
|
||||||
|
if (tmp[0] !== 1) {
|
||||||
|
throw new Error("tmp[0]: " + tmp[0]);
|
||||||
|
}
|
||||||
|
if (s.A[0] !== 2) {
|
||||||
|
throw new Error("s.A[0] (1): " + s.A[0]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
fn, ok := AssertFunction(v)
|
||||||
|
if !ok {
|
||||||
|
t.Fatal("Not a function")
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run("[]int", func(t *testing.T) {
|
||||||
|
type S struct {
|
||||||
|
A []int
|
||||||
|
}
|
||||||
|
var s S
|
||||||
|
_, err := fn(nil, vm.ToValue(&s))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if len(s.A) != 1 {
|
||||||
|
t.Fatal(s)
|
||||||
|
}
|
||||||
|
if s.A[0] != 2 {
|
||||||
|
t.Fatal(s.A)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("[]interface{}", func(t *testing.T) {
|
||||||
|
type S struct {
|
||||||
|
A []interface{}
|
||||||
|
}
|
||||||
|
var s S
|
||||||
|
_, err := fn(nil, vm.ToValue(&s))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if len(s.A) != 1 {
|
||||||
|
t.Fatal(s)
|
||||||
|
}
|
||||||
|
if s.A[0] != int64(2) {
|
||||||
|
t.Fatal(s.A)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCopyOnChangeSort(t *testing.T) {
|
||||||
|
a := []struct {
|
||||||
|
Test string
|
||||||
|
}{{"2"}, {"1"}}
|
||||||
|
|
||||||
|
vm := New()
|
||||||
|
vm.Set("a", &a)
|
||||||
|
|
||||||
|
_, err := vm.RunString(`
|
||||||
|
let a0 = a[0];
|
||||||
|
let a1 = a[1];
|
||||||
|
a.sort((a, b) => a.Test.localeCompare(b.Test));
|
||||||
|
if (a[0].Test !== "1") {
|
||||||
|
throw new Error("a[0]: " + a[0]);
|
||||||
|
}
|
||||||
|
if (a[1].Test !== "2") {
|
||||||
|
throw new Error("a[1]: " + a[1]);
|
||||||
|
}
|
||||||
|
if (a0 !== a[1]) {
|
||||||
|
throw new Error("a0 !== a[1]");
|
||||||
|
}
|
||||||
|
if (a1 !== a[0]) {
|
||||||
|
throw new Error("a1 !== a[0]");
|
||||||
|
}
|
||||||
|
`)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if a[0].Test != "1" || a[1].Test != "2" {
|
||||||
|
t.Fatal(a)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type testStringerArray [8]byte
|
||||||
|
|
||||||
|
func (a testStringerArray) String() string {
|
||||||
|
return "X"
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestReflectArrayToString(t *testing.T) {
|
||||||
|
vm := New()
|
||||||
|
var a testStringerArray
|
||||||
|
vm.Set("a", &a)
|
||||||
|
res, err := vm.RunString("`${a}`")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if exp := res.Export(); exp != "X" {
|
||||||
|
t.Fatal(exp)
|
||||||
|
}
|
||||||
|
|
||||||
|
var a1 [2]byte
|
||||||
|
vm.Set("a", &a1)
|
||||||
|
res, err = vm.RunString("`${a}`")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if exp := res.Export(); exp != "0,0" {
|
||||||
|
t.Fatal(exp)
|
||||||
|
}
|
||||||
|
}
|
||||||
350
goja/object_gomap_reflect_test.go
Normal file
350
goja/object_gomap_reflect_test.go
Normal file
@ -0,0 +1,350 @@
|
|||||||
|
package goja
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGoMapReflectGetSet(t *testing.T) {
|
||||||
|
const SCRIPT = `
|
||||||
|
m.c = m.a + m.b;
|
||||||
|
`
|
||||||
|
|
||||||
|
vm := New()
|
||||||
|
m := map[string]string{
|
||||||
|
"a": "4",
|
||||||
|
"b": "2",
|
||||||
|
}
|
||||||
|
vm.Set("m", m)
|
||||||
|
|
||||||
|
_, err := vm.RunString(SCRIPT)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if c := m["c"]; c != "42" {
|
||||||
|
t.Fatalf("Unexpected value: '%s'", c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGoMapReflectIntKey(t *testing.T) {
|
||||||
|
const SCRIPT = `
|
||||||
|
m[2] = m[0] + m[1];
|
||||||
|
`
|
||||||
|
|
||||||
|
vm := New()
|
||||||
|
m := map[int]int{
|
||||||
|
0: 40,
|
||||||
|
1: 2,
|
||||||
|
}
|
||||||
|
vm.Set("m", m)
|
||||||
|
|
||||||
|
_, err := vm.RunString(SCRIPT)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if c := m[2]; c != 42 {
|
||||||
|
t.Fatalf("Unexpected value: '%d'", c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGoMapReflectDelete(t *testing.T) {
|
||||||
|
const SCRIPT = `
|
||||||
|
delete m.a;
|
||||||
|
`
|
||||||
|
|
||||||
|
vm := New()
|
||||||
|
m := map[string]string{
|
||||||
|
"a": "4",
|
||||||
|
"b": "2",
|
||||||
|
}
|
||||||
|
vm.Set("m", m)
|
||||||
|
|
||||||
|
_, err := vm.RunString(SCRIPT)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, exists := m["a"]; exists {
|
||||||
|
t.Fatal("a still exists")
|
||||||
|
}
|
||||||
|
|
||||||
|
if b := m["b"]; b != "2" {
|
||||||
|
t.Fatalf("Unexpected b: '%s'", b)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGoMapReflectJSON(t *testing.T) {
|
||||||
|
const SCRIPT = `
|
||||||
|
function f(m) {
|
||||||
|
return JSON.stringify(m);
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
vm := New()
|
||||||
|
m := map[string]string{
|
||||||
|
"t": "42",
|
||||||
|
}
|
||||||
|
_, err := vm.RunString(SCRIPT)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
f := vm.Get("f")
|
||||||
|
if call, ok := AssertFunction(f); ok {
|
||||||
|
v, err := call(nil, ([]Value{vm.ToValue(m)})...)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if !v.StrictEquals(asciiString(`{"t":"42"}`)) {
|
||||||
|
t.Fatalf("Unexpected value: %v", v)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
t.Fatalf("Not a function: %v", f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGoMapReflectProto(t *testing.T) {
|
||||||
|
const SCRIPT = `
|
||||||
|
m.hasOwnProperty("t");
|
||||||
|
`
|
||||||
|
|
||||||
|
vm := New()
|
||||||
|
m := map[string]string{
|
||||||
|
"t": "42",
|
||||||
|
}
|
||||||
|
vm.Set("m", m)
|
||||||
|
v, err := vm.RunString(SCRIPT)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if !v.StrictEquals(valueTrue) {
|
||||||
|
t.Fatalf("Expected true, got %v", v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type gomapReflect_noMethods map[string]interface{}
|
||||||
|
type gomapReflect_withMethods map[string]interface{}
|
||||||
|
|
||||||
|
func (m gomapReflect_withMethods) Method() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGoMapReflectNoMethods(t *testing.T) {
|
||||||
|
const SCRIPT = `
|
||||||
|
typeof m === "object" && m.hasOwnProperty("t") && m.t === 42;
|
||||||
|
`
|
||||||
|
|
||||||
|
vm := New()
|
||||||
|
m := make(gomapReflect_noMethods)
|
||||||
|
m["t"] = 42
|
||||||
|
vm.Set("m", m)
|
||||||
|
v, err := vm.RunString(SCRIPT)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if !v.StrictEquals(valueTrue) {
|
||||||
|
t.Fatalf("Expected true, got %v", v)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGoMapReflectWithMethods(t *testing.T) {
|
||||||
|
const SCRIPT = `
|
||||||
|
typeof m === "object" && !m.hasOwnProperty("t") && m.hasOwnProperty("Method") && m.Method();
|
||||||
|
`
|
||||||
|
|
||||||
|
vm := New()
|
||||||
|
m := make(gomapReflect_withMethods)
|
||||||
|
m["t"] = 42
|
||||||
|
vm.Set("m", m)
|
||||||
|
v, err := vm.RunString(SCRIPT)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if !v.StrictEquals(valueTrue) {
|
||||||
|
t.Fatalf("Expected true, got %v", v)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGoMapReflectWithProto(t *testing.T) {
|
||||||
|
vm := New()
|
||||||
|
m := map[string]string{
|
||||||
|
"t": "42",
|
||||||
|
}
|
||||||
|
vm.Set("m", m)
|
||||||
|
vm.testScriptWithTestLib(`
|
||||||
|
(function() {
|
||||||
|
'use strict';
|
||||||
|
var proto = {};
|
||||||
|
var getterAllowed = false;
|
||||||
|
var setterAllowed = false;
|
||||||
|
var tHolder = "proto t";
|
||||||
|
Object.defineProperty(proto, "t", {
|
||||||
|
get: function() {
|
||||||
|
if (!getterAllowed) throw new Error("getter is called");
|
||||||
|
return tHolder;
|
||||||
|
},
|
||||||
|
set: function(v) {
|
||||||
|
if (!setterAllowed) throw new Error("setter is called");
|
||||||
|
tHolder = v;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
var t1Holder;
|
||||||
|
Object.defineProperty(proto, "t1", {
|
||||||
|
get: function() {
|
||||||
|
return t1Holder;
|
||||||
|
},
|
||||||
|
set: function(v) {
|
||||||
|
t1Holder = v;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
Object.setPrototypeOf(m, proto);
|
||||||
|
assert.sameValue(m.t, "42");
|
||||||
|
m.t = 43;
|
||||||
|
assert.sameValue(m.t, "43");
|
||||||
|
t1Holder = "test";
|
||||||
|
assert.sameValue(m.t1, "test");
|
||||||
|
m.t1 = "test1";
|
||||||
|
assert.sameValue(m.t1, "test1");
|
||||||
|
delete m.t;
|
||||||
|
getterAllowed = true;
|
||||||
|
assert.sameValue(m.t, "proto t", "after delete");
|
||||||
|
setterAllowed = true;
|
||||||
|
m.t = true;
|
||||||
|
assert.sameValue(m.t, true, "m.t === true");
|
||||||
|
assert.sameValue(tHolder, true, "tHolder === true");
|
||||||
|
Object.preventExtensions(m);
|
||||||
|
assert.throws(TypeError, function() {
|
||||||
|
m.t2 = 1;
|
||||||
|
});
|
||||||
|
m.t1 = "test2";
|
||||||
|
assert.sameValue(m.t1, "test2");
|
||||||
|
})();
|
||||||
|
`, _undefined, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGoMapReflectProtoProp(t *testing.T) {
|
||||||
|
const SCRIPT = `
|
||||||
|
(function() {
|
||||||
|
"use strict";
|
||||||
|
var proto = {};
|
||||||
|
Object.defineProperty(proto, "ro", {value: 42});
|
||||||
|
Object.setPrototypeOf(m, proto);
|
||||||
|
assert.throws(TypeError, function() {
|
||||||
|
m.ro = 43;
|
||||||
|
});
|
||||||
|
Object.defineProperty(m, "ro", {value: 43});
|
||||||
|
assert.sameValue(m.ro, "43");
|
||||||
|
})();
|
||||||
|
`
|
||||||
|
|
||||||
|
r := New()
|
||||||
|
r.Set("m", map[string]string{})
|
||||||
|
r.testScriptWithTestLib(SCRIPT, _undefined, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGoMapReflectUnicode(t *testing.T) {
|
||||||
|
const SCRIPT = `
|
||||||
|
Object.setPrototypeOf(m, s);
|
||||||
|
if (m.Тест !== "passed") {
|
||||||
|
throw new Error("m.Тест: " + m.Тест);
|
||||||
|
}
|
||||||
|
m["é"];
|
||||||
|
`
|
||||||
|
type S struct {
|
||||||
|
Тест string
|
||||||
|
}
|
||||||
|
vm := New()
|
||||||
|
m := map[string]int{
|
||||||
|
"é": 42,
|
||||||
|
}
|
||||||
|
s := S{
|
||||||
|
Тест: "passed",
|
||||||
|
}
|
||||||
|
vm.Set("m", m)
|
||||||
|
vm.Set("s", &s)
|
||||||
|
res, err := vm.RunString(SCRIPT)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if res == nil || !res.StrictEquals(valueInt(42)) {
|
||||||
|
t.Fatalf("Unexpected value: %v", res)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGoMapReflectStruct(t *testing.T) {
|
||||||
|
type S struct {
|
||||||
|
Test int
|
||||||
|
}
|
||||||
|
|
||||||
|
m := map[string]S{
|
||||||
|
"1": {Test: 1},
|
||||||
|
}
|
||||||
|
|
||||||
|
vm := New()
|
||||||
|
vm.Set("m", m)
|
||||||
|
res, err := vm.RunString("m[1].Test = 2; m[1].Test")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if res.Export() != int64(1) {
|
||||||
|
t.Fatal(res)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGoMapReflectElt(t *testing.T) {
|
||||||
|
type mapping map[string]interface{}
|
||||||
|
|
||||||
|
const SCRIPT = `a.s() && a.t === null && a.t1 === undefined;`
|
||||||
|
|
||||||
|
r := New()
|
||||||
|
|
||||||
|
r.Set("a", mapping{
|
||||||
|
"s": func() bool { return true },
|
||||||
|
"t": nil,
|
||||||
|
})
|
||||||
|
|
||||||
|
r.testScript(SCRIPT, valueTrue, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGoMapReflectKeyToString(t *testing.T) {
|
||||||
|
vm := New()
|
||||||
|
|
||||||
|
test := func(v any, t *testing.T) {
|
||||||
|
o1 := vm.ToValue(v).ToObject(vm)
|
||||||
|
keys := o1.Keys()
|
||||||
|
sort.Strings(keys)
|
||||||
|
if len(keys) != 2 || keys[0] != "1" || keys[1] != "2" {
|
||||||
|
t.Fatal(keys)
|
||||||
|
}
|
||||||
|
|
||||||
|
keys1 := o1.self.stringKeys(true, nil)
|
||||||
|
sort.Slice(keys1, func(a, b int) bool {
|
||||||
|
return strings.Compare(keys1[a].String(), keys1[b].String()) < 0
|
||||||
|
})
|
||||||
|
if len(keys1) != 2 || keys1[0] != asciiString("1") || keys1[1] != asciiString("2") {
|
||||||
|
t.Fatal(keys1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run("int", func(t *testing.T) {
|
||||||
|
m1 := map[int]any{
|
||||||
|
1: 2,
|
||||||
|
2: 3,
|
||||||
|
}
|
||||||
|
test(m1, t)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("CustomString", func(t *testing.T) {
|
||||||
|
type CustomString string
|
||||||
|
m2 := map[CustomString]any{
|
||||||
|
"1": 2,
|
||||||
|
"2": 3,
|
||||||
|
}
|
||||||
|
test(m2, t)
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
328
goja/object_gomap_test.go
Normal file
328
goja/object_gomap_test.go
Normal file
@ -0,0 +1,328 @@
|
|||||||
|
package goja
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
func TestGomapProp(t *testing.T) {
|
||||||
|
const SCRIPT = `
|
||||||
|
o.a + o.b;
|
||||||
|
`
|
||||||
|
r := New()
|
||||||
|
r.Set("o", map[string]interface{}{
|
||||||
|
"a": 40,
|
||||||
|
"b": 2,
|
||||||
|
})
|
||||||
|
v, err := r.RunString(SCRIPT)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if i := v.ToInteger(); i != 42 {
|
||||||
|
t.Fatalf("Expected 42, got: %d", i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGomapEnumerate(t *testing.T) {
|
||||||
|
const SCRIPT = `
|
||||||
|
var hasX = false;
|
||||||
|
var hasY = false;
|
||||||
|
for (var key in o) {
|
||||||
|
switch (key) {
|
||||||
|
case "x":
|
||||||
|
if (hasX) {
|
||||||
|
throw "Already have x";
|
||||||
|
}
|
||||||
|
hasX = true;
|
||||||
|
break;
|
||||||
|
case "y":
|
||||||
|
if (hasY) {
|
||||||
|
throw "Already have y";
|
||||||
|
}
|
||||||
|
hasY = true;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw "Unexpected property: " + key;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
hasX && hasY;
|
||||||
|
`
|
||||||
|
r := New()
|
||||||
|
r.Set("o", map[string]interface{}{
|
||||||
|
"x": 40,
|
||||||
|
"y": 2,
|
||||||
|
})
|
||||||
|
v, err := r.RunString(SCRIPT)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !v.StrictEquals(valueTrue) {
|
||||||
|
t.Fatalf("Expected true, got %v", v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGomapDeleteWhileEnumerate(t *testing.T) {
|
||||||
|
const SCRIPT = `
|
||||||
|
var hasX = false;
|
||||||
|
var hasY = false;
|
||||||
|
for (var key in o) {
|
||||||
|
switch (key) {
|
||||||
|
case "x":
|
||||||
|
if (hasX) {
|
||||||
|
throw "Already have x";
|
||||||
|
}
|
||||||
|
hasX = true;
|
||||||
|
delete o.y;
|
||||||
|
break;
|
||||||
|
case "y":
|
||||||
|
if (hasY) {
|
||||||
|
throw "Already have y";
|
||||||
|
}
|
||||||
|
hasY = true;
|
||||||
|
delete o.x;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw "Unexpected property: " + key;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
hasX && !hasY || hasY && !hasX;
|
||||||
|
`
|
||||||
|
r := New()
|
||||||
|
r.Set("o", map[string]interface{}{
|
||||||
|
"x": 40,
|
||||||
|
"y": 2,
|
||||||
|
})
|
||||||
|
v, err := r.RunString(SCRIPT)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !v.StrictEquals(valueTrue) {
|
||||||
|
t.Fatalf("Expected true, got %v", v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGomapInstanceOf(t *testing.T) {
|
||||||
|
const SCRIPT = `
|
||||||
|
(o instanceof Object) && !(o instanceof Error);
|
||||||
|
`
|
||||||
|
r := New()
|
||||||
|
r.Set("o", map[string]interface{}{})
|
||||||
|
v, err := r.RunString(SCRIPT)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !v.StrictEquals(valueTrue) {
|
||||||
|
t.Fatalf("Expected true, got %v", v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGomapTypeOf(t *testing.T) {
|
||||||
|
const SCRIPT = `
|
||||||
|
typeof o;
|
||||||
|
`
|
||||||
|
r := New()
|
||||||
|
r.Set("o", map[string]interface{}{})
|
||||||
|
v, err := r.RunString(SCRIPT)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !v.StrictEquals(asciiString("object")) {
|
||||||
|
t.Fatalf("Expected object, got %v", v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGomapProto(t *testing.T) {
|
||||||
|
const SCRIPT = `
|
||||||
|
o.hasOwnProperty("test");
|
||||||
|
`
|
||||||
|
r := New()
|
||||||
|
r.Set("o", map[string]interface{}{
|
||||||
|
"test": 42,
|
||||||
|
})
|
||||||
|
v, err := r.RunString(SCRIPT)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !v.StrictEquals(valueTrue) {
|
||||||
|
t.Fatalf("Expected true, got %v", v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGoMapExtensibility(t *testing.T) {
|
||||||
|
const SCRIPT = `
|
||||||
|
"use strict";
|
||||||
|
o.test = 42;
|
||||||
|
Object.preventExtensions(o);
|
||||||
|
o.test = 43;
|
||||||
|
try {
|
||||||
|
o.test1 = 42;
|
||||||
|
} catch (e) {
|
||||||
|
if (!(e instanceof TypeError)) {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
o.test === 43 && o.test1 === undefined;
|
||||||
|
`
|
||||||
|
|
||||||
|
r := New()
|
||||||
|
r.Set("o", map[string]interface{}{})
|
||||||
|
v, err := r.RunString(SCRIPT)
|
||||||
|
if err != nil {
|
||||||
|
if ex, ok := err.(*Exception); ok {
|
||||||
|
t.Fatal(ex.String())
|
||||||
|
} else {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !v.StrictEquals(valueTrue) {
|
||||||
|
t.Fatalf("Expected true, got %v", v)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGoMapWithProto(t *testing.T) {
|
||||||
|
vm := New()
|
||||||
|
m := map[string]interface{}{
|
||||||
|
"t": "42",
|
||||||
|
}
|
||||||
|
vm.Set("m", m)
|
||||||
|
vm.testScriptWithTestLib(`
|
||||||
|
(function() {
|
||||||
|
'use strict';
|
||||||
|
var proto = {};
|
||||||
|
var getterAllowed = false;
|
||||||
|
var setterAllowed = false;
|
||||||
|
var tHolder = "proto t";
|
||||||
|
Object.defineProperty(proto, "t", {
|
||||||
|
get: function() {
|
||||||
|
if (!getterAllowed) throw new Error("getter is called");
|
||||||
|
return tHolder;
|
||||||
|
},
|
||||||
|
set: function(v) {
|
||||||
|
if (!setterAllowed) throw new Error("setter is called");
|
||||||
|
tHolder = v;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
var t1Holder;
|
||||||
|
Object.defineProperty(proto, "t1", {
|
||||||
|
get: function() {
|
||||||
|
return t1Holder;
|
||||||
|
},
|
||||||
|
set: function(v) {
|
||||||
|
t1Holder = v;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
Object.setPrototypeOf(m, proto);
|
||||||
|
assert.sameValue(m.t, "42");
|
||||||
|
m.t = 43;
|
||||||
|
assert.sameValue(m.t, 43);
|
||||||
|
t1Holder = "test";
|
||||||
|
assert.sameValue(m.t1, "test");
|
||||||
|
m.t1 = "test1";
|
||||||
|
assert.sameValue(m.t1, "test1");
|
||||||
|
delete m.t;
|
||||||
|
getterAllowed = true;
|
||||||
|
assert.sameValue(m.t, "proto t", "after delete");
|
||||||
|
setterAllowed = true;
|
||||||
|
m.t = true;
|
||||||
|
assert.sameValue(m.t, true);
|
||||||
|
assert.sameValue(tHolder, true);
|
||||||
|
Object.preventExtensions(m);
|
||||||
|
assert.throws(TypeError, function() {
|
||||||
|
m.t2 = 1;
|
||||||
|
});
|
||||||
|
m.t1 = "test2";
|
||||||
|
assert.sameValue(m.t1, "test2");
|
||||||
|
})();
|
||||||
|
`, _undefined, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGoMapProtoProp(t *testing.T) {
|
||||||
|
const SCRIPT = `
|
||||||
|
(function() {
|
||||||
|
"use strict";
|
||||||
|
var proto = {};
|
||||||
|
Object.defineProperty(proto, "ro", {value: 42});
|
||||||
|
Object.setPrototypeOf(m, proto);
|
||||||
|
assert.throws(TypeError, function() {
|
||||||
|
m.ro = 43;
|
||||||
|
});
|
||||||
|
Object.defineProperty(m, "ro", {value: 43});
|
||||||
|
assert.sameValue(m.ro, 43);
|
||||||
|
})();
|
||||||
|
`
|
||||||
|
|
||||||
|
r := New()
|
||||||
|
r.Set("m", map[string]interface{}{})
|
||||||
|
r.testScriptWithTestLib(SCRIPT, _undefined, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGoMapProtoPropChain(t *testing.T) {
|
||||||
|
const SCRIPT = `
|
||||||
|
(function() {
|
||||||
|
"use strict";
|
||||||
|
var p1 = Object.create(null);
|
||||||
|
m.__proto__ = p1;
|
||||||
|
|
||||||
|
Object.defineProperty(p1, "test", {
|
||||||
|
value: 42
|
||||||
|
});
|
||||||
|
|
||||||
|
Object.defineProperty(m, "test", {
|
||||||
|
value: 43,
|
||||||
|
writable: true,
|
||||||
|
});
|
||||||
|
var o = Object.create(m);
|
||||||
|
o.test = 44;
|
||||||
|
assert.sameValue(o.test, 44);
|
||||||
|
|
||||||
|
var sym = Symbol(true);
|
||||||
|
Object.defineProperty(p1, sym, {
|
||||||
|
value: 42
|
||||||
|
});
|
||||||
|
|
||||||
|
Object.defineProperty(m, sym, {
|
||||||
|
value: 43,
|
||||||
|
writable: true,
|
||||||
|
});
|
||||||
|
o[sym] = 44;
|
||||||
|
assert.sameValue(o[sym], 44);
|
||||||
|
})();
|
||||||
|
`
|
||||||
|
|
||||||
|
r := New()
|
||||||
|
r.Set("m", map[string]interface{}{})
|
||||||
|
r.testScriptWithTestLib(SCRIPT, _undefined, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGoMapUnicode(t *testing.T) {
|
||||||
|
const SCRIPT = `
|
||||||
|
Object.setPrototypeOf(m, s);
|
||||||
|
if (m.Тест !== "passed") {
|
||||||
|
throw new Error("m.Тест: " + m.Тест);
|
||||||
|
}
|
||||||
|
m["é"];
|
||||||
|
`
|
||||||
|
type S struct {
|
||||||
|
Тест string
|
||||||
|
}
|
||||||
|
vm := New()
|
||||||
|
m := map[string]interface{}{
|
||||||
|
"é": 42,
|
||||||
|
}
|
||||||
|
s := S{
|
||||||
|
Тест: "passed",
|
||||||
|
}
|
||||||
|
vm.Set("m", m)
|
||||||
|
vm.Set("s", &s)
|
||||||
|
res, err := vm.RunString(SCRIPT)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if res == nil || !res.StrictEquals(valueInt(42)) {
|
||||||
|
t.Fatalf("Unexpected value: %v", res)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -221,6 +221,9 @@ func (o *objectGoReflect) _getMethod(jsName string) reflect.Value {
|
|||||||
|
|
||||||
func (o *objectGoReflect) elemToValue(ev reflect.Value) (Value, reflectValueWrapper) {
|
func (o *objectGoReflect) elemToValue(ev reflect.Value) (Value, reflectValueWrapper) {
|
||||||
if isContainer(ev.Kind()) {
|
if isContainer(ev.Kind()) {
|
||||||
|
if ev.CanAddr() {
|
||||||
|
ev = ev.Addr()
|
||||||
|
}
|
||||||
ret := o.val.runtime.toValue(ev.Interface(), ev)
|
ret := o.val.runtime.toValue(ev.Interface(), ev)
|
||||||
if obj, ok := ret.(*Object); ok {
|
if obj, ok := ret.(*Object); ok {
|
||||||
if w, ok := obj.self.(reflectValueWrapper); ok {
|
if w, ok := obj.self.(reflectValueWrapper); ok {
|
||||||
|
|||||||
1687
goja/object_goreflect_test.go
Normal file
1687
goja/object_goreflect_test.go
Normal file
File diff suppressed because it is too large
Load Diff
520
goja/object_goslice_reflect_test.go
Normal file
520
goja/object_goslice_reflect_test.go
Normal file
@ -0,0 +1,520 @@
|
|||||||
|
package goja
|
||||||
|
|
||||||
|
import (
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGoSliceReflectBasic(t *testing.T) {
|
||||||
|
const SCRIPT = `
|
||||||
|
var sum = 0;
|
||||||
|
for (var i = 0; i < a.length; i++) {
|
||||||
|
sum += a[i];
|
||||||
|
}
|
||||||
|
sum;
|
||||||
|
`
|
||||||
|
r := New()
|
||||||
|
r.Set("a", []int{1, 2, 3, 4})
|
||||||
|
v, err := r.RunString(SCRIPT)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if i := v.ToInteger(); i != 10 {
|
||||||
|
t.Fatalf("Expected 10, got: %d", i)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGoSliceReflectIn(t *testing.T) {
|
||||||
|
const SCRIPT = `
|
||||||
|
var idx = "";
|
||||||
|
for (var i in a) {
|
||||||
|
idx += i;
|
||||||
|
}
|
||||||
|
idx;
|
||||||
|
`
|
||||||
|
r := New()
|
||||||
|
r.Set("a", []int{1, 2, 3, 4})
|
||||||
|
v, err := r.RunString(SCRIPT)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if i := v.String(); i != "0123" {
|
||||||
|
t.Fatalf("Expected '0123', got: '%s'", i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGoSliceReflectSet(t *testing.T) {
|
||||||
|
const SCRIPT = `
|
||||||
|
a[0] = 33;
|
||||||
|
a[1] = 333;
|
||||||
|
a[2] = "42";
|
||||||
|
a[3] = {};
|
||||||
|
a[4] = 0;
|
||||||
|
`
|
||||||
|
r := New()
|
||||||
|
a := []int8{1, 2, 3, 4}
|
||||||
|
r.Set("a", a)
|
||||||
|
_, err := r.RunString(SCRIPT)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if a[0] != 33 {
|
||||||
|
t.Fatalf("a[0] = %d, expected 33", a[0])
|
||||||
|
}
|
||||||
|
if a[1] != 77 {
|
||||||
|
t.Fatalf("a[1] = %d, expected 77", a[1])
|
||||||
|
}
|
||||||
|
if a[2] != 42 {
|
||||||
|
t.Fatalf("a[2] = %d, expected 42", a[2])
|
||||||
|
}
|
||||||
|
if a[3] != 0 {
|
||||||
|
t.Fatalf("a[3] = %d, expected 0", a[3])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGoSliceReflectPush(t *testing.T) {
|
||||||
|
|
||||||
|
r := New()
|
||||||
|
|
||||||
|
t.Run("Can push to array by array ptr", func(t *testing.T) {
|
||||||
|
a := []int8{1}
|
||||||
|
r.Set("a", &a)
|
||||||
|
_, err := r.RunString(`a.push (10)`)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if a[1] != 10 {
|
||||||
|
t.Fatalf("a[1] = %d, expected 10", a[1])
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Can push to array by struct ptr", func(t *testing.T) {
|
||||||
|
type testStr struct {
|
||||||
|
A []int
|
||||||
|
}
|
||||||
|
a := testStr{
|
||||||
|
A: []int{2},
|
||||||
|
}
|
||||||
|
|
||||||
|
r.Set("a", &a)
|
||||||
|
_, err := r.RunString(`a.A.push (10)`)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if a.A[1] != 10 {
|
||||||
|
t.Fatalf("a[1] = %v, expected 10", a)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGoSliceReflectStructField(t *testing.T) {
|
||||||
|
vm := New()
|
||||||
|
var s struct {
|
||||||
|
A []int
|
||||||
|
B *[]int
|
||||||
|
}
|
||||||
|
vm.Set("s", &s)
|
||||||
|
_, err := vm.RunString(`
|
||||||
|
'use strict';
|
||||||
|
s.A.push(1);
|
||||||
|
if (s.B !== null) {
|
||||||
|
throw new Error("s.B is not null: " + s.B);
|
||||||
|
}
|
||||||
|
s.B = [2];
|
||||||
|
`)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if len(s.A) != 1 || s.A[0] != 1 {
|
||||||
|
t.Fatalf("s.A: %v", s.A)
|
||||||
|
}
|
||||||
|
if len(*s.B) != 1 || (*s.B)[0] != 2 {
|
||||||
|
t.Fatalf("s.B: %v", *s.B)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGoSliceReflectExportToStructField(t *testing.T) {
|
||||||
|
vm := New()
|
||||||
|
v, err := vm.RunString(`({A: [1], B: [2]})`)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
var s struct {
|
||||||
|
A []int
|
||||||
|
B *[]int
|
||||||
|
}
|
||||||
|
err = vm.ExportTo(v, &s)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if len(s.A) != 1 || s.A[0] != 1 {
|
||||||
|
t.Fatalf("s.A: %v", s.A)
|
||||||
|
}
|
||||||
|
if len(*s.B) != 1 || (*s.B)[0] != 2 {
|
||||||
|
t.Fatalf("s.B: %v", *s.B)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGoSliceReflectProtoMethod(t *testing.T) {
|
||||||
|
const SCRIPT = `
|
||||||
|
a.join(",")
|
||||||
|
`
|
||||||
|
|
||||||
|
r := New()
|
||||||
|
a := []int8{1, 2, 3, 4}
|
||||||
|
r.Set("a", a)
|
||||||
|
ret, err := r.RunString(SCRIPT)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if s := ret.String(); s != "1,2,3,4" {
|
||||||
|
t.Fatalf("Unexpected result: '%s'", s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type gosliceReflect_withMethods []interface{}
|
||||||
|
|
||||||
|
func (s gosliceReflect_withMethods) Method() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGoSliceReflectMethod(t *testing.T) {
|
||||||
|
const SCRIPT = `
|
||||||
|
typeof a === "object" && a[0] === 42 && a.Method() === true;
|
||||||
|
`
|
||||||
|
|
||||||
|
vm := New()
|
||||||
|
a := make(gosliceReflect_withMethods, 1)
|
||||||
|
a[0] = 42
|
||||||
|
vm.Set("a", a)
|
||||||
|
v, err := vm.RunString(SCRIPT)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if !v.StrictEquals(valueTrue) {
|
||||||
|
t.Fatalf("Expected true, got %v", v)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGoSliceReflectGetStr(t *testing.T) {
|
||||||
|
r := New()
|
||||||
|
v := r.ToValue([]string{"test"})
|
||||||
|
if o, ok := v.(*Object); ok {
|
||||||
|
if e := o.Get("0").Export(); e != "test" {
|
||||||
|
t.Fatalf("Unexpected o.Get(\"0\"): %v", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGoSliceReflectNilObjectIfaceVal(t *testing.T) {
|
||||||
|
r := New()
|
||||||
|
a := []Value{(*Object)(nil)}
|
||||||
|
r.Set("a", a)
|
||||||
|
ret, err := r.RunString(`
|
||||||
|
""+a[0];
|
||||||
|
`)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if !asciiString("null").SameAs(ret) {
|
||||||
|
t.Fatalf("ret: %v", ret)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGoSliceReflectSetLength(t *testing.T) {
|
||||||
|
r := New()
|
||||||
|
a := []int{1, 2, 3, 4}
|
||||||
|
b := []testing.TB{&testing.T{}, &testing.T{}, (*testing.T)(nil)}
|
||||||
|
r.Set("a", &a)
|
||||||
|
r.Set("b", &b)
|
||||||
|
_, err := r.RunString(`
|
||||||
|
'use strict';
|
||||||
|
a.length = 3;
|
||||||
|
if (a.length !== 3) {
|
||||||
|
throw new Error("length="+a.length);
|
||||||
|
}
|
||||||
|
if (a[3] !== undefined) {
|
||||||
|
throw new Error("a[3]="+a[3]);
|
||||||
|
}
|
||||||
|
a.length = 5;
|
||||||
|
if (a.length !== 5) {
|
||||||
|
throw new Error("a.length="+a.length);
|
||||||
|
}
|
||||||
|
if (a[3] !== 0) {
|
||||||
|
throw new Error("a[3]="+a[3]);
|
||||||
|
}
|
||||||
|
if (a[4] !== 0) {
|
||||||
|
throw new Error("a[4]="+a[4]);
|
||||||
|
}
|
||||||
|
|
||||||
|
b.length = 3;
|
||||||
|
if (b.length !== 3) {
|
||||||
|
throw new Error("b.length="+b.length);
|
||||||
|
}
|
||||||
|
if (b[3] !== undefined) {
|
||||||
|
throw new Error("b[3]="+b[3]);
|
||||||
|
}
|
||||||
|
b.length = 5;
|
||||||
|
if (b.length !== 5) {
|
||||||
|
throw new Error("length="+b.length);
|
||||||
|
}
|
||||||
|
if (b[3] !== null) {
|
||||||
|
throw new Error("b[3]="+b[3]);
|
||||||
|
}
|
||||||
|
if (b[4] !== null) {
|
||||||
|
throw new Error("b[4]="+b[4]);
|
||||||
|
}
|
||||||
|
if (b[2] !== null) {
|
||||||
|
throw new Error("b[2]="+b[2]);
|
||||||
|
}
|
||||||
|
`)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGoSliceReflectProto(t *testing.T) {
|
||||||
|
r := New()
|
||||||
|
a := []*Object{{}, nil, {}}
|
||||||
|
r.Set("a", &a)
|
||||||
|
r.testScriptWithTestLib(`
|
||||||
|
var proto = [,2,,4];
|
||||||
|
Object.setPrototypeOf(a, proto);
|
||||||
|
assert.sameValue(a[1], null, "a[1]");
|
||||||
|
assert.sameValue(a[3], 4, "a[3]");
|
||||||
|
var desc = Object.getOwnPropertyDescriptor(a, "1");
|
||||||
|
assert.sameValue(desc.value, null, "desc.value");
|
||||||
|
assert(desc.writable, "writable");
|
||||||
|
assert(desc.enumerable, "enumerable");
|
||||||
|
assert(!desc.configurable, "configurable");
|
||||||
|
var v5;
|
||||||
|
Object.defineProperty(proto, "5", {
|
||||||
|
set: function(v) {
|
||||||
|
v5 = v;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
a[5] = "test";
|
||||||
|
assert.sameValue(v5, "test", "v5");
|
||||||
|
`, _undefined, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGoSliceReflectProtoProto(t *testing.T) {
|
||||||
|
r := New()
|
||||||
|
a := []*Object{{}, nil, {}}
|
||||||
|
proto := []*Object{{}, {}, {}, {}}
|
||||||
|
r.Set("a", &a)
|
||||||
|
r.Set("proto", proto)
|
||||||
|
_, err := r.RunString(`
|
||||||
|
"use strict";
|
||||||
|
var protoproto = {};
|
||||||
|
Object.defineProperty(protoproto, "3", {
|
||||||
|
value: 42
|
||||||
|
});
|
||||||
|
Object.setPrototypeOf(proto, protoproto);
|
||||||
|
Object.setPrototypeOf(a, proto);
|
||||||
|
if (a.hasOwnProperty("3")) {
|
||||||
|
throw new Error("a.hasOwnProperty(\"3\")");
|
||||||
|
}
|
||||||
|
if (a[3] !== null) {
|
||||||
|
throw new Error("a[3]="+a[3]);
|
||||||
|
}
|
||||||
|
a[3] = null;
|
||||||
|
if (a[3] !== null) {
|
||||||
|
throw new Error("a[3]=" + a[3]);
|
||||||
|
}
|
||||||
|
`)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGoSliceReflectDelete(t *testing.T) {
|
||||||
|
r := New()
|
||||||
|
a := []*Object{{}, nil, {}}
|
||||||
|
r.Set("a", a)
|
||||||
|
v, err := r.RunString(`
|
||||||
|
delete a[0] && delete a[1] && delete a[3];
|
||||||
|
`)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if v != valueTrue {
|
||||||
|
t.Fatalf("not true: %v", v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGoSliceReflectPop(t *testing.T) {
|
||||||
|
r := New()
|
||||||
|
a := []string{"1", "", "3"}
|
||||||
|
r.Set("a", &a)
|
||||||
|
v, err := r.RunString(`
|
||||||
|
a.pop()
|
||||||
|
`)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if !v.SameAs(asciiString("3")) {
|
||||||
|
t.Fatal(v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGoSliceReflectPopNoPtr(t *testing.T) {
|
||||||
|
r := New()
|
||||||
|
a := []string{"1", "", "3"}
|
||||||
|
r.Set("a", a)
|
||||||
|
v, err := r.RunString(`
|
||||||
|
a.pop()
|
||||||
|
`)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if !v.SameAs(asciiString("3")) {
|
||||||
|
t.Fatal(v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGoSliceReflectLengthProperty(t *testing.T) {
|
||||||
|
vm := New()
|
||||||
|
vm.Set("s", []int{2, 3, 4})
|
||||||
|
_, err := vm.RunString(`
|
||||||
|
if (!s.hasOwnProperty("length")) {
|
||||||
|
throw new Error("hasOwnProperty() returned false");
|
||||||
|
}
|
||||||
|
let desc = Object.getOwnPropertyDescriptor(s, "length");
|
||||||
|
if (desc.value !== 3 || !desc.writable || desc.enumerable || desc.configurable) {
|
||||||
|
throw new Error("incorrect property descriptor: " + JSON.stringify(desc));
|
||||||
|
}
|
||||||
|
`)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type testCustomSliceWithMethods []int
|
||||||
|
|
||||||
|
func (a testCustomSliceWithMethods) Method() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGoSliceReflectMethods(t *testing.T) {
|
||||||
|
vm := New()
|
||||||
|
vm.Set("s", testCustomSliceWithMethods{1, 2, 3})
|
||||||
|
_, err := vm.RunString(`
|
||||||
|
if (!s.hasOwnProperty("Method")) {
|
||||||
|
throw new Error("hasOwnProperty() returned false");
|
||||||
|
}
|
||||||
|
let desc = Object.getOwnPropertyDescriptor(s, "Method");
|
||||||
|
if (desc.value() !== true || desc.writable || !desc.enumerable || desc.configurable) {
|
||||||
|
throw new Error("incorrect property descriptor: " + JSON.stringify(desc));
|
||||||
|
}
|
||||||
|
`)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGoSliceReflectExportAfterGrow(t *testing.T) {
|
||||||
|
vm := New()
|
||||||
|
vm.Set("a", []int{1})
|
||||||
|
v, err := vm.RunString(`
|
||||||
|
a.push(2);
|
||||||
|
a;
|
||||||
|
`)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
exp := v.Export()
|
||||||
|
if a, ok := exp.([]int); ok {
|
||||||
|
if len(a) != 2 || a[0] != 1 || a[1] != 2 {
|
||||||
|
t.Fatal(a)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
t.Fatalf("Wrong type: %T", exp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGoSliceReflectSort(t *testing.T) {
|
||||||
|
vm := New()
|
||||||
|
type Thing struct{ Name string }
|
||||||
|
vm.Set("v", []*Thing{
|
||||||
|
{Name: "log"},
|
||||||
|
{Name: "etc"},
|
||||||
|
{Name: "test"},
|
||||||
|
{Name: "bin"},
|
||||||
|
})
|
||||||
|
ret, err := vm.RunString(`
|
||||||
|
//v.sort((a, b) => a.Name.localeCompare(b.Name)).map((x) => x.Name);
|
||||||
|
const tmp = v[0];
|
||||||
|
v[0] = v[1];
|
||||||
|
v[1] = tmp;
|
||||||
|
v[0].Name + v[1].Name;
|
||||||
|
`)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
t.Log(ret.Export())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGoSliceReflect111(t *testing.T) {
|
||||||
|
vm := New()
|
||||||
|
vm.Set("v", []int32{
|
||||||
|
1, 2,
|
||||||
|
})
|
||||||
|
ret, err := vm.RunString(`
|
||||||
|
//v.sort((a, b) => a.Name.localeCompare(b.Name)).map((x) => x.Name);
|
||||||
|
const tmp = v[0];
|
||||||
|
v[0] = v[1];
|
||||||
|
v[1] = tmp;
|
||||||
|
"" + v[0] + v[1];
|
||||||
|
`)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
t.Log(ret.Export())
|
||||||
|
a := []int{1, 2}
|
||||||
|
a0 := reflect.ValueOf(a).Index(0)
|
||||||
|
a0.Set(reflect.ValueOf(0))
|
||||||
|
t.Log(a[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGoSliceReflectExternalLenUpdate(t *testing.T) {
|
||||||
|
data := &[]int{1}
|
||||||
|
|
||||||
|
vm := New()
|
||||||
|
vm.Set("data", data)
|
||||||
|
vm.Set("append", func(a *[]int, v int) {
|
||||||
|
if a != data {
|
||||||
|
panic(vm.NewTypeError("a != data"))
|
||||||
|
}
|
||||||
|
*a = append(*a, v)
|
||||||
|
})
|
||||||
|
|
||||||
|
vm.testScriptWithTestLib(`
|
||||||
|
assert.sameValue(data.length, 1);
|
||||||
|
|
||||||
|
// modify with js
|
||||||
|
data.push(1);
|
||||||
|
assert.sameValue(data.length, 2);
|
||||||
|
|
||||||
|
// modify with go
|
||||||
|
append(data, 2);
|
||||||
|
assert.sameValue(data.length, 3);
|
||||||
|
`, _undefined, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkGoSliceReflectSet(b *testing.B) {
|
||||||
|
vm := New()
|
||||||
|
a := vm.ToValue([]int{1}).(*Object)
|
||||||
|
b.ResetTimer()
|
||||||
|
v := intToValue(0)
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
a.Set("0", v)
|
||||||
|
}
|
||||||
|
}
|
||||||
298
goja/object_goslice_test.go
Normal file
298
goja/object_goslice_test.go
Normal file
@ -0,0 +1,298 @@
|
|||||||
|
package goja
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGoSliceBasic(t *testing.T) {
|
||||||
|
const SCRIPT = `
|
||||||
|
var sum = 0;
|
||||||
|
for (var i = 0; i < a.length; i++) {
|
||||||
|
sum += a[i];
|
||||||
|
}
|
||||||
|
sum;
|
||||||
|
`
|
||||||
|
r := New()
|
||||||
|
r.Set("a", []interface{}{1, 2, 3, 4})
|
||||||
|
v, err := r.RunString(SCRIPT)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if i := v.ToInteger(); i != 10 {
|
||||||
|
t.Fatalf("Expected 10, got: %d", i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGoSliceIn(t *testing.T) {
|
||||||
|
const SCRIPT = `
|
||||||
|
var idx = "";
|
||||||
|
for (var i in a) {
|
||||||
|
idx += i;
|
||||||
|
}
|
||||||
|
idx;
|
||||||
|
`
|
||||||
|
r := New()
|
||||||
|
r.Set("a", []interface{}{1, 2, 3, 4})
|
||||||
|
v, err := r.RunString(SCRIPT)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if i := v.String(); i != "0123" {
|
||||||
|
t.Fatalf("Expected '0123', got: '%s'", i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGoSliceExpand(t *testing.T) {
|
||||||
|
const SCRIPT = `
|
||||||
|
var l = a.length;
|
||||||
|
for (var i = 0; i < l; i++) {
|
||||||
|
a[l + i] = a[i] * 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
var sum = 0;
|
||||||
|
for (var i = 0; i < a.length; i++) {
|
||||||
|
sum += a[i];
|
||||||
|
}
|
||||||
|
sum;
|
||||||
|
`
|
||||||
|
r := New()
|
||||||
|
a := []interface{}{int64(1), int64(2), int64(3), int64(4)}
|
||||||
|
r.Set("a", &a)
|
||||||
|
v, err := r.RunString(SCRIPT)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
sum := int64(0)
|
||||||
|
for _, v := range a {
|
||||||
|
sum += v.(int64)
|
||||||
|
}
|
||||||
|
if i := v.ToInteger(); i != sum {
|
||||||
|
t.Fatalf("Expected %d, got: %d", sum, i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGoSliceProtoMethod(t *testing.T) {
|
||||||
|
const SCRIPT = `
|
||||||
|
a.join(",")
|
||||||
|
`
|
||||||
|
|
||||||
|
r := New()
|
||||||
|
a := []interface{}{1, 2, 3, 4}
|
||||||
|
r.Set("a", a)
|
||||||
|
ret, err := r.RunString(SCRIPT)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if s := ret.String(); s != "1,2,3,4" {
|
||||||
|
t.Fatalf("Unexpected result: '%s'", s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGoSliceSetLength(t *testing.T) {
|
||||||
|
r := New()
|
||||||
|
a := []interface{}{1, 2, 3, 4}
|
||||||
|
r.Set("a", &a)
|
||||||
|
_, err := r.RunString(`
|
||||||
|
'use strict';
|
||||||
|
a.length = 3;
|
||||||
|
if (a.length !== 3) {
|
||||||
|
throw new Error("length="+a.length);
|
||||||
|
}
|
||||||
|
if (a[3] !== undefined) {
|
||||||
|
throw new Error("a[3](1)="+a[3]);
|
||||||
|
}
|
||||||
|
a.length = 5;
|
||||||
|
if (a.length !== 5) {
|
||||||
|
throw new Error("length="+a.length);
|
||||||
|
}
|
||||||
|
if (a[3] !== null) {
|
||||||
|
throw new Error("a[3](2)="+a[3]);
|
||||||
|
}
|
||||||
|
if (a[4] !== null) {
|
||||||
|
throw new Error("a[4]="+a[4]);
|
||||||
|
}
|
||||||
|
`)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGoSliceProto(t *testing.T) {
|
||||||
|
r := New()
|
||||||
|
a := []interface{}{1, nil, 3}
|
||||||
|
r.Set("a", &a)
|
||||||
|
r.testScriptWithTestLib(`
|
||||||
|
var proto = [,2,,4];
|
||||||
|
Object.setPrototypeOf(a, proto);
|
||||||
|
assert.sameValue(a[1], null, "a[1]");
|
||||||
|
assert.sameValue(a[3], 4, "a[3]");
|
||||||
|
var desc = Object.getOwnPropertyDescriptor(a, "1");
|
||||||
|
assert.sameValue(desc.value, null, "desc.value");
|
||||||
|
assert(desc.writable, "writable");
|
||||||
|
assert(desc.enumerable, "enumerable");
|
||||||
|
assert(!desc.configurable, "configurable");
|
||||||
|
var v5;
|
||||||
|
Object.defineProperty(proto, "5", {
|
||||||
|
set: function(v) {
|
||||||
|
v5 = v;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
a[5] = "test";
|
||||||
|
assert.sameValue(v5, "test", "v5");
|
||||||
|
`, _undefined, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGoSliceProtoProto(t *testing.T) {
|
||||||
|
r := New()
|
||||||
|
a := []interface{}{1, nil, 3}
|
||||||
|
proto := []interface{}{1, 2, 3, 4}
|
||||||
|
r.Set("a", &a)
|
||||||
|
r.Set("proto", proto)
|
||||||
|
_, err := r.RunString(`
|
||||||
|
"use strict";
|
||||||
|
var protoproto = Object.create(null);
|
||||||
|
Object.defineProperty(protoproto, "3", {
|
||||||
|
value: 42
|
||||||
|
});
|
||||||
|
Object.setPrototypeOf(proto, protoproto);
|
||||||
|
Object.setPrototypeOf(a, proto);
|
||||||
|
a[3] = 11;
|
||||||
|
if (a[3] !== 11) {
|
||||||
|
throw new Error("a[3]=" + a[3]);
|
||||||
|
}
|
||||||
|
`)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGoSliceDelete(t *testing.T) {
|
||||||
|
r := New()
|
||||||
|
a := []interface{}{1, nil, 3}
|
||||||
|
r.Set("a", a)
|
||||||
|
v, err := r.RunString(`
|
||||||
|
delete a[0] && delete a[1] && delete a[3];
|
||||||
|
`)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if v != valueTrue {
|
||||||
|
t.Fatalf("not true: %v", v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGoSlicePop(t *testing.T) {
|
||||||
|
r := New()
|
||||||
|
a := []interface{}{1, nil, 3}
|
||||||
|
r.Set("a", &a)
|
||||||
|
v, err := r.RunString(`
|
||||||
|
a.pop()
|
||||||
|
`)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if !v.SameAs(intToValue(3)) {
|
||||||
|
t.Fatal(v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGoSlicePopNoPtr(t *testing.T) {
|
||||||
|
r := New()
|
||||||
|
a := []interface{}{1, nil, 3}
|
||||||
|
r.Set("a", a)
|
||||||
|
v, err := r.RunString(`
|
||||||
|
a.pop()
|
||||||
|
`)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if !v.SameAs(intToValue(3)) {
|
||||||
|
t.Fatal(v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGoSliceShift(t *testing.T) {
|
||||||
|
r := New()
|
||||||
|
a := []interface{}{1, nil, 3}
|
||||||
|
r.Set("a", &a)
|
||||||
|
v, err := r.RunString(`
|
||||||
|
a.shift()
|
||||||
|
`)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if !v.SameAs(intToValue(1)) {
|
||||||
|
t.Fatal(v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGoSliceLengthProperty(t *testing.T) {
|
||||||
|
vm := New()
|
||||||
|
vm.Set("s", []interface{}{2, 3, 4})
|
||||||
|
_, err := vm.RunString(`
|
||||||
|
if (!s.hasOwnProperty("length")) {
|
||||||
|
throw new Error("hasOwnProperty() returned false");
|
||||||
|
}
|
||||||
|
let desc = Object.getOwnPropertyDescriptor(s, "length");
|
||||||
|
if (desc.value !== 3 || !desc.writable || desc.enumerable || desc.configurable) {
|
||||||
|
throw new Error("incorrect property descriptor: " + JSON.stringify(desc));
|
||||||
|
}
|
||||||
|
`)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGoSliceSort(t *testing.T) {
|
||||||
|
vm := New()
|
||||||
|
s := []interface{}{4, 2, 3}
|
||||||
|
vm.Set("s", &s)
|
||||||
|
_, err := vm.RunString(`s.sort()`)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if len(s) != 3 {
|
||||||
|
t.Fatalf("len: %d", len(s))
|
||||||
|
}
|
||||||
|
if s[0] != 2 || s[1] != 3 || s[2] != 4 {
|
||||||
|
t.Fatalf("val: %v", s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGoSliceToString(t *testing.T) {
|
||||||
|
vm := New()
|
||||||
|
s := []interface{}{4, 2, 3}
|
||||||
|
vm.Set("s", &s)
|
||||||
|
res, err := vm.RunString("`${s}`")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if exp := res.Export(); exp != "4,2,3" {
|
||||||
|
t.Fatal(exp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGoSliceExternalLenUpdate(t *testing.T) {
|
||||||
|
data := &[]interface{}{1}
|
||||||
|
|
||||||
|
vm := New()
|
||||||
|
vm.Set("data", data)
|
||||||
|
vm.Set("append", func(a *[]interface{}, v int) {
|
||||||
|
if a != data {
|
||||||
|
panic(vm.NewTypeError("a != data"))
|
||||||
|
}
|
||||||
|
*a = append(*a, v)
|
||||||
|
})
|
||||||
|
|
||||||
|
vm.testScriptWithTestLib(`
|
||||||
|
assert.sameValue(data.length, 1);
|
||||||
|
|
||||||
|
// modify with js
|
||||||
|
data.push(1);
|
||||||
|
assert.sameValue(data.length, 2);
|
||||||
|
|
||||||
|
// modify with go
|
||||||
|
append(data, 2);
|
||||||
|
assert.sameValue(data.length, 3);
|
||||||
|
`, _undefined, t)
|
||||||
|
}
|
||||||
668
goja/object_test.go
Normal file
668
goja/object_test.go
Normal file
@ -0,0 +1,668 @@
|
|||||||
|
package goja
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestDefineProperty(t *testing.T) {
|
||||||
|
r := New()
|
||||||
|
o := r.NewObject()
|
||||||
|
|
||||||
|
err := o.DefineDataProperty("data", r.ToValue(42), FLAG_TRUE, FLAG_TRUE, FLAG_TRUE)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = o.DefineAccessorProperty("accessor_ro", r.ToValue(func() int {
|
||||||
|
return 1
|
||||||
|
}), nil, FLAG_TRUE, FLAG_TRUE)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = o.DefineAccessorProperty("accessor_rw",
|
||||||
|
r.ToValue(func(call FunctionCall) Value {
|
||||||
|
return o.Get("__hidden")
|
||||||
|
}),
|
||||||
|
r.ToValue(func(call FunctionCall) (ret Value) {
|
||||||
|
o.Set("__hidden", call.Argument(0))
|
||||||
|
return
|
||||||
|
}),
|
||||||
|
FLAG_TRUE, FLAG_TRUE)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if v := o.Get("accessor_ro"); v.ToInteger() != 1 {
|
||||||
|
t.Fatalf("Unexpected accessor value: %v", v)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = o.Set("accessor_ro", r.ToValue(2))
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("Expected an error")
|
||||||
|
}
|
||||||
|
if ex, ok := err.(*Exception); ok {
|
||||||
|
if msg := ex.Error(); msg != "TypeError: Cannot assign to read only property 'accessor_ro'" {
|
||||||
|
t.Fatalf("Unexpected error: '%s'", msg)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
t.Fatalf("Unexected error type: %T", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = o.Set("accessor_rw", 42)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if v := o.Get("accessor_rw"); v.ToInteger() != 42 {
|
||||||
|
t.Fatalf("Unexpected value: %v", v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPropertyOrder(t *testing.T) {
|
||||||
|
const SCRIPT = `
|
||||||
|
var o = {};
|
||||||
|
var sym1 = Symbol(1);
|
||||||
|
var sym2 = Symbol(2);
|
||||||
|
o[sym2] = 1;
|
||||||
|
o[4294967294] = 1;
|
||||||
|
o[2] = 1;
|
||||||
|
o[1] = 1;
|
||||||
|
o[0] = 1;
|
||||||
|
o["02"] = 1;
|
||||||
|
o[4294967295] = 1;
|
||||||
|
o["01"] = 1;
|
||||||
|
o["00"] = 1;
|
||||||
|
o[sym1] = 1;
|
||||||
|
var expected = ["0", "1", "2", "4294967294", "02", "4294967295", "01", "00", sym2, sym1];
|
||||||
|
var actual = Reflect.ownKeys(o);
|
||||||
|
if (actual.length !== expected.length) {
|
||||||
|
throw new Error("Unexpected length: "+actual.length);
|
||||||
|
}
|
||||||
|
for (var i = 0; i < actual.length; i++) {
|
||||||
|
if (actual[i] !== expected[i]) {
|
||||||
|
throw new Error("Unexpected list: " + actual);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
testScript(SCRIPT, _undefined, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDefinePropertiesSymbol(t *testing.T) {
|
||||||
|
const SCRIPT = `
|
||||||
|
var desc = {};
|
||||||
|
desc[Symbol.toStringTag] = {value: "Test"};
|
||||||
|
var o = {};
|
||||||
|
Object.defineProperties(o, desc);
|
||||||
|
o[Symbol.toStringTag] === "Test";
|
||||||
|
`
|
||||||
|
|
||||||
|
testScript(SCRIPT, valueTrue, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestObjectShorthandProperties(t *testing.T) {
|
||||||
|
const SCRIPT = `
|
||||||
|
var b = 1;
|
||||||
|
var a = {b, get() {return "c"}};
|
||||||
|
|
||||||
|
assert.sameValue(a.b, b, "#1");
|
||||||
|
assert.sameValue(a.get(), "c", "#2");
|
||||||
|
|
||||||
|
var obj = {
|
||||||
|
w\u0069th() { return 42; }
|
||||||
|
};
|
||||||
|
|
||||||
|
assert.sameValue(obj['with'](), 42, 'property exists');
|
||||||
|
`
|
||||||
|
testScriptWithTestLib(SCRIPT, _undefined, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestObjectAssign(t *testing.T) {
|
||||||
|
const SCRIPT = `
|
||||||
|
assert.sameValue(Object.assign({ b: 1 }, { get a() {
|
||||||
|
Object.defineProperty(this, "b", {
|
||||||
|
value: 3,
|
||||||
|
enumerable: false
|
||||||
|
});
|
||||||
|
}, b: 2 }).b, 1, "#1");
|
||||||
|
|
||||||
|
assert.sameValue(Object.assign({ b: 1 }, { get a() {
|
||||||
|
delete this.b;
|
||||||
|
}, b: 2 }).b, 1, "#2");
|
||||||
|
`
|
||||||
|
testScriptWithTestLib(SCRIPT, _undefined, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestExportCircular(t *testing.T) {
|
||||||
|
vm := New()
|
||||||
|
o := vm.NewObject()
|
||||||
|
o.Set("o", o)
|
||||||
|
v := o.Export()
|
||||||
|
if m, ok := v.(map[string]interface{}); ok {
|
||||||
|
if reflect.ValueOf(m["o"]).Pointer() != reflect.ValueOf(v).Pointer() {
|
||||||
|
t.Fatal("Unexpected value")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
t.Fatal("Unexpected type")
|
||||||
|
}
|
||||||
|
|
||||||
|
res, err := vm.RunString(`var a = []; a[0] = a;`)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
v = res.Export()
|
||||||
|
if a, ok := v.([]interface{}); ok {
|
||||||
|
if reflect.ValueOf(a[0]).Pointer() != reflect.ValueOf(v).Pointer() {
|
||||||
|
t.Fatal("Unexpected value")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
t.Fatal("Unexpected type")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type test_s struct {
|
||||||
|
S *test_s1
|
||||||
|
}
|
||||||
|
type test_s1 struct {
|
||||||
|
S *test_s
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestExportToCircular(t *testing.T) {
|
||||||
|
vm := New()
|
||||||
|
o := vm.NewObject()
|
||||||
|
o.Set("o", o)
|
||||||
|
var m map[string]interface{}
|
||||||
|
err := vm.ExportTo(o, &m)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
type K string
|
||||||
|
type T map[K]T
|
||||||
|
var m1 T
|
||||||
|
err = vm.ExportTo(o, &m1)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
type A []A
|
||||||
|
var a A
|
||||||
|
res, err := vm.RunString("var a = []; a[0] = a;")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
err = vm.ExportTo(res, &a)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if &a[0] != &a[0][0] {
|
||||||
|
t.Fatal("values do not match")
|
||||||
|
}
|
||||||
|
|
||||||
|
o = vm.NewObject()
|
||||||
|
o.Set("S", o)
|
||||||
|
var s test_s
|
||||||
|
err = vm.ExportTo(o, &s)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if s.S.S != &s {
|
||||||
|
t.Fatalf("values do not match: %v, %v", s.S.S, &s)
|
||||||
|
}
|
||||||
|
|
||||||
|
type test_s2 struct {
|
||||||
|
S interface{}
|
||||||
|
S1 *test_s2
|
||||||
|
}
|
||||||
|
|
||||||
|
var s2 test_s2
|
||||||
|
o.Set("S1", o)
|
||||||
|
|
||||||
|
err = vm.ExportTo(o, &s2)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if m, ok := s2.S.(map[string]interface{}); ok {
|
||||||
|
if reflect.ValueOf(m["S"]).Pointer() != reflect.ValueOf(m).Pointer() {
|
||||||
|
t.Fatal("Unexpected m.S")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
t.Fatalf("Unexpected s2.S type: %T", s2.S)
|
||||||
|
}
|
||||||
|
if s2.S1 != &s2 {
|
||||||
|
t.Fatal("Unexpected s2.S1")
|
||||||
|
}
|
||||||
|
|
||||||
|
o1 := vm.NewObject()
|
||||||
|
o1.Set("S", o)
|
||||||
|
o1.Set("S1", o)
|
||||||
|
err = vm.ExportTo(o1, &s2)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if s2.S1.S1 != s2.S1 {
|
||||||
|
t.Fatal("Unexpected s2.S1.S1")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestExportWrappedMap(t *testing.T) {
|
||||||
|
vm := New()
|
||||||
|
m := map[string]interface{}{
|
||||||
|
"test": "failed",
|
||||||
|
}
|
||||||
|
exported := vm.ToValue(m).Export()
|
||||||
|
if exportedMap, ok := exported.(map[string]interface{}); ok {
|
||||||
|
exportedMap["test"] = "passed"
|
||||||
|
if v := m["test"]; v != "passed" {
|
||||||
|
t.Fatalf("Unexpected m[\"test\"]: %v", v)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
t.Fatalf("Unexpected export type: %T", exported)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestExportToWrappedMap(t *testing.T) {
|
||||||
|
vm := New()
|
||||||
|
m := map[string]interface{}{
|
||||||
|
"test": "failed",
|
||||||
|
}
|
||||||
|
var exported map[string]interface{}
|
||||||
|
err := vm.ExportTo(vm.ToValue(m), &exported)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
exported["test"] = "passed"
|
||||||
|
if v := m["test"]; v != "passed" {
|
||||||
|
t.Fatalf("Unexpected m[\"test\"]: %v", v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestExportToWrappedMapCustom(t *testing.T) {
|
||||||
|
type CustomMap map[string]bool
|
||||||
|
vm := New()
|
||||||
|
m := CustomMap{}
|
||||||
|
var exported CustomMap
|
||||||
|
err := vm.ExportTo(vm.ToValue(m), &exported)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
exported["test"] = true
|
||||||
|
if v := m["test"]; v != true {
|
||||||
|
t.Fatalf("Unexpected m[\"test\"]: %v", v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestExportToSliceNonIterable(t *testing.T) {
|
||||||
|
vm := New()
|
||||||
|
o := vm.NewObject()
|
||||||
|
var a []interface{}
|
||||||
|
err := vm.ExportTo(o, &a)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("Expected an error")
|
||||||
|
}
|
||||||
|
if len(a) != 0 {
|
||||||
|
t.Fatalf("a: %v", a)
|
||||||
|
}
|
||||||
|
if msg := err.Error(); msg != "cannot convert [object Object] to []interface {}: not an array or iterable" {
|
||||||
|
t.Fatalf("Unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleRuntime_ExportTo_iterableToSlice() {
|
||||||
|
vm := New()
|
||||||
|
v, err := vm.RunString(`
|
||||||
|
function reverseIterator() {
|
||||||
|
const arr = this;
|
||||||
|
let idx = arr.length;
|
||||||
|
return {
|
||||||
|
next: () => idx > 0 ? {value: arr[--idx]} : {done: true}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const arr = [1,2,3];
|
||||||
|
arr[Symbol.iterator] = reverseIterator;
|
||||||
|
arr;
|
||||||
|
`)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var arr []int
|
||||||
|
err = vm.ExportTo(v, &arr)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println(arr)
|
||||||
|
// Output: [3 2 1]
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRuntime_ExportTo_proxiedIterableToSlice(t *testing.T) {
|
||||||
|
vm := New()
|
||||||
|
v, err := vm.RunString(`
|
||||||
|
function reverseIterator() {
|
||||||
|
const arr = this;
|
||||||
|
let idx = arr.length;
|
||||||
|
return {
|
||||||
|
next: () => idx > 0 ? {value: arr[--idx]} : {done: true}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const arr = [1,2,3];
|
||||||
|
arr[Symbol.iterator] = reverseIterator;
|
||||||
|
new Proxy(arr, {});
|
||||||
|
`)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var arr []int
|
||||||
|
err = vm.ExportTo(v, &arr)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if out := fmt.Sprint(arr); out != "[3 2 1]" {
|
||||||
|
t.Fatal(out)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleRuntime_ExportTo_arrayLikeToSlice() {
|
||||||
|
vm := New()
|
||||||
|
v, err := vm.RunString(`
|
||||||
|
({
|
||||||
|
length: 3,
|
||||||
|
0: 1,
|
||||||
|
1: 2,
|
||||||
|
2: 3
|
||||||
|
});
|
||||||
|
`)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var arr []int
|
||||||
|
err = vm.ExportTo(v, &arr)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println(arr)
|
||||||
|
// Output: [1 2 3]
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestExportArrayToArrayMismatchedLengths(t *testing.T) {
|
||||||
|
vm := New()
|
||||||
|
a := vm.NewArray(1, 2)
|
||||||
|
var a1 [3]int
|
||||||
|
err := vm.ExportTo(a, &a1)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("expected error")
|
||||||
|
}
|
||||||
|
if msg := err.Error(); !strings.Contains(msg, "lengths mismatch") {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestExportIterableToArrayMismatchedLengths(t *testing.T) {
|
||||||
|
vm := New()
|
||||||
|
a, err := vm.RunString(`
|
||||||
|
new Map([[1, true], [2, true]]);
|
||||||
|
`)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var a1 [3]interface{}
|
||||||
|
err = vm.ExportTo(a, &a1)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("expected error")
|
||||||
|
}
|
||||||
|
if msg := err.Error(); !strings.Contains(msg, "lengths mismatch") {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestExportArrayLikeToArrayMismatchedLengths(t *testing.T) {
|
||||||
|
vm := New()
|
||||||
|
a, err := vm.RunString(`
|
||||||
|
({
|
||||||
|
length: 2,
|
||||||
|
0: true,
|
||||||
|
1: true
|
||||||
|
});
|
||||||
|
`)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var a1 [3]interface{}
|
||||||
|
err = vm.ExportTo(a, &a1)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("expected error")
|
||||||
|
}
|
||||||
|
if msg := err.Error(); !strings.Contains(msg, "lengths mismatch") {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSetForeignReturnValue(t *testing.T) {
|
||||||
|
const SCRIPT = `
|
||||||
|
var array = [1, 2, 3];
|
||||||
|
var arrayTarget = new Proxy(array, {});
|
||||||
|
|
||||||
|
Object.preventExtensions(array);
|
||||||
|
|
||||||
|
!Reflect.set(arrayTarget, "foo", 2);
|
||||||
|
`
|
||||||
|
|
||||||
|
testScript(SCRIPT, valueTrue, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDefinePropertiesUndefinedVal(t *testing.T) {
|
||||||
|
const SCRIPT = `
|
||||||
|
var target = {};
|
||||||
|
var sym = Symbol();
|
||||||
|
target[sym] = 1;
|
||||||
|
target.foo = 2;
|
||||||
|
target[0] = 3;
|
||||||
|
|
||||||
|
var getOwnKeys = [];
|
||||||
|
var proxy = new Proxy(target, {
|
||||||
|
getOwnPropertyDescriptor: function(_target, key) {
|
||||||
|
getOwnKeys.push(key);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
Object.defineProperties({}, proxy);
|
||||||
|
true;
|
||||||
|
`
|
||||||
|
|
||||||
|
testScript(SCRIPT, valueTrue, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleObject_Delete() {
|
||||||
|
vm := New()
|
||||||
|
obj := vm.NewObject()
|
||||||
|
_ = obj.Set("test", true)
|
||||||
|
before := obj.Get("test")
|
||||||
|
_ = obj.Delete("test")
|
||||||
|
after := obj.Get("test")
|
||||||
|
fmt.Printf("before: %v, after: %v", before, after)
|
||||||
|
// Output: before: true, after: <nil>
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleObject_GetOwnPropertyNames() {
|
||||||
|
vm := New()
|
||||||
|
obj := vm.NewObject()
|
||||||
|
obj.DefineDataProperty("test", vm.ToValue(true), FLAG_TRUE, FLAG_TRUE, FLAG_FALSE) // define a non-enumerable property that Keys() would not return
|
||||||
|
fmt.Print(obj.GetOwnPropertyNames())
|
||||||
|
// Output: [test]
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestObjectEquality(t *testing.T) {
|
||||||
|
type CustomInt int
|
||||||
|
type S struct {
|
||||||
|
F CustomInt
|
||||||
|
}
|
||||||
|
vm := New()
|
||||||
|
vm.Set("s", S{})
|
||||||
|
// indexOf() and includes() use different equality checks (StrictEquals and SameValueZero respectively),
|
||||||
|
// but for objects they must behave the same. De-referencing s.F creates a new wrapper each time.
|
||||||
|
vm.testScriptWithTestLib(`
|
||||||
|
assert.sameValue([s.F].indexOf(s.F), 0, "indexOf");
|
||||||
|
assert([s.F].includes(s.F));
|
||||||
|
`, _undefined, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkPut(b *testing.B) {
|
||||||
|
v := &Object{}
|
||||||
|
|
||||||
|
o := &baseObject{
|
||||||
|
val: v,
|
||||||
|
extensible: true,
|
||||||
|
}
|
||||||
|
v.self = o
|
||||||
|
|
||||||
|
o.init()
|
||||||
|
|
||||||
|
var key Value = asciiString("test")
|
||||||
|
var val Value = valueInt(123)
|
||||||
|
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
v.setOwn(key, val, false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkPutStr(b *testing.B) {
|
||||||
|
v := &Object{}
|
||||||
|
|
||||||
|
o := &baseObject{
|
||||||
|
val: v,
|
||||||
|
extensible: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
o.init()
|
||||||
|
|
||||||
|
v.self = o
|
||||||
|
|
||||||
|
var val Value = valueInt(123)
|
||||||
|
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
o.setOwnStr("test", val, false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkGet(b *testing.B) {
|
||||||
|
v := &Object{}
|
||||||
|
|
||||||
|
o := &baseObject{
|
||||||
|
val: v,
|
||||||
|
extensible: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
o.init()
|
||||||
|
|
||||||
|
v.self = o
|
||||||
|
var n Value = asciiString("test")
|
||||||
|
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
v.get(n, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkGetStr(b *testing.B) {
|
||||||
|
v := &Object{}
|
||||||
|
|
||||||
|
o := &baseObject{
|
||||||
|
val: v,
|
||||||
|
extensible: true,
|
||||||
|
}
|
||||||
|
v.self = o
|
||||||
|
|
||||||
|
o.init()
|
||||||
|
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
o.getStr("test", nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func __toString(v Value) string {
|
||||||
|
switch v := v.(type) {
|
||||||
|
case asciiString:
|
||||||
|
return string(v)
|
||||||
|
default:
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkToString1(b *testing.B) {
|
||||||
|
v := asciiString("test")
|
||||||
|
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
v.toString()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkToString2(b *testing.B) {
|
||||||
|
v := asciiString("test")
|
||||||
|
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
__toString(v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkConv(b *testing.B) {
|
||||||
|
count := int64(0)
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
count += valueInt(123).ToInteger()
|
||||||
|
}
|
||||||
|
if count == 0 {
|
||||||
|
b.Fatal("zero")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkToUTF8String(b *testing.B) {
|
||||||
|
var s String = asciiString("test")
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
_ = s.String()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkAdd(b *testing.B) {
|
||||||
|
var x, y Value
|
||||||
|
x = valueInt(2)
|
||||||
|
y = valueInt(2)
|
||||||
|
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
if xi, ok := x.(valueInt); ok {
|
||||||
|
if yi, ok := y.(valueInt); ok {
|
||||||
|
x = xi + yi
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkAddString(b *testing.B) {
|
||||||
|
var x, y Value
|
||||||
|
|
||||||
|
tst := asciiString("22")
|
||||||
|
x = asciiString("2")
|
||||||
|
y = asciiString("2")
|
||||||
|
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
var z Value
|
||||||
|
if xi, ok := x.(String); ok {
|
||||||
|
if yi, ok := y.(String); ok {
|
||||||
|
z = xi.Concat(yi)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !z.StrictEquals(tst) {
|
||||||
|
b.Fatalf("Unexpected result %v", x)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
184
goja/parser/README.markdown
Normal file
184
goja/parser/README.markdown
Normal file
@ -0,0 +1,184 @@
|
|||||||
|
# parser
|
||||||
|
--
|
||||||
|
import "apigo.cc/gojs/goja/parser"
|
||||||
|
|
||||||
|
Package parser implements a parser for JavaScript. Borrowed from https://github.com/robertkrimen/otto/tree/master/parser
|
||||||
|
|
||||||
|
import (
|
||||||
|
"apigo.cc/gojs/goja/parser"
|
||||||
|
)
|
||||||
|
|
||||||
|
Parse and return an AST
|
||||||
|
|
||||||
|
filename := "" // A filename is optional
|
||||||
|
src := `
|
||||||
|
// Sample xyzzy example
|
||||||
|
(function(){
|
||||||
|
if (3.14159 > 0) {
|
||||||
|
console.log("Hello, World.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var xyzzy = NaN;
|
||||||
|
console.log("Nothing happens.");
|
||||||
|
return xyzzy;
|
||||||
|
})();
|
||||||
|
`
|
||||||
|
|
||||||
|
// Parse some JavaScript, yielding a *ast.Program and/or an ErrorList
|
||||||
|
program, err := parser.ParseFile(nil, filename, src, 0)
|
||||||
|
|
||||||
|
|
||||||
|
### Warning
|
||||||
|
|
||||||
|
The parser and AST interfaces are still works-in-progress (particularly where
|
||||||
|
node types are concerned) and may change in the future.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
#### func ParseFile
|
||||||
|
|
||||||
|
```go
|
||||||
|
func ParseFile(fileSet *file.FileSet, filename string, src interface{}, mode Mode) (*ast.Program, error)
|
||||||
|
```
|
||||||
|
ParseFile parses the source code of a single JavaScript/ECMAScript source file
|
||||||
|
and returns the corresponding ast.Program node.
|
||||||
|
|
||||||
|
If fileSet == nil, ParseFile parses source without a FileSet. If fileSet != nil,
|
||||||
|
ParseFile first adds filename and src to fileSet.
|
||||||
|
|
||||||
|
The filename argument is optional and is used for labelling errors, etc.
|
||||||
|
|
||||||
|
src may be a string, a byte slice, a bytes.Buffer, or an io.Reader, but it MUST
|
||||||
|
always be in UTF-8.
|
||||||
|
|
||||||
|
// Parse some JavaScript, yielding a *ast.Program and/or an ErrorList
|
||||||
|
program, err := parser.ParseFile(nil, "", `if (abc > 1) {}`, 0)
|
||||||
|
|
||||||
|
#### func ParseFunction
|
||||||
|
|
||||||
|
```go
|
||||||
|
func ParseFunction(parameterList, body string) (*ast.FunctionLiteral, error)
|
||||||
|
```
|
||||||
|
ParseFunction parses a given parameter list and body as a function and returns
|
||||||
|
the corresponding ast.FunctionLiteral node.
|
||||||
|
|
||||||
|
The parameter list, if any, should be a comma-separated list of identifiers.
|
||||||
|
|
||||||
|
#### func ReadSource
|
||||||
|
|
||||||
|
```go
|
||||||
|
func ReadSource(filename string, src interface{}) ([]byte, error)
|
||||||
|
```
|
||||||
|
|
||||||
|
#### func TransformRegExp
|
||||||
|
|
||||||
|
```go
|
||||||
|
func TransformRegExp(pattern string) (string, error)
|
||||||
|
```
|
||||||
|
TransformRegExp transforms a JavaScript pattern into a Go "regexp" pattern.
|
||||||
|
|
||||||
|
re2 (Go) cannot do backtracking, so the presence of a lookahead (?=) (?!) or
|
||||||
|
backreference (\1, \2, ...) will cause an error.
|
||||||
|
|
||||||
|
re2 (Go) has a different definition for \s: [\t\n\f\r ]. The JavaScript
|
||||||
|
definition, on the other hand, also includes \v, Unicode "Separator, Space",
|
||||||
|
etc.
|
||||||
|
|
||||||
|
If the pattern is invalid (not valid even in JavaScript), then this function
|
||||||
|
returns the empty string and an error.
|
||||||
|
|
||||||
|
If the pattern is valid, but incompatible (contains a lookahead or
|
||||||
|
backreference), then this function returns the transformation (a non-empty
|
||||||
|
string) AND an error.
|
||||||
|
|
||||||
|
#### type Error
|
||||||
|
|
||||||
|
```go
|
||||||
|
type Error struct {
|
||||||
|
Position file.Position
|
||||||
|
Message string
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
An Error represents a parsing error. It includes the position where the error
|
||||||
|
occurred and a message/description.
|
||||||
|
|
||||||
|
#### func (Error) Error
|
||||||
|
|
||||||
|
```go
|
||||||
|
func (self Error) Error() string
|
||||||
|
```
|
||||||
|
|
||||||
|
#### type ErrorList
|
||||||
|
|
||||||
|
```go
|
||||||
|
type ErrorList []*Error
|
||||||
|
```
|
||||||
|
|
||||||
|
ErrorList is a list of *Errors.
|
||||||
|
|
||||||
|
#### func (*ErrorList) Add
|
||||||
|
|
||||||
|
```go
|
||||||
|
func (self *ErrorList) Add(position file.Position, msg string)
|
||||||
|
```
|
||||||
|
Add adds an Error with given position and message to an ErrorList.
|
||||||
|
|
||||||
|
#### func (ErrorList) Err
|
||||||
|
|
||||||
|
```go
|
||||||
|
func (self ErrorList) Err() error
|
||||||
|
```
|
||||||
|
Err returns an error equivalent to this ErrorList. If the list is empty, Err
|
||||||
|
returns nil.
|
||||||
|
|
||||||
|
#### func (ErrorList) Error
|
||||||
|
|
||||||
|
```go
|
||||||
|
func (self ErrorList) Error() string
|
||||||
|
```
|
||||||
|
Error implements the Error interface.
|
||||||
|
|
||||||
|
#### func (ErrorList) Len
|
||||||
|
|
||||||
|
```go
|
||||||
|
func (self ErrorList) Len() int
|
||||||
|
```
|
||||||
|
|
||||||
|
#### func (ErrorList) Less
|
||||||
|
|
||||||
|
```go
|
||||||
|
func (self ErrorList) Less(i, j int) bool
|
||||||
|
```
|
||||||
|
|
||||||
|
#### func (*ErrorList) Reset
|
||||||
|
|
||||||
|
```go
|
||||||
|
func (self *ErrorList) Reset()
|
||||||
|
```
|
||||||
|
Reset resets an ErrorList to no errors.
|
||||||
|
|
||||||
|
#### func (ErrorList) Sort
|
||||||
|
|
||||||
|
```go
|
||||||
|
func (self ErrorList) Sort()
|
||||||
|
```
|
||||||
|
|
||||||
|
#### func (ErrorList) Swap
|
||||||
|
|
||||||
|
```go
|
||||||
|
func (self ErrorList) Swap(i, j int)
|
||||||
|
```
|
||||||
|
|
||||||
|
#### type Mode
|
||||||
|
|
||||||
|
```go
|
||||||
|
type Mode uint
|
||||||
|
```
|
||||||
|
|
||||||
|
A Mode value is a set of flags (or 0). They control optional parser
|
||||||
|
functionality.
|
||||||
|
|
||||||
|
--
|
||||||
|
**godocdown** http://github.com/robertkrimen/godocdown
|
||||||
@ -1287,6 +1287,12 @@ func (self *_parser) parseAssignmentExpression() ast.Expression {
|
|||||||
operator = token.SHIFT_RIGHT
|
operator = token.SHIFT_RIGHT
|
||||||
case token.UNSIGNED_SHIFT_RIGHT_ASSIGN:
|
case token.UNSIGNED_SHIFT_RIGHT_ASSIGN:
|
||||||
operator = token.UNSIGNED_SHIFT_RIGHT
|
operator = token.UNSIGNED_SHIFT_RIGHT
|
||||||
|
case token.LOGICAL_AND_ASSIGN:
|
||||||
|
operator = token.LOGICAL_AND
|
||||||
|
case token.LOGICAL_OR_ASSIGN:
|
||||||
|
operator = token.LOGICAL_OR
|
||||||
|
case token.COALESCE_ASSIGN:
|
||||||
|
operator = token.COALESCE
|
||||||
case token.ARROW:
|
case token.ARROW:
|
||||||
var paramList *ast.ParameterList
|
var paramList *ast.ParameterList
|
||||||
if id, ok := left.(*ast.Identifier); ok {
|
if id, ok := left.(*ast.Identifier); ok {
|
||||||
|
|||||||
@ -409,9 +409,9 @@ func (self *_parser) scan() (tkn token.Token, literal string, parsedLiteral unis
|
|||||||
tkn = token.STRICT_NOT_EQUAL
|
tkn = token.STRICT_NOT_EQUAL
|
||||||
}
|
}
|
||||||
case '&':
|
case '&':
|
||||||
tkn = self.switch3(token.AND, token.AND_ASSIGN, '&', token.LOGICAL_AND)
|
tkn = self.switch4(token.AND, token.AND_ASSIGN, '&', token.LOGICAL_AND, token.LOGICAL_AND_ASSIGN)
|
||||||
case '|':
|
case '|':
|
||||||
tkn = self.switch3(token.OR, token.OR_ASSIGN, '|', token.LOGICAL_OR)
|
tkn = self.switch4(token.OR, token.OR_ASSIGN, '|', token.LOGICAL_OR, token.LOGICAL_OR_ASSIGN)
|
||||||
case '~':
|
case '~':
|
||||||
tkn = token.BITWISE_NOT
|
tkn = token.BITWISE_NOT
|
||||||
case '?':
|
case '?':
|
||||||
@ -420,7 +420,12 @@ func (self *_parser) scan() (tkn token.Token, literal string, parsedLiteral unis
|
|||||||
tkn = token.QUESTION_DOT
|
tkn = token.QUESTION_DOT
|
||||||
} else if self.chr == '?' {
|
} else if self.chr == '?' {
|
||||||
self.read()
|
self.read()
|
||||||
tkn = token.COALESCE
|
if self.chr == '=' {
|
||||||
|
self.read()
|
||||||
|
tkn = token.COALESCE_ASSIGN
|
||||||
|
} else {
|
||||||
|
tkn = token.COALESCE
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
tkn = token.QUESTION_MARK
|
tkn = token.QUESTION_MARK
|
||||||
}
|
}
|
||||||
|
|||||||
423
goja/parser/lexer_test.go
Normal file
423
goja/parser/lexer_test.go
Normal file
@ -0,0 +1,423 @@
|
|||||||
|
package parser
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"apigo.cc/gojs/goja/file"
|
||||||
|
"apigo.cc/gojs/goja/token"
|
||||||
|
"apigo.cc/gojs/goja/unistring"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestLexer(t *testing.T) {
|
||||||
|
tt(t, func() {
|
||||||
|
setup := func(src string) *_parser {
|
||||||
|
parser := newParser("", src)
|
||||||
|
return parser
|
||||||
|
}
|
||||||
|
|
||||||
|
test := func(src string, test ...interface{}) {
|
||||||
|
parser := setup(src)
|
||||||
|
for len(test) > 0 {
|
||||||
|
tkn, literal, _, idx := parser.scan()
|
||||||
|
if len(test) > 0 {
|
||||||
|
is(tkn, test[0].(token.Token))
|
||||||
|
test = test[1:]
|
||||||
|
}
|
||||||
|
if len(test) > 0 {
|
||||||
|
is(literal, unistring.String(test[0].(string)))
|
||||||
|
test = test[1:]
|
||||||
|
}
|
||||||
|
if len(test) > 0 {
|
||||||
|
// FIXME terst, Fix this so that cast to file.Idx is not necessary?
|
||||||
|
is(idx, file.Idx(test[0].(int)))
|
||||||
|
test = test[1:]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
test("",
|
||||||
|
token.EOF, "", 1,
|
||||||
|
)
|
||||||
|
|
||||||
|
test("#!",
|
||||||
|
token.EOF, "", 3,
|
||||||
|
)
|
||||||
|
|
||||||
|
test("#!\n1",
|
||||||
|
token.NUMBER, "1", 4,
|
||||||
|
token.EOF, "", 5,
|
||||||
|
)
|
||||||
|
|
||||||
|
test("1",
|
||||||
|
token.NUMBER, "1", 1,
|
||||||
|
token.EOF, "", 2,
|
||||||
|
)
|
||||||
|
|
||||||
|
test(".0",
|
||||||
|
token.NUMBER, ".0", 1,
|
||||||
|
token.EOF, "", 3,
|
||||||
|
)
|
||||||
|
|
||||||
|
test("abc",
|
||||||
|
token.IDENTIFIER, "abc", 1,
|
||||||
|
token.EOF, "", 4,
|
||||||
|
)
|
||||||
|
|
||||||
|
test("abc(1)",
|
||||||
|
token.IDENTIFIER, "abc", 1,
|
||||||
|
token.LEFT_PARENTHESIS, "", 4,
|
||||||
|
token.NUMBER, "1", 5,
|
||||||
|
token.RIGHT_PARENTHESIS, "", 6,
|
||||||
|
token.EOF, "", 7,
|
||||||
|
)
|
||||||
|
|
||||||
|
test(".",
|
||||||
|
token.PERIOD, "", 1,
|
||||||
|
token.EOF, "", 2,
|
||||||
|
)
|
||||||
|
|
||||||
|
test("===.",
|
||||||
|
token.STRICT_EQUAL, "", 1,
|
||||||
|
token.PERIOD, "", 4,
|
||||||
|
token.EOF, "", 5,
|
||||||
|
)
|
||||||
|
|
||||||
|
test(">>>=.0",
|
||||||
|
token.UNSIGNED_SHIFT_RIGHT_ASSIGN, "", 1,
|
||||||
|
token.NUMBER, ".0", 5,
|
||||||
|
token.EOF, "", 7,
|
||||||
|
)
|
||||||
|
|
||||||
|
test(">>>=0.0.",
|
||||||
|
token.UNSIGNED_SHIFT_RIGHT_ASSIGN, "", 1,
|
||||||
|
token.NUMBER, "0.0", 5,
|
||||||
|
token.PERIOD, "", 8,
|
||||||
|
token.EOF, "", 9,
|
||||||
|
)
|
||||||
|
|
||||||
|
test("\"abc\"",
|
||||||
|
token.STRING, "\"abc\"", 1,
|
||||||
|
token.EOF, "", 6,
|
||||||
|
)
|
||||||
|
|
||||||
|
test("abc = //",
|
||||||
|
token.IDENTIFIER, "abc", 1,
|
||||||
|
token.ASSIGN, "", 5,
|
||||||
|
token.EOF, "", 9,
|
||||||
|
)
|
||||||
|
|
||||||
|
test("abc = 1 / 2",
|
||||||
|
token.IDENTIFIER, "abc", 1,
|
||||||
|
token.ASSIGN, "", 5,
|
||||||
|
token.NUMBER, "1", 7,
|
||||||
|
token.SLASH, "", 9,
|
||||||
|
token.NUMBER, "2", 11,
|
||||||
|
token.EOF, "", 12,
|
||||||
|
)
|
||||||
|
|
||||||
|
test("xyzzy = 'Nothing happens.'",
|
||||||
|
token.IDENTIFIER, "xyzzy", 1,
|
||||||
|
token.ASSIGN, "", 7,
|
||||||
|
token.STRING, "'Nothing happens.'", 9,
|
||||||
|
token.EOF, "", 27,
|
||||||
|
)
|
||||||
|
|
||||||
|
test("abc = !false",
|
||||||
|
token.IDENTIFIER, "abc", 1,
|
||||||
|
token.ASSIGN, "", 5,
|
||||||
|
token.NOT, "", 7,
|
||||||
|
token.BOOLEAN, "false", 8,
|
||||||
|
token.EOF, "", 13,
|
||||||
|
)
|
||||||
|
|
||||||
|
test("abc = !!true",
|
||||||
|
token.IDENTIFIER, "abc", 1,
|
||||||
|
token.ASSIGN, "", 5,
|
||||||
|
token.NOT, "", 7,
|
||||||
|
token.NOT, "", 8,
|
||||||
|
token.BOOLEAN, "true", 9,
|
||||||
|
token.EOF, "", 13,
|
||||||
|
)
|
||||||
|
|
||||||
|
test("abc *= 1",
|
||||||
|
token.IDENTIFIER, "abc", 1,
|
||||||
|
token.MULTIPLY_ASSIGN, "", 5,
|
||||||
|
token.NUMBER, "1", 8,
|
||||||
|
token.EOF, "", 9,
|
||||||
|
)
|
||||||
|
|
||||||
|
test("if 1 else",
|
||||||
|
token.IF, "if", 1,
|
||||||
|
token.NUMBER, "1", 4,
|
||||||
|
token.ELSE, "else", 6,
|
||||||
|
token.EOF, "", 10,
|
||||||
|
)
|
||||||
|
|
||||||
|
test("null",
|
||||||
|
token.NULL, "null", 1,
|
||||||
|
token.EOF, "", 5,
|
||||||
|
)
|
||||||
|
|
||||||
|
test(`"\u007a\x79\u000a\x78"`,
|
||||||
|
token.STRING, "\"\\u007a\\x79\\u000a\\x78\"", 1,
|
||||||
|
token.EOF, "", 23,
|
||||||
|
)
|
||||||
|
|
||||||
|
test(`"[First line \
|
||||||
|
Second line \
|
||||||
|
Third line\
|
||||||
|
. ]"
|
||||||
|
`,
|
||||||
|
token.STRING, "\"[First line \\\nSecond line \\\n Third line\\\n. ]\"", 1,
|
||||||
|
token.EOF, "", 53,
|
||||||
|
)
|
||||||
|
|
||||||
|
test("/",
|
||||||
|
token.SLASH, "", 1,
|
||||||
|
token.EOF, "", 2,
|
||||||
|
)
|
||||||
|
|
||||||
|
test("var abc = \"abc\uFFFFabc\"",
|
||||||
|
token.VAR, "var", 1,
|
||||||
|
token.IDENTIFIER, "abc", 5,
|
||||||
|
token.ASSIGN, "", 9,
|
||||||
|
token.STRING, "\"abc\uFFFFabc\"", 11,
|
||||||
|
token.EOF, "", 22,
|
||||||
|
)
|
||||||
|
|
||||||
|
test(`'\t' === '\r'`,
|
||||||
|
token.STRING, "'\\t'", 1,
|
||||||
|
token.STRICT_EQUAL, "", 6,
|
||||||
|
token.STRING, "'\\r'", 10,
|
||||||
|
token.EOF, "", 14,
|
||||||
|
)
|
||||||
|
|
||||||
|
test(`var \u0024 = 1`,
|
||||||
|
token.VAR, "var", 1,
|
||||||
|
token.IDENTIFIER, "\\u0024", 5,
|
||||||
|
token.ASSIGN, "", 12,
|
||||||
|
token.NUMBER, "1", 14,
|
||||||
|
token.EOF, "", 15,
|
||||||
|
)
|
||||||
|
|
||||||
|
test("10e10000",
|
||||||
|
token.NUMBER, "10e10000", 1,
|
||||||
|
token.EOF, "", 9,
|
||||||
|
)
|
||||||
|
|
||||||
|
test(`var if var class`,
|
||||||
|
token.VAR, "var", 1,
|
||||||
|
token.IF, "if", 5,
|
||||||
|
token.VAR, "var", 8,
|
||||||
|
token.CLASS, "class", 12,
|
||||||
|
token.EOF, "", 17,
|
||||||
|
)
|
||||||
|
|
||||||
|
test(`-0`,
|
||||||
|
token.MINUS, "", 1,
|
||||||
|
token.NUMBER, "0", 2,
|
||||||
|
token.EOF, "", 3,
|
||||||
|
)
|
||||||
|
|
||||||
|
test(`.01`,
|
||||||
|
token.NUMBER, ".01", 1,
|
||||||
|
token.EOF, "", 4,
|
||||||
|
)
|
||||||
|
|
||||||
|
test(`.01e+2`,
|
||||||
|
token.NUMBER, ".01e+2", 1,
|
||||||
|
token.EOF, "", 7,
|
||||||
|
)
|
||||||
|
|
||||||
|
test(";",
|
||||||
|
token.SEMICOLON, "", 1,
|
||||||
|
token.EOF, "", 2,
|
||||||
|
)
|
||||||
|
|
||||||
|
test(";;",
|
||||||
|
token.SEMICOLON, "", 1,
|
||||||
|
token.SEMICOLON, "", 2,
|
||||||
|
token.EOF, "", 3,
|
||||||
|
)
|
||||||
|
|
||||||
|
test("//",
|
||||||
|
token.EOF, "", 3,
|
||||||
|
)
|
||||||
|
|
||||||
|
test(";;//",
|
||||||
|
token.SEMICOLON, "", 1,
|
||||||
|
token.SEMICOLON, "", 2,
|
||||||
|
token.EOF, "", 5,
|
||||||
|
)
|
||||||
|
|
||||||
|
test("1",
|
||||||
|
token.NUMBER, "1", 1,
|
||||||
|
)
|
||||||
|
|
||||||
|
test("12 123",
|
||||||
|
token.NUMBER, "12", 1,
|
||||||
|
token.NUMBER, "123", 4,
|
||||||
|
)
|
||||||
|
|
||||||
|
test("1.2 12.3",
|
||||||
|
token.NUMBER, "1.2", 1,
|
||||||
|
token.NUMBER, "12.3", 5,
|
||||||
|
)
|
||||||
|
|
||||||
|
test("1_000 1_000_000",
|
||||||
|
token.NUMBER, "1_000", 1,
|
||||||
|
token.NUMBER, "1_000_000", 7,
|
||||||
|
)
|
||||||
|
|
||||||
|
test(`1n`,
|
||||||
|
token.NUMBER, "1n", 1,
|
||||||
|
)
|
||||||
|
|
||||||
|
test(`1n 9007199254740991n`,
|
||||||
|
token.NUMBER, "1n", 1,
|
||||||
|
token.NUMBER, "9007199254740991n", 4,
|
||||||
|
)
|
||||||
|
|
||||||
|
test(`0xabn`,
|
||||||
|
token.NUMBER, "0xabn", 1,
|
||||||
|
)
|
||||||
|
|
||||||
|
test(`0xabcdef0123456789abcdef0123n`,
|
||||||
|
token.NUMBER, "0xabcdef0123456789abcdef0123n", 1,
|
||||||
|
)
|
||||||
|
|
||||||
|
test("/ /=",
|
||||||
|
token.SLASH, "", 1,
|
||||||
|
token.QUOTIENT_ASSIGN, "", 3,
|
||||||
|
)
|
||||||
|
|
||||||
|
test(`"abc"`,
|
||||||
|
token.STRING, `"abc"`, 1,
|
||||||
|
)
|
||||||
|
|
||||||
|
test(`'abc'`,
|
||||||
|
token.STRING, `'abc'`, 1,
|
||||||
|
)
|
||||||
|
|
||||||
|
test("++",
|
||||||
|
token.INCREMENT, "", 1,
|
||||||
|
)
|
||||||
|
|
||||||
|
test(">",
|
||||||
|
token.GREATER, "", 1,
|
||||||
|
)
|
||||||
|
|
||||||
|
test(">=",
|
||||||
|
token.GREATER_OR_EQUAL, "", 1,
|
||||||
|
)
|
||||||
|
|
||||||
|
test(">>",
|
||||||
|
token.SHIFT_RIGHT, "", 1,
|
||||||
|
)
|
||||||
|
|
||||||
|
test(">>=",
|
||||||
|
token.SHIFT_RIGHT_ASSIGN, "", 1,
|
||||||
|
)
|
||||||
|
|
||||||
|
test(">>>",
|
||||||
|
token.UNSIGNED_SHIFT_RIGHT, "", 1,
|
||||||
|
)
|
||||||
|
|
||||||
|
test(">>>=",
|
||||||
|
token.UNSIGNED_SHIFT_RIGHT_ASSIGN, "", 1,
|
||||||
|
)
|
||||||
|
|
||||||
|
test("1 \"abc\"",
|
||||||
|
token.NUMBER, "1", 1,
|
||||||
|
token.STRING, "\"abc\"", 3,
|
||||||
|
)
|
||||||
|
|
||||||
|
test(",",
|
||||||
|
token.COMMA, "", 1,
|
||||||
|
)
|
||||||
|
|
||||||
|
test("1, \"abc\"",
|
||||||
|
token.NUMBER, "1", 1,
|
||||||
|
token.COMMA, "", 2,
|
||||||
|
token.STRING, "\"abc\"", 4,
|
||||||
|
)
|
||||||
|
|
||||||
|
test("new abc(1, 3.14159);",
|
||||||
|
token.NEW, "new", 1,
|
||||||
|
token.IDENTIFIER, "abc", 5,
|
||||||
|
token.LEFT_PARENTHESIS, "", 8,
|
||||||
|
token.NUMBER, "1", 9,
|
||||||
|
token.COMMA, "", 10,
|
||||||
|
token.NUMBER, "3.14159", 12,
|
||||||
|
token.RIGHT_PARENTHESIS, "", 19,
|
||||||
|
token.SEMICOLON, "", 20,
|
||||||
|
)
|
||||||
|
|
||||||
|
test("1 == \"1\"",
|
||||||
|
token.NUMBER, "1", 1,
|
||||||
|
token.EQUAL, "", 3,
|
||||||
|
token.STRING, "\"1\"", 6,
|
||||||
|
)
|
||||||
|
|
||||||
|
test("1\n[]\n",
|
||||||
|
token.NUMBER, "1", 1,
|
||||||
|
token.LEFT_BRACKET, "", 3,
|
||||||
|
token.RIGHT_BRACKET, "", 4,
|
||||||
|
)
|
||||||
|
|
||||||
|
test("1\ufeff[]\ufeff",
|
||||||
|
token.NUMBER, "1", 1,
|
||||||
|
token.LEFT_BRACKET, "", 5,
|
||||||
|
token.RIGHT_BRACKET, "", 6,
|
||||||
|
)
|
||||||
|
|
||||||
|
test("x ?.30 : false",
|
||||||
|
token.IDENTIFIER, "x", 1,
|
||||||
|
token.QUESTION_MARK, "", 3,
|
||||||
|
token.NUMBER, ".30", 4,
|
||||||
|
token.COLON, "", 8,
|
||||||
|
token.BOOLEAN, "false", 10,
|
||||||
|
)
|
||||||
|
|
||||||
|
test("a\n?.b",
|
||||||
|
token.IDENTIFIER, "a", 1,
|
||||||
|
token.QUESTION_DOT, "", 3,
|
||||||
|
token.IDENTIFIER, "b", 5,
|
||||||
|
)
|
||||||
|
|
||||||
|
// ILLEGAL
|
||||||
|
|
||||||
|
test(`3ea`,
|
||||||
|
token.ILLEGAL, "3e", 1,
|
||||||
|
token.IDENTIFIER, "a", 3,
|
||||||
|
token.EOF, "", 4,
|
||||||
|
)
|
||||||
|
|
||||||
|
test(`3in`,
|
||||||
|
token.ILLEGAL, "3", 1,
|
||||||
|
token.IN, "in", 2,
|
||||||
|
token.EOF, "", 4,
|
||||||
|
)
|
||||||
|
|
||||||
|
test("\"Hello\nWorld\"",
|
||||||
|
token.ILLEGAL, "", 1,
|
||||||
|
token.IDENTIFIER, "World", 8,
|
||||||
|
token.ILLEGAL, "", 13,
|
||||||
|
token.EOF, "", 14,
|
||||||
|
)
|
||||||
|
|
||||||
|
test("\u203f = 10",
|
||||||
|
token.ILLEGAL, "", 1,
|
||||||
|
token.ASSIGN, "", 5,
|
||||||
|
token.NUMBER, "10", 7,
|
||||||
|
token.EOF, "", 9,
|
||||||
|
)
|
||||||
|
|
||||||
|
test(`"\x0G"`,
|
||||||
|
token.ILLEGAL, "\"\\x0G\"", 1,
|
||||||
|
//token.STRING, "\"\\x0G\"", 1,
|
||||||
|
token.EOF, "", 7,
|
||||||
|
)
|
||||||
|
|
||||||
|
})
|
||||||
|
}
|
||||||
890
goja/parser/marshal_test.go
Normal file
890
goja/parser/marshal_test.go
Normal file
@ -0,0 +1,890 @@
|
|||||||
|
package parser
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"reflect"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"apigo.cc/gojs/goja/ast"
|
||||||
|
)
|
||||||
|
|
||||||
|
func marshal(name string, children ...interface{}) interface{} {
|
||||||
|
if len(children) == 1 {
|
||||||
|
if name == "" {
|
||||||
|
return testMarshalNode(children[0])
|
||||||
|
}
|
||||||
|
return map[string]interface{}{
|
||||||
|
name: children[0],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
map_ := map[string]interface{}{}
|
||||||
|
length := len(children) / 2
|
||||||
|
for i := 0; i < length; i++ {
|
||||||
|
name := children[i*2].(string)
|
||||||
|
value := children[i*2+1]
|
||||||
|
map_[name] = value
|
||||||
|
}
|
||||||
|
if name == "" {
|
||||||
|
return map_
|
||||||
|
}
|
||||||
|
return map[string]interface{}{
|
||||||
|
name: map_,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testMarshalNode(node interface{}) interface{} {
|
||||||
|
switch node := node.(type) {
|
||||||
|
|
||||||
|
// Expression
|
||||||
|
|
||||||
|
case *ast.ArrayLiteral:
|
||||||
|
return marshal("Array", testMarshalNode(node.Value))
|
||||||
|
|
||||||
|
case *ast.AssignExpression:
|
||||||
|
return marshal("Assign",
|
||||||
|
"Left", testMarshalNode(node.Left),
|
||||||
|
"Right", testMarshalNode(node.Right),
|
||||||
|
)
|
||||||
|
|
||||||
|
case *ast.BinaryExpression:
|
||||||
|
return marshal("BinaryExpression",
|
||||||
|
"Operator", node.Operator.String(),
|
||||||
|
"Left", testMarshalNode(node.Left),
|
||||||
|
"Right", testMarshalNode(node.Right),
|
||||||
|
)
|
||||||
|
|
||||||
|
case *ast.BooleanLiteral:
|
||||||
|
return marshal("Literal", node.Value)
|
||||||
|
|
||||||
|
case *ast.CallExpression:
|
||||||
|
return marshal("Call",
|
||||||
|
"Callee", testMarshalNode(node.Callee),
|
||||||
|
"ArgumentList", testMarshalNode(node.ArgumentList),
|
||||||
|
)
|
||||||
|
|
||||||
|
case *ast.ConditionalExpression:
|
||||||
|
return marshal("Conditional",
|
||||||
|
"Test", testMarshalNode(node.Test),
|
||||||
|
"Consequent", testMarshalNode(node.Consequent),
|
||||||
|
"Alternate", testMarshalNode(node.Alternate),
|
||||||
|
)
|
||||||
|
|
||||||
|
case *ast.DotExpression:
|
||||||
|
return marshal("Dot",
|
||||||
|
"Left", testMarshalNode(node.Left),
|
||||||
|
"Member", node.Identifier.Name,
|
||||||
|
)
|
||||||
|
|
||||||
|
case *ast.NewExpression:
|
||||||
|
return marshal("New",
|
||||||
|
"Callee", testMarshalNode(node.Callee),
|
||||||
|
"ArgumentList", testMarshalNode(node.ArgumentList),
|
||||||
|
)
|
||||||
|
|
||||||
|
case *ast.NullLiteral:
|
||||||
|
return marshal("Literal", nil)
|
||||||
|
|
||||||
|
case *ast.NumberLiteral:
|
||||||
|
return marshal("Literal", node.Value)
|
||||||
|
|
||||||
|
case *ast.ObjectLiteral:
|
||||||
|
return marshal("Object", testMarshalNode(node.Value))
|
||||||
|
|
||||||
|
case *ast.RegExpLiteral:
|
||||||
|
return marshal("Literal", node.Literal)
|
||||||
|
|
||||||
|
case *ast.StringLiteral:
|
||||||
|
return marshal("Literal", node.Literal)
|
||||||
|
|
||||||
|
case *ast.Binding:
|
||||||
|
return marshal("Binding", "Target", testMarshalNode(node.Target),
|
||||||
|
"Initializer", testMarshalNode(node.Initializer))
|
||||||
|
|
||||||
|
// Statement
|
||||||
|
|
||||||
|
case *ast.Program:
|
||||||
|
return testMarshalNode(node.Body)
|
||||||
|
|
||||||
|
case *ast.BlockStatement:
|
||||||
|
return marshal("BlockStatement", testMarshalNode(node.List))
|
||||||
|
|
||||||
|
case *ast.EmptyStatement:
|
||||||
|
return "EmptyStatement"
|
||||||
|
|
||||||
|
case *ast.ExpressionStatement:
|
||||||
|
return testMarshalNode(node.Expression)
|
||||||
|
|
||||||
|
case *ast.ForInStatement:
|
||||||
|
return marshal("ForIn",
|
||||||
|
"Into", testMarshalNode(node.Into),
|
||||||
|
"Source", marshal("", node.Source),
|
||||||
|
"Body", marshal("", node.Body),
|
||||||
|
)
|
||||||
|
|
||||||
|
case *ast.FunctionLiteral:
|
||||||
|
return marshal("Function", testMarshalNode(node.Body))
|
||||||
|
|
||||||
|
case *ast.Identifier:
|
||||||
|
return marshal("Identifier", node.Name)
|
||||||
|
|
||||||
|
case *ast.IfStatement:
|
||||||
|
if_ := marshal("",
|
||||||
|
"Test", testMarshalNode(node.Test),
|
||||||
|
"Consequent", testMarshalNode(node.Consequent),
|
||||||
|
).(map[string]interface{})
|
||||||
|
if node.Alternate != nil {
|
||||||
|
if_["Alternate"] = testMarshalNode(node.Alternate)
|
||||||
|
}
|
||||||
|
return marshal("If", if_)
|
||||||
|
|
||||||
|
case *ast.LabelledStatement:
|
||||||
|
return marshal("Label",
|
||||||
|
"Name", node.Label.Name,
|
||||||
|
"Statement", testMarshalNode(node.Statement),
|
||||||
|
)
|
||||||
|
case *ast.PropertyKeyed:
|
||||||
|
return marshal("",
|
||||||
|
"Key", node.Key,
|
||||||
|
"Value", testMarshalNode(node.Value),
|
||||||
|
)
|
||||||
|
|
||||||
|
case *ast.ReturnStatement:
|
||||||
|
return marshal("Return", testMarshalNode(node.Argument))
|
||||||
|
|
||||||
|
case *ast.SequenceExpression:
|
||||||
|
return marshal("Sequence", testMarshalNode(node.Sequence))
|
||||||
|
|
||||||
|
case *ast.ThrowStatement:
|
||||||
|
return marshal("Throw", testMarshalNode(node.Argument))
|
||||||
|
|
||||||
|
case *ast.VariableStatement:
|
||||||
|
return marshal("Var", testMarshalNode(node.List))
|
||||||
|
|
||||||
|
// Special
|
||||||
|
case *ast.ForDeclaration:
|
||||||
|
return marshal("For-Into-Decl", testMarshalNode(node.Target))
|
||||||
|
|
||||||
|
case *ast.ForIntoVar:
|
||||||
|
return marshal("For-Into-Var", testMarshalNode(node.Binding))
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
value := reflect.ValueOf(node)
|
||||||
|
if value.Kind() == reflect.Slice {
|
||||||
|
tmp0 := []interface{}{}
|
||||||
|
for index := 0; index < value.Len(); index++ {
|
||||||
|
tmp0 = append(tmp0, testMarshalNode(value.Index(index).Interface()))
|
||||||
|
}
|
||||||
|
return tmp0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func testMarshal(node interface{}) string {
|
||||||
|
value, err := json.Marshal(testMarshalNode(node))
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return string(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParserAST(t *testing.T) {
|
||||||
|
tt(t, func() {
|
||||||
|
|
||||||
|
test := func(inputOutput string) {
|
||||||
|
match := matchBeforeAfterSeparator.FindStringIndex(inputOutput)
|
||||||
|
input := strings.TrimSpace(inputOutput[0:match[0]])
|
||||||
|
wantOutput := strings.TrimSpace(inputOutput[match[1]:])
|
||||||
|
_, program, err := testParse(input)
|
||||||
|
is(err, nil)
|
||||||
|
haveOutput := testMarshal(program)
|
||||||
|
tmp0, tmp1 := bytes.Buffer{}, bytes.Buffer{}
|
||||||
|
json.Indent(&tmp0, []byte(haveOutput), "\t\t", " ")
|
||||||
|
json.Indent(&tmp1, []byte(wantOutput), "\t\t", " ")
|
||||||
|
is("\n\t\t"+tmp0.String(), "\n\t\t"+tmp1.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
test(`
|
||||||
|
---
|
||||||
|
[]
|
||||||
|
`)
|
||||||
|
|
||||||
|
test(`
|
||||||
|
;
|
||||||
|
---
|
||||||
|
[
|
||||||
|
"EmptyStatement"
|
||||||
|
]
|
||||||
|
`)
|
||||||
|
|
||||||
|
test(`
|
||||||
|
;;;
|
||||||
|
---
|
||||||
|
[
|
||||||
|
"EmptyStatement",
|
||||||
|
"EmptyStatement",
|
||||||
|
"EmptyStatement"
|
||||||
|
]
|
||||||
|
`)
|
||||||
|
|
||||||
|
test(`
|
||||||
|
1; true; abc; "abc"; null;
|
||||||
|
---
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"Literal": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Literal": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Identifier": "abc"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Literal": "\"abc\""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Literal": null
|
||||||
|
}
|
||||||
|
]
|
||||||
|
`)
|
||||||
|
|
||||||
|
test(`
|
||||||
|
{ 1; null; 3.14159; ; }
|
||||||
|
---
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"BlockStatement": [
|
||||||
|
{
|
||||||
|
"Literal": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Literal": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Literal": 3.14159
|
||||||
|
},
|
||||||
|
"EmptyStatement"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
`)
|
||||||
|
|
||||||
|
test(`
|
||||||
|
new abc();
|
||||||
|
---
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"New": {
|
||||||
|
"ArgumentList": [],
|
||||||
|
"Callee": {
|
||||||
|
"Identifier": "abc"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
`)
|
||||||
|
|
||||||
|
test(`
|
||||||
|
new abc(1, 3.14159)
|
||||||
|
---
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"New": {
|
||||||
|
"ArgumentList": [
|
||||||
|
{
|
||||||
|
"Literal": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Literal": 3.14159
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"Callee": {
|
||||||
|
"Identifier": "abc"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
`)
|
||||||
|
|
||||||
|
test(`
|
||||||
|
true ? false : true
|
||||||
|
---
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"Conditional": {
|
||||||
|
"Alternate": {
|
||||||
|
"Literal": true
|
||||||
|
},
|
||||||
|
"Consequent": {
|
||||||
|
"Literal": false
|
||||||
|
},
|
||||||
|
"Test": {
|
||||||
|
"Literal": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
`)
|
||||||
|
|
||||||
|
test(`
|
||||||
|
true || false
|
||||||
|
---
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"BinaryExpression": {
|
||||||
|
"Left": {
|
||||||
|
"Literal": true
|
||||||
|
},
|
||||||
|
"Operator": "||",
|
||||||
|
"Right": {
|
||||||
|
"Literal": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
`)
|
||||||
|
|
||||||
|
test(`
|
||||||
|
0 + { abc: true }
|
||||||
|
---
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"BinaryExpression": {
|
||||||
|
"Left": {
|
||||||
|
"Literal": 0
|
||||||
|
},
|
||||||
|
"Operator": "+",
|
||||||
|
"Right": {
|
||||||
|
"Object": [
|
||||||
|
{
|
||||||
|
"Key": {
|
||||||
|
"Idx": 7,
|
||||||
|
"Literal": "abc",
|
||||||
|
"Value": "abc"
|
||||||
|
},
|
||||||
|
"Value": {
|
||||||
|
"Literal": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
`)
|
||||||
|
|
||||||
|
test(`
|
||||||
|
1 == "1"
|
||||||
|
---
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"BinaryExpression": {
|
||||||
|
"Left": {
|
||||||
|
"Literal": 1
|
||||||
|
},
|
||||||
|
"Operator": "==",
|
||||||
|
"Right": {
|
||||||
|
"Literal": "\"1\""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
`)
|
||||||
|
|
||||||
|
test(`
|
||||||
|
abc(1)
|
||||||
|
---
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"Call": {
|
||||||
|
"ArgumentList": [
|
||||||
|
{
|
||||||
|
"Literal": 1
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"Callee": {
|
||||||
|
"Identifier": "abc"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
`)
|
||||||
|
|
||||||
|
test(`
|
||||||
|
Math.pow(3, 2)
|
||||||
|
---
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"Call": {
|
||||||
|
"ArgumentList": [
|
||||||
|
{
|
||||||
|
"Literal": 3
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Literal": 2
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"Callee": {
|
||||||
|
"Dot": {
|
||||||
|
"Left": {
|
||||||
|
"Identifier": "Math"
|
||||||
|
},
|
||||||
|
"Member": "pow"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
`)
|
||||||
|
|
||||||
|
test(`
|
||||||
|
1, 2, 3
|
||||||
|
---
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"Sequence": [
|
||||||
|
{
|
||||||
|
"Literal": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Literal": 2
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Literal": 3
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
`)
|
||||||
|
|
||||||
|
test(`
|
||||||
|
/ abc /gim;
|
||||||
|
---
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"Literal": "/ abc /gim"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
`)
|
||||||
|
|
||||||
|
test(`
|
||||||
|
if (0)
|
||||||
|
1;
|
||||||
|
---
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"If": {
|
||||||
|
"Consequent": {
|
||||||
|
"Literal": 1
|
||||||
|
},
|
||||||
|
"Test": {
|
||||||
|
"Literal": 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
`)
|
||||||
|
|
||||||
|
test(`
|
||||||
|
0+function(){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
---
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"BinaryExpression": {
|
||||||
|
"Left": {
|
||||||
|
"Literal": 0
|
||||||
|
},
|
||||||
|
"Operator": "+",
|
||||||
|
"Right": {
|
||||||
|
"Function": {
|
||||||
|
"BlockStatement": [
|
||||||
|
{
|
||||||
|
"Return": null
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
`)
|
||||||
|
|
||||||
|
test(`
|
||||||
|
xyzzy // Ignore it
|
||||||
|
// Ignore this
|
||||||
|
// And this
|
||||||
|
/* And all..
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
... of this!
|
||||||
|
*/
|
||||||
|
"Nothing happens."
|
||||||
|
// And finally this
|
||||||
|
---
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"Identifier": "xyzzy"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Literal": "\"Nothing happens.\""
|
||||||
|
}
|
||||||
|
]
|
||||||
|
`)
|
||||||
|
|
||||||
|
test(`
|
||||||
|
((x & (x = 1)) !== 0)
|
||||||
|
---
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"BinaryExpression": {
|
||||||
|
"Left": {
|
||||||
|
"BinaryExpression": {
|
||||||
|
"Left": {
|
||||||
|
"Identifier": "x"
|
||||||
|
},
|
||||||
|
"Operator": "\u0026",
|
||||||
|
"Right": {
|
||||||
|
"Assign": {
|
||||||
|
"Left": {
|
||||||
|
"Identifier": "x"
|
||||||
|
},
|
||||||
|
"Right": {
|
||||||
|
"Literal": 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Operator": "!==",
|
||||||
|
"Right": {
|
||||||
|
"Literal": 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
`)
|
||||||
|
|
||||||
|
test(`
|
||||||
|
{ abc: 'def' }
|
||||||
|
---
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"BlockStatement": [
|
||||||
|
{
|
||||||
|
"Label": {
|
||||||
|
"Name": "abc",
|
||||||
|
"Statement": {
|
||||||
|
"Literal": "'def'"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
`)
|
||||||
|
|
||||||
|
test(`
|
||||||
|
// This is not an object, this is a string literal with a label!
|
||||||
|
({ abc: 'def' })
|
||||||
|
---
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"Object": [
|
||||||
|
{
|
||||||
|
"Key": {
|
||||||
|
"Idx": 77,
|
||||||
|
"Literal": "abc",
|
||||||
|
"Value": "abc"
|
||||||
|
},
|
||||||
|
"Value": {
|
||||||
|
"Literal": "'def'"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
`)
|
||||||
|
|
||||||
|
test(`
|
||||||
|
[,]
|
||||||
|
---
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"Array": [
|
||||||
|
null
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
`)
|
||||||
|
|
||||||
|
test(`
|
||||||
|
[,,]
|
||||||
|
---
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"Array": [
|
||||||
|
null,
|
||||||
|
null
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
`)
|
||||||
|
|
||||||
|
test(`
|
||||||
|
({ get abc() {} })
|
||||||
|
---
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"Object": [
|
||||||
|
{
|
||||||
|
"Key": {
|
||||||
|
"Idx": 8,
|
||||||
|
"Literal": "abc",
|
||||||
|
"Value": "abc"
|
||||||
|
},
|
||||||
|
"Value": {
|
||||||
|
"Function": {
|
||||||
|
"BlockStatement": []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
`)
|
||||||
|
|
||||||
|
test(`
|
||||||
|
/abc/.source
|
||||||
|
---
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"Dot": {
|
||||||
|
"Left": {
|
||||||
|
"Literal": "/abc/"
|
||||||
|
},
|
||||||
|
"Member": "source"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
`)
|
||||||
|
|
||||||
|
test(`
|
||||||
|
xyzzy
|
||||||
|
|
||||||
|
throw new TypeError("Nothing happens.")
|
||||||
|
---
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"Identifier": "xyzzy"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Throw": {
|
||||||
|
"New": {
|
||||||
|
"ArgumentList": [
|
||||||
|
{
|
||||||
|
"Literal": "\"Nothing happens.\""
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"Callee": {
|
||||||
|
"Identifier": "TypeError"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
`)
|
||||||
|
|
||||||
|
// When run, this will call a type error to be thrown
|
||||||
|
// This is essentially the same as:
|
||||||
|
//
|
||||||
|
// var abc = 1(function(){})()
|
||||||
|
//
|
||||||
|
test(`
|
||||||
|
var abc = 1
|
||||||
|
(function(){
|
||||||
|
})()
|
||||||
|
---
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"Var": [
|
||||||
|
{
|
||||||
|
"Binding": {
|
||||||
|
"Initializer": {
|
||||||
|
"Call": {
|
||||||
|
"ArgumentList": [],
|
||||||
|
"Callee": {
|
||||||
|
"Call": {
|
||||||
|
"ArgumentList": [
|
||||||
|
{
|
||||||
|
"Function": {
|
||||||
|
"BlockStatement": []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"Callee": {
|
||||||
|
"Literal": 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Target": {
|
||||||
|
"Identifier": "abc"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
`)
|
||||||
|
|
||||||
|
test(`
|
||||||
|
"use strict"
|
||||||
|
---
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"Literal": "\"use strict\""
|
||||||
|
}
|
||||||
|
]
|
||||||
|
`)
|
||||||
|
|
||||||
|
test(`
|
||||||
|
"use strict"
|
||||||
|
abc = 1 + 2 + 11
|
||||||
|
---
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"Literal": "\"use strict\""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Assign": {
|
||||||
|
"Left": {
|
||||||
|
"Identifier": "abc"
|
||||||
|
},
|
||||||
|
"Right": {
|
||||||
|
"BinaryExpression": {
|
||||||
|
"Left": {
|
||||||
|
"BinaryExpression": {
|
||||||
|
"Left": {
|
||||||
|
"Literal": 1
|
||||||
|
},
|
||||||
|
"Operator": "+",
|
||||||
|
"Right": {
|
||||||
|
"Literal": 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Operator": "+",
|
||||||
|
"Right": {
|
||||||
|
"Literal": 11
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
`)
|
||||||
|
|
||||||
|
test(`
|
||||||
|
abc = function() { 'use strict' }
|
||||||
|
---
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"Assign": {
|
||||||
|
"Left": {
|
||||||
|
"Identifier": "abc"
|
||||||
|
},
|
||||||
|
"Right": {
|
||||||
|
"Function": {
|
||||||
|
"BlockStatement": [
|
||||||
|
{
|
||||||
|
"Literal": "'use strict'"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
`)
|
||||||
|
|
||||||
|
test(`
|
||||||
|
for (var abc in def) {
|
||||||
|
}
|
||||||
|
---
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"ForIn": {
|
||||||
|
"Body": {
|
||||||
|
"BlockStatement": []
|
||||||
|
},
|
||||||
|
"Into": {
|
||||||
|
"For-Into-Var": {
|
||||||
|
"Binding": {
|
||||||
|
"Initializer": null,
|
||||||
|
"Target": {
|
||||||
|
"Identifier": "abc"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Source": {
|
||||||
|
"Identifier": "def"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
`)
|
||||||
|
|
||||||
|
test(`
|
||||||
|
abc = {
|
||||||
|
'"': "'",
|
||||||
|
"'": '"',
|
||||||
|
}
|
||||||
|
---
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"Assign": {
|
||||||
|
"Left": {
|
||||||
|
"Identifier": "abc"
|
||||||
|
},
|
||||||
|
"Right": {
|
||||||
|
"Object": [
|
||||||
|
{
|
||||||
|
"Key": {
|
||||||
|
"Idx": 21,
|
||||||
|
"Literal": "'\"'",
|
||||||
|
"Value": "\""
|
||||||
|
},
|
||||||
|
"Value": {
|
||||||
|
"Literal": "\"'\""
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Key": {
|
||||||
|
"Idx": 43,
|
||||||
|
"Literal": "\"'\"",
|
||||||
|
"Value": "'"
|
||||||
|
},
|
||||||
|
"Value": {
|
||||||
|
"Literal": "'\"'"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
`)
|
||||||
|
|
||||||
|
})
|
||||||
|
}
|
||||||
1334
goja/parser/parser_test.go
Normal file
1334
goja/parser/parser_test.go
Normal file
File diff suppressed because it is too large
Load Diff
191
goja/parser/regexp_test.go
Normal file
191
goja/parser/regexp_test.go
Normal file
@ -0,0 +1,191 @@
|
|||||||
|
package parser
|
||||||
|
|
||||||
|
import (
|
||||||
|
"regexp"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestRegExp(t *testing.T) {
|
||||||
|
tt(t, func() {
|
||||||
|
{
|
||||||
|
// err
|
||||||
|
test := func(input string, expect interface{}) {
|
||||||
|
_, err := TransformRegExp(input, false, false)
|
||||||
|
_, incompat := err.(RegexpErrorIncompatible)
|
||||||
|
is(incompat, false)
|
||||||
|
is(err, expect)
|
||||||
|
}
|
||||||
|
|
||||||
|
test("[", "Unterminated character class")
|
||||||
|
|
||||||
|
test("(", "Unterminated group")
|
||||||
|
|
||||||
|
test("\\(?=)", "Unmatched ')'")
|
||||||
|
|
||||||
|
test(")", "Unmatched ')'")
|
||||||
|
test("0:(?)", "Invalid group")
|
||||||
|
test("(?)", "Invalid group")
|
||||||
|
test("(?U)", "Invalid group")
|
||||||
|
test("(?)|(?i)", "Invalid group")
|
||||||
|
test("(?P<w>)(?P<w>)(?P<D>)", "Invalid group")
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
// incompatible
|
||||||
|
test := func(input string, expectErr interface{}) {
|
||||||
|
_, err := TransformRegExp(input, false, false)
|
||||||
|
_, incompat := err.(RegexpErrorIncompatible)
|
||||||
|
is(incompat, true)
|
||||||
|
is(err, expectErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
test(`<%([\s\S]+?)%>`, "S in class")
|
||||||
|
|
||||||
|
test("(?<=y)x", "re2: Invalid (?<) <lookbehind>")
|
||||||
|
|
||||||
|
test(`(?!test)`, "re2: Invalid (?!) <lookahead>")
|
||||||
|
|
||||||
|
test(`\1`, "re2: Invalid \\1 <backreference>")
|
||||||
|
|
||||||
|
test(`\8`, "re2: Invalid \\8 <backreference>")
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
// err
|
||||||
|
test := func(input string, expect string) {
|
||||||
|
result, err := TransformRegExp(input, false, false)
|
||||||
|
is(err, nil)
|
||||||
|
_, incompat := err.(RegexpErrorIncompatible)
|
||||||
|
is(incompat, false)
|
||||||
|
is(result, expect)
|
||||||
|
_, err = regexp.Compile(result)
|
||||||
|
is(err, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
test("", "")
|
||||||
|
|
||||||
|
test("abc", "abc")
|
||||||
|
|
||||||
|
test(`\abc`, `abc`)
|
||||||
|
|
||||||
|
test(`\a\b\c`, `a\bc`)
|
||||||
|
|
||||||
|
test(`\x`, `x`)
|
||||||
|
|
||||||
|
test(`\c`, `c`)
|
||||||
|
|
||||||
|
test(`\cA`, `\x01`)
|
||||||
|
|
||||||
|
test(`\cz`, `\x1a`)
|
||||||
|
|
||||||
|
test(`\ca`, `\x01`)
|
||||||
|
|
||||||
|
test(`\cj`, `\x0a`)
|
||||||
|
|
||||||
|
test(`\ck`, `\x0b`)
|
||||||
|
|
||||||
|
test(`\+`, `\+`)
|
||||||
|
|
||||||
|
test(`[\b]`, `[\x08]`)
|
||||||
|
|
||||||
|
test(`\u0z01\x\undefined`, `u0z01xundefined`)
|
||||||
|
|
||||||
|
test(`\\|'|\r|\n|\t|\u2028|\u2029`, `\\|'|\r|\n|\t|\x{2028}|\x{2029}`)
|
||||||
|
|
||||||
|
test("]", "]")
|
||||||
|
|
||||||
|
test("}", "}")
|
||||||
|
|
||||||
|
test("%", "%")
|
||||||
|
|
||||||
|
test("(%)", "(%)")
|
||||||
|
|
||||||
|
test("(?:[%\\s])", "(?:[%"+WhitespaceChars+"])")
|
||||||
|
|
||||||
|
test("[[]", "[[]")
|
||||||
|
|
||||||
|
test("\\101", "\\x41")
|
||||||
|
|
||||||
|
test("\\51", "\\x29")
|
||||||
|
|
||||||
|
test("\\051", "\\x29")
|
||||||
|
|
||||||
|
test("\\175", "\\x7d")
|
||||||
|
|
||||||
|
test("\\0", "\\0")
|
||||||
|
|
||||||
|
test("\\04", "\\x04")
|
||||||
|
|
||||||
|
test(`(.)^`, "("+Re2Dot+")^")
|
||||||
|
|
||||||
|
test(`\$`, `\$`)
|
||||||
|
|
||||||
|
test(`[G-b]`, `[G-b]`)
|
||||||
|
|
||||||
|
test(`[G-b\0]`, `[G-b\0]`)
|
||||||
|
|
||||||
|
test(`\k`, `k`)
|
||||||
|
|
||||||
|
test(`\x20`, `\x20`)
|
||||||
|
|
||||||
|
test(`😊`, `😊`)
|
||||||
|
|
||||||
|
test(`^.*`, `^`+Re2Dot+`*`)
|
||||||
|
|
||||||
|
test(`(\n)`, `(\n)`)
|
||||||
|
|
||||||
|
test(`(a(bc))`, `(a(bc))`)
|
||||||
|
|
||||||
|
test(`[]`, "[^\u0000-\U0001FFFF]")
|
||||||
|
|
||||||
|
test(`[^]`, "[\u0000-\U0001FFFF]")
|
||||||
|
|
||||||
|
test(`\s+`, "["+WhitespaceChars+"]+")
|
||||||
|
|
||||||
|
test(`\S+`, "[^"+WhitespaceChars+"]+")
|
||||||
|
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTransformRegExp(t *testing.T) {
|
||||||
|
tt(t, func() {
|
||||||
|
pattern, err := TransformRegExp(`\s+abc\s+`, false, false)
|
||||||
|
is(err, nil)
|
||||||
|
is(pattern, `[`+WhitespaceChars+`]+abc[`+WhitespaceChars+`]+`)
|
||||||
|
is(regexp.MustCompile(pattern).MatchString("\t abc def"), true)
|
||||||
|
})
|
||||||
|
tt(t, func() {
|
||||||
|
pattern, err := TransformRegExp(`\u{1d306}`, false, true)
|
||||||
|
is(err, nil)
|
||||||
|
is(pattern, `\x{1d306}`)
|
||||||
|
})
|
||||||
|
tt(t, func() {
|
||||||
|
pattern, err := TransformRegExp(`\u1234`, false, false)
|
||||||
|
is(err, nil)
|
||||||
|
is(pattern, `\x{1234}`)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkTransformRegExp(b *testing.B) {
|
||||||
|
f := func(reStr string, b *testing.B) {
|
||||||
|
b.ResetTimer()
|
||||||
|
b.ReportAllocs()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
_, _ = TransformRegExp(reStr, false, false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
b.Run("Re", func(b *testing.B) {
|
||||||
|
f(`^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$`, b)
|
||||||
|
})
|
||||||
|
|
||||||
|
b.Run("Re2-1", func(b *testing.B) {
|
||||||
|
f(`(?=)^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$`, b)
|
||||||
|
})
|
||||||
|
|
||||||
|
b.Run("Re2-1", func(b *testing.B) {
|
||||||
|
f(`^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$(?=)`, b)
|
||||||
|
})
|
||||||
|
}
|
||||||
49
goja/parser/testutil_test.go
Normal file
49
goja/parser/testutil_test.go
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
package parser
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Quick and dirty replacement for terst
|
||||||
|
|
||||||
|
func tt(t *testing.T, f func()) {
|
||||||
|
defer func() {
|
||||||
|
if x := recover(); x != nil {
|
||||||
|
pcs := make([]uintptr, 16)
|
||||||
|
pcs = pcs[:runtime.Callers(1, pcs)]
|
||||||
|
frames := runtime.CallersFrames(pcs)
|
||||||
|
var file string
|
||||||
|
var line int
|
||||||
|
for {
|
||||||
|
frame, more := frames.Next()
|
||||||
|
// The line number here must match the line where f() is called (see below)
|
||||||
|
if frame.Line == 40 && filepath.Base(frame.File) == "testutil_test.go" {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if !more {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
file, line = frame.File, frame.Line
|
||||||
|
}
|
||||||
|
if line > 0 {
|
||||||
|
t.Errorf("Error at %s:%d: %v", filepath.Base(file), line, x)
|
||||||
|
} else {
|
||||||
|
t.Errorf("Error at <unknown>: %v", x)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
f()
|
||||||
|
}
|
||||||
|
|
||||||
|
func is(a, b interface{}) {
|
||||||
|
as := fmt.Sprintf("%v", a)
|
||||||
|
bs := fmt.Sprintf("%v", b)
|
||||||
|
if as != bs {
|
||||||
|
panic(fmt.Errorf("%+v(%T) != %+v(%T)", a, a, b, b))
|
||||||
|
}
|
||||||
|
}
|
||||||
102
goja/profiler_test.go
Normal file
102
goja/profiler_test.go
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
package goja
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync/atomic"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestProfiler(t *testing.T) {
|
||||||
|
|
||||||
|
err := StartProfile(nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
vm := New()
|
||||||
|
go func() {
|
||||||
|
_, err := vm.RunScript("test123.js", `
|
||||||
|
const a = 2 + 2;
|
||||||
|
function loop() {
|
||||||
|
for(;;) {}
|
||||||
|
}
|
||||||
|
loop();
|
||||||
|
`)
|
||||||
|
if err != nil {
|
||||||
|
if _, ok := err.(*InterruptedError); !ok {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
time.Sleep(200 * time.Millisecond)
|
||||||
|
|
||||||
|
atomic.StoreInt32(&globalProfiler.enabled, 0)
|
||||||
|
pr := globalProfiler.p.stop()
|
||||||
|
|
||||||
|
if len(pr.Sample) == 0 {
|
||||||
|
t.Fatal("No samples were recorded")
|
||||||
|
}
|
||||||
|
|
||||||
|
var running bool
|
||||||
|
for i := 0; i < 10; i++ {
|
||||||
|
time.Sleep(10 * time.Millisecond)
|
||||||
|
globalProfiler.p.mu.Lock()
|
||||||
|
running = globalProfiler.p.running
|
||||||
|
globalProfiler.p.mu.Unlock()
|
||||||
|
if !running {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if running {
|
||||||
|
t.Fatal("The profiler is still running")
|
||||||
|
}
|
||||||
|
vm.Interrupt(nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestProfiler1(t *testing.T) {
|
||||||
|
t.Skip("This test takes too long with race detector enabled and is non-deterministic. It's left here mostly for documentation purposes.")
|
||||||
|
|
||||||
|
err := StartProfile(nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
sleep := func() {
|
||||||
|
time.Sleep(1 * time.Second)
|
||||||
|
}
|
||||||
|
// Spawn new vms faster than once every 10ms (the profiler interval) and make sure they don't finish too soon.
|
||||||
|
// This means (*profiler).run() won't be fast enough to collect the samples, so they must be collected
|
||||||
|
// after the profiler is stopped.
|
||||||
|
for i := 0; i < 500; i++ {
|
||||||
|
go func() {
|
||||||
|
vm := New()
|
||||||
|
vm.Set("sleep", sleep)
|
||||||
|
_, err := vm.RunScript("test123.js", `
|
||||||
|
function loop() {
|
||||||
|
for (let i = 0; i < 50000; i++) {
|
||||||
|
const a = Math.pow(Math.Pi, Math.Pi);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
loop();
|
||||||
|
sleep();
|
||||||
|
`)
|
||||||
|
if err != nil {
|
||||||
|
if _, ok := err.(*InterruptedError); !ok {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
time.Sleep(1 * time.Millisecond)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
time.Sleep(500 * time.Millisecond)
|
||||||
|
atomic.StoreInt32(&globalProfiler.enabled, 0)
|
||||||
|
pr := globalProfiler.p.stop()
|
||||||
|
|
||||||
|
if len(pr.Sample) == 0 {
|
||||||
|
t.Fatal("No samples were recorded")
|
||||||
|
}
|
||||||
|
}
|
||||||
960
goja/regexp_test.go
Normal file
960
goja/regexp_test.go
Normal file
@ -0,0 +1,960 @@
|
|||||||
|
package goja
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestRegexp1(t *testing.T) {
|
||||||
|
const SCRIPT = `
|
||||||
|
var r = new RegExp("(['\"])(.*?)\\1");
|
||||||
|
var m = r.exec("'test'");
|
||||||
|
m !== null && m.length == 3 && m[2] === "test";
|
||||||
|
`
|
||||||
|
|
||||||
|
testScript(SCRIPT, valueTrue, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRegexp2(t *testing.T) {
|
||||||
|
const SCRIPT = `
|
||||||
|
var r = new RegExp("(['\"])(.*?)['\"]");
|
||||||
|
var m = r.exec("'test'");
|
||||||
|
m !== null && m.length == 3 && m[2] === "test";
|
||||||
|
`
|
||||||
|
|
||||||
|
testScript(SCRIPT, valueTrue, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRegexpLiteral(t *testing.T) {
|
||||||
|
const SCRIPT = `
|
||||||
|
var r = /(['\"])(.*?)\1/;
|
||||||
|
var m = r.exec("'test'");
|
||||||
|
m !== null && m.length == 3 && m[2] === "test";
|
||||||
|
`
|
||||||
|
|
||||||
|
testScript(SCRIPT, valueTrue, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRegexpRe2Unicode(t *testing.T) {
|
||||||
|
const SCRIPT = `
|
||||||
|
var r = /(тест)/i;
|
||||||
|
var m = r.exec("'Тест'");
|
||||||
|
m !== null && m.length == 2 && m[1] === "Тест";
|
||||||
|
`
|
||||||
|
|
||||||
|
testScript(SCRIPT, valueTrue, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRegexpRe2UnicodeTarget(t *testing.T) {
|
||||||
|
const SCRIPT = `
|
||||||
|
var r = /(['\"])(.*?)['\"]/i;
|
||||||
|
var m = r.exec("'Тест'");
|
||||||
|
m !== null && m.length == 3 && m[2] === "Тест";
|
||||||
|
`
|
||||||
|
|
||||||
|
testScript(SCRIPT, valueTrue, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRegexpRegexp2Unicode(t *testing.T) {
|
||||||
|
const SCRIPT = `
|
||||||
|
var r = /(['\"])(тест)\1/i;
|
||||||
|
var m = r.exec("'Тест'");
|
||||||
|
m !== null && m.length == 3 && m[2] === "Тест";
|
||||||
|
`
|
||||||
|
|
||||||
|
testScript(SCRIPT, valueTrue, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRegexpRegexp2UnicodeTarget(t *testing.T) {
|
||||||
|
const SCRIPT = `
|
||||||
|
var r = /(['\"])(.*?)\1/;
|
||||||
|
var m = r.exec("'Тест'");
|
||||||
|
m !== null && m.length == 3 && m[2] === "Тест";
|
||||||
|
`
|
||||||
|
|
||||||
|
testScript(SCRIPT, valueTrue, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRegexpRe2Whitespace(t *testing.T) {
|
||||||
|
const SCRIPT = `
|
||||||
|
"\u2000\u2001\u2002\u200b".replace(/\s+/g, "") === "\u200b";
|
||||||
|
`
|
||||||
|
|
||||||
|
testScript(SCRIPT, valueTrue, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRegexpRegexp2Whitespace(t *testing.T) {
|
||||||
|
const SCRIPT = `
|
||||||
|
"A\u2000\u2001\u2002A\u200b".replace(/(A)\s+\1/g, "") === "\u200b"
|
||||||
|
`
|
||||||
|
testScript(SCRIPT, valueTrue, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEmptyCharClassRe2(t *testing.T) {
|
||||||
|
const SCRIPT = `
|
||||||
|
/[]/.test("\u0000");
|
||||||
|
`
|
||||||
|
|
||||||
|
testScript(SCRIPT, valueFalse, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNegatedEmptyCharClassRe2(t *testing.T) {
|
||||||
|
const SCRIPT = `
|
||||||
|
/[^]/.test("\u0000");
|
||||||
|
`
|
||||||
|
|
||||||
|
testScript(SCRIPT, valueTrue, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEmptyCharClassRegexp2(t *testing.T) {
|
||||||
|
const SCRIPT = `
|
||||||
|
/([])\1/.test("\u0000\u0000");
|
||||||
|
`
|
||||||
|
|
||||||
|
testScript(SCRIPT, valueFalse, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRegexp2Negate(t *testing.T) {
|
||||||
|
const SCRIPT = `
|
||||||
|
/([\D1])\1/.test("aa");
|
||||||
|
`
|
||||||
|
|
||||||
|
testScript(SCRIPT, valueTrue, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAlternativeRe2(t *testing.T) {
|
||||||
|
const SCRIPT = `
|
||||||
|
/()|/.exec("") !== null;
|
||||||
|
`
|
||||||
|
|
||||||
|
testScript(SCRIPT, valueTrue, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRegexpReplaceGlobal(t *testing.T) {
|
||||||
|
const SCRIPT = `
|
||||||
|
"QBZPbage\ny_cynprubyqre".replace(/^\s*|\s*$/g, '')
|
||||||
|
`
|
||||||
|
|
||||||
|
testScript(SCRIPT, asciiString("QBZPbage\ny_cynprubyqre"), t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRegexpNumCaptures(t *testing.T) {
|
||||||
|
const SCRIPT = `
|
||||||
|
"Fubpxjnir Synfu 9.0 e115".replace(/([a-zA-Z]|\s)+/, '')
|
||||||
|
`
|
||||||
|
testScript(SCRIPT, asciiString("9.0 e115"), t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRegexpNumCaptures1(t *testing.T) {
|
||||||
|
const SCRIPT = `
|
||||||
|
"Fubpxjnir Sy\tfu 9.0 e115".replace(/^.*\s+(\S+\s+\S+$)/, '')
|
||||||
|
`
|
||||||
|
testScript(SCRIPT, asciiString(""), t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRegexpSInClass(t *testing.T) {
|
||||||
|
const SCRIPT = `
|
||||||
|
/[\S]/.test("\u2028");
|
||||||
|
`
|
||||||
|
testScript(SCRIPT, valueFalse, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRegexpDotMatchCR(t *testing.T) {
|
||||||
|
const SCRIPT = `
|
||||||
|
/./.test("\r");
|
||||||
|
`
|
||||||
|
|
||||||
|
testScript(SCRIPT, valueFalse, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRegexpDotMatchCRInGroup(t *testing.T) {
|
||||||
|
const SCRIPT = `
|
||||||
|
/(.)/.test("\r");
|
||||||
|
`
|
||||||
|
|
||||||
|
testScript(SCRIPT, valueFalse, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRegexpDotMatchLF(t *testing.T) {
|
||||||
|
const SCRIPT = `
|
||||||
|
/./.test("\n");
|
||||||
|
`
|
||||||
|
|
||||||
|
testScript(SCRIPT, valueFalse, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRegexpSplitWithBackRef(t *testing.T) {
|
||||||
|
const SCRIPT = `
|
||||||
|
"a++b+-c".split(/([+-])\1/).join(" $$ ")
|
||||||
|
`
|
||||||
|
|
||||||
|
testScript(SCRIPT, asciiString("a $$ + $$ b+-c"), t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEscapeNonASCII(t *testing.T) {
|
||||||
|
const SCRIPT = `
|
||||||
|
/\⩓/.test("⩓")
|
||||||
|
`
|
||||||
|
|
||||||
|
testScript(SCRIPT, valueTrue, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRegexpUTF16(t *testing.T) {
|
||||||
|
const SCRIPT = `
|
||||||
|
var str = "\uD800\uDC00";
|
||||||
|
|
||||||
|
assert(/\uD800/g.test(str), "#1");
|
||||||
|
assert(/\uD800/.test(str), "#2");
|
||||||
|
assert(/𐀀/.test(str), "#3");
|
||||||
|
|
||||||
|
var re = /\uD800/;
|
||||||
|
|
||||||
|
assert(compareArray(str.replace(re, "X"), ["X", "\uDC00"]), "#4");
|
||||||
|
assert(compareArray(str.split(re), ["", "\uDC00"]), "#5");
|
||||||
|
assert(compareArray("a\uD800\uDC00b".split(/\uD800/g), ["a", "\uDC00b"]), "#6");
|
||||||
|
assert(compareArray("a\uD800\uDC00b".split(/(?:)/g), ["a", "\uD800", "\uDC00", "b"]), "#7");
|
||||||
|
assert(compareArray("0\x80".split(/(0){0}/g), ["0", undefined, "\x80"]), "#7+");
|
||||||
|
|
||||||
|
re = /(?=)a/; // a hack to use regexp2
|
||||||
|
assert.sameValue(re.exec('\ud83d\ude02a').index, 2, "#8");
|
||||||
|
|
||||||
|
assert.sameValue(/./.exec('\ud83d\ude02')[0], '\ud83d', "#9");
|
||||||
|
|
||||||
|
assert(RegExp("\uD800").test("\uD800"), "#10");
|
||||||
|
|
||||||
|
var cu = 0xD800;
|
||||||
|
var xx = "a\\" + String.fromCharCode(cu);
|
||||||
|
var pattern = eval("/" + xx + "/");
|
||||||
|
assert.sameValue(pattern.source, "a\\\\\\ud800", "Code unit: " + cu.toString(16), "#11");
|
||||||
|
assert(pattern.test("a\\\uD800"), "#12");
|
||||||
|
`
|
||||||
|
|
||||||
|
testScriptWithTestLib(SCRIPT, _undefined, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRegexpUnicode(t *testing.T) {
|
||||||
|
const SCRIPT = `
|
||||||
|
|
||||||
|
assert(!/\uD800/u.test("\uD800\uDC00"), "#1");
|
||||||
|
assert(!/\uFFFD/u.test("\uD800\uDC00"), "#2");
|
||||||
|
|
||||||
|
assert(/\uD800\uDC00/u.test("\uD800\uDC00"), "#3");
|
||||||
|
|
||||||
|
assert(/\uD800/u.test("\uD800"), "#4");
|
||||||
|
|
||||||
|
assert(compareArray("a\uD800\uDC00b".split(/\uD800/gu), ["a\uD800\uDC00b"]), "#5");
|
||||||
|
|
||||||
|
assert(compareArray("a\uD800\uDC00b".split(/(?:)/gu), ["a", "𐀀", "b"]), "#6");
|
||||||
|
|
||||||
|
assert(compareArray("0\x80".split(/(0){0}/gu), ["0", undefined, "\x80"]), "#7");
|
||||||
|
|
||||||
|
var re = eval('/' + /\ud834\udf06/u.source + '/u');
|
||||||
|
assert(re.test('\ud834\udf06'), "#9");
|
||||||
|
|
||||||
|
/*re = RegExp("\\p{L}", "u");
|
||||||
|
if (!re.test("A")) {
|
||||||
|
throw new Error("Test 9 failed");
|
||||||
|
}*/
|
||||||
|
`
|
||||||
|
|
||||||
|
testScriptWithTestLib(SCRIPT, _undefined, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestConvertRegexpToUnicode(t *testing.T) {
|
||||||
|
if s := convertRegexpToUnicode(`test\uD800\u0C00passed`); s != `test\uD800\u0C00passed` {
|
||||||
|
t.Fatal(s)
|
||||||
|
}
|
||||||
|
if s := convertRegexpToUnicode(`test\uD800\uDC00passed`); s != `test𐀀passed` {
|
||||||
|
t.Fatal(s)
|
||||||
|
}
|
||||||
|
if s := convertRegexpToUnicode(`test\u0023passed`); s != `test\u0023passed` {
|
||||||
|
t.Fatal(s)
|
||||||
|
}
|
||||||
|
if s := convertRegexpToUnicode(`test\u0passed`); s != `test\u0passed` {
|
||||||
|
t.Fatal(s)
|
||||||
|
}
|
||||||
|
if s := convertRegexpToUnicode(`test\uD800passed`); s != `test\uD800passed` {
|
||||||
|
t.Fatal(s)
|
||||||
|
}
|
||||||
|
if s := convertRegexpToUnicode(`test\uD800`); s != `test\uD800` {
|
||||||
|
t.Fatal(s)
|
||||||
|
}
|
||||||
|
if s := convertRegexpToUnicode(`test\uD80`); s != `test\uD80` {
|
||||||
|
t.Fatal(s)
|
||||||
|
}
|
||||||
|
if s := convertRegexpToUnicode(`\\uD800\uDC00passed`); s != `\\uD800\uDC00passed` {
|
||||||
|
t.Fatal(s)
|
||||||
|
}
|
||||||
|
if s := convertRegexpToUnicode(`testpassed`); s != `testpassed` {
|
||||||
|
t.Fatal(s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestConvertRegexpToUtf16(t *testing.T) {
|
||||||
|
if s := convertRegexpToUtf16(`𐀀`); s != `\ud800\udc00` {
|
||||||
|
t.Fatal(s)
|
||||||
|
}
|
||||||
|
if s := convertRegexpToUtf16(`\𐀀`); s != `\\\ud800\udc00` {
|
||||||
|
t.Fatal(s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEscapeInvalidUtf16(t *testing.T) {
|
||||||
|
if s := escapeInvalidUtf16(asciiString("test")); s != "test" {
|
||||||
|
t.Fatal(s)
|
||||||
|
}
|
||||||
|
if s := escapeInvalidUtf16(newStringValue("test\U00010000")); s != "test\U00010000" {
|
||||||
|
t.Fatal(s)
|
||||||
|
}
|
||||||
|
if s := escapeInvalidUtf16(unicodeStringFromRunes([]rune{'t', 0xD800})); s != "t\\ud800" {
|
||||||
|
t.Fatal(s)
|
||||||
|
}
|
||||||
|
if s := escapeInvalidUtf16(unicodeStringFromRunes([]rune{'t', 0xD800, 'p'})); s != "t\\ud800p" {
|
||||||
|
t.Fatal(s)
|
||||||
|
}
|
||||||
|
if s := escapeInvalidUtf16(unicodeStringFromRunes([]rune{0xD800, 'p'})); s != "\\ud800p" {
|
||||||
|
t.Fatal(s)
|
||||||
|
}
|
||||||
|
if s := escapeInvalidUtf16(unicodeStringFromRunes([]rune{'t', '\\', 0xD800, 'p'})); s != `t\\\ud800p` {
|
||||||
|
t.Fatal(s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRegexpAssertion(t *testing.T) {
|
||||||
|
const SCRIPT = `
|
||||||
|
var res = 'aaa'.match(/^a/g);
|
||||||
|
res.length === 1 || res[0] === 'a';
|
||||||
|
`
|
||||||
|
testScript(SCRIPT, valueTrue, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRegexpUnicodeAdvanceStringIndex(t *testing.T) {
|
||||||
|
const SCRIPT = `
|
||||||
|
// deoptimise RegExp
|
||||||
|
var origExec = RegExp.prototype.exec;
|
||||||
|
RegExp.prototype.exec = function(s) {
|
||||||
|
return origExec.call(this, s);
|
||||||
|
};
|
||||||
|
|
||||||
|
var re = /(?:)/gu;
|
||||||
|
var str = "a\uD800\uDC00b";
|
||||||
|
assert(compareArray(str.split(re), ["a", "𐀀", "b"]), "#1");
|
||||||
|
|
||||||
|
re.lastIndex = 3;
|
||||||
|
assert.sameValue(re.exec(str).index, 3, "#2");
|
||||||
|
|
||||||
|
re.lastIndex = 2;
|
||||||
|
assert.sameValue(re.exec(str).index, 1, "#3");
|
||||||
|
|
||||||
|
re.lastIndex = 4;
|
||||||
|
assert.sameValue(re.exec(str).index, 4, "#4");
|
||||||
|
|
||||||
|
re.lastIndex = 5;
|
||||||
|
assert.sameValue(re.exec(str), null, "#5");
|
||||||
|
|
||||||
|
var iterator = str.matchAll(re); // regexp is copied by matchAll, but resets lastIndex
|
||||||
|
var matches = [];
|
||||||
|
for (var v of iterator) {matches.push(v);}
|
||||||
|
assert.sameValue(matches.length, 4, "#6");
|
||||||
|
assert.sameValue(matches[0].index, 0, "#7 index");
|
||||||
|
assert.sameValue(matches[0][0], "", "#7 value");
|
||||||
|
assert.sameValue(matches[1].index, 1, "#8 index");
|
||||||
|
assert.sameValue(matches[1][0], "", "#8 value");
|
||||||
|
assert.sameValue(matches[2].index, 3, "#9 index");
|
||||||
|
assert.sameValue(matches[2][0], "", "#9 value");
|
||||||
|
assert.sameValue(matches[3].index, 4, "#10 index");
|
||||||
|
assert.sameValue(matches[3][0], "", "#10 value");
|
||||||
|
`
|
||||||
|
testScriptWithTestLib(SCRIPT, _undefined, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRegexpInit(t *testing.T) {
|
||||||
|
const SCRIPT = `
|
||||||
|
RegExp(".").lastIndex;
|
||||||
|
`
|
||||||
|
testScript(SCRIPT, intToValue(0), t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRegexpToString(t *testing.T) {
|
||||||
|
const SCRIPT = `
|
||||||
|
RegExp.prototype.toString.call({
|
||||||
|
source: 'foo',
|
||||||
|
flags: 'bar'});
|
||||||
|
`
|
||||||
|
testScript(SCRIPT, asciiString("/foo/bar"), t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRegexpEscapeSource(t *testing.T) {
|
||||||
|
const SCRIPT = `
|
||||||
|
/href="(.+?)(\/.*\/\S+?)\/"/.source;
|
||||||
|
`
|
||||||
|
testScript(SCRIPT, asciiString(`href="(.+?)(\/.*\/\S+?)\/"`), t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRegexpConsecutiveMatchCache(t *testing.T) {
|
||||||
|
const SCRIPT = `
|
||||||
|
(function test(unicode) {
|
||||||
|
var regex = new RegExp('t(e)(st(\\d?))', unicode?'gu':'g');
|
||||||
|
var string = 'test1test2';
|
||||||
|
var match;
|
||||||
|
var matches = [];
|
||||||
|
while (match = regex.exec(string)) {
|
||||||
|
matches.push(match);
|
||||||
|
}
|
||||||
|
var expectedMatches = [
|
||||||
|
[
|
||||||
|
'test1',
|
||||||
|
'e',
|
||||||
|
'st1',
|
||||||
|
'1'
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'test2',
|
||||||
|
'e',
|
||||||
|
'st2',
|
||||||
|
'2'
|
||||||
|
]
|
||||||
|
];
|
||||||
|
expectedMatches[0].index = 0;
|
||||||
|
expectedMatches[0].input = 'test1test2';
|
||||||
|
expectedMatches[1].index = 5;
|
||||||
|
expectedMatches[1].input = 'test1test2';
|
||||||
|
|
||||||
|
assert(deepEqual(matches, expectedMatches), "#1");
|
||||||
|
|
||||||
|
// try the same regexp with a different string
|
||||||
|
regex.lastIndex = 0;
|
||||||
|
match = regex.exec(' test5');
|
||||||
|
var expectedMatch = [
|
||||||
|
'test5',
|
||||||
|
'e',
|
||||||
|
'st5',
|
||||||
|
'5'
|
||||||
|
];
|
||||||
|
expectedMatch.index = 1;
|
||||||
|
expectedMatch.input = ' test5';
|
||||||
|
assert(deepEqual(match, expectedMatch), "#2");
|
||||||
|
assert.sameValue(regex.lastIndex, 6, "#3");
|
||||||
|
|
||||||
|
// continue matching with a different string
|
||||||
|
match = regex.exec(' test5test6');
|
||||||
|
expectedMatch = [
|
||||||
|
'test6',
|
||||||
|
'e',
|
||||||
|
'st6',
|
||||||
|
'6'
|
||||||
|
];
|
||||||
|
expectedMatch.index = 6;
|
||||||
|
expectedMatch.input = ' test5test6';
|
||||||
|
assert(deepEqual(match, expectedMatch), "#4");
|
||||||
|
assert.sameValue(regex.lastIndex, 11, "#5");
|
||||||
|
|
||||||
|
match = regex.exec(' test5test6');
|
||||||
|
assert.sameValue(match, null, "#6");
|
||||||
|
return regex;
|
||||||
|
});
|
||||||
|
`
|
||||||
|
vm := New()
|
||||||
|
_, _ = vm.RunProgram(testLib())
|
||||||
|
_, _ = vm.RunProgram(testLibX())
|
||||||
|
v, err := vm.RunString(SCRIPT)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
var f func(bool) (*Object, error)
|
||||||
|
err = vm.ExportTo(v, &f)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
regex, err := f(false)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if regex.self.(*regexpObject).pattern.regexp2Wrapper.cache != nil {
|
||||||
|
t.Fatal("Cache is not nil (non-unicode)")
|
||||||
|
}
|
||||||
|
|
||||||
|
regex, err = f(true)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if regex.self.(*regexpObject).pattern.regexp2Wrapper.cache != nil {
|
||||||
|
t.Fatal("Cache is not nil (unicode)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRegexpMatchAll(t *testing.T) {
|
||||||
|
const SCRIPT = `
|
||||||
|
(function test(unicode) {
|
||||||
|
var regex = new RegExp('t(e)(st(\\d?))', unicode?'gu':'g');
|
||||||
|
var string = 'test1test2';
|
||||||
|
var matches = [];
|
||||||
|
for (var match of string.matchAll(regex)) {
|
||||||
|
matches.push(match);
|
||||||
|
}
|
||||||
|
var expectedMatches = [
|
||||||
|
[
|
||||||
|
'test1',
|
||||||
|
'e',
|
||||||
|
'st1',
|
||||||
|
'1'
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'test2',
|
||||||
|
'e',
|
||||||
|
'st2',
|
||||||
|
'2'
|
||||||
|
]
|
||||||
|
];
|
||||||
|
expectedMatches[0].index = 0;
|
||||||
|
expectedMatches[0].input = 'test1test2';
|
||||||
|
expectedMatches[1].index = 5;
|
||||||
|
expectedMatches[1].input = 'test1test2';
|
||||||
|
|
||||||
|
assert(deepEqual(matches, expectedMatches), "#1");
|
||||||
|
assert.sameValue(regex.lastIndex, 0, "#1 lastIndex");
|
||||||
|
|
||||||
|
// try the same regexp with a different string
|
||||||
|
string = ' test5';
|
||||||
|
matches = [];
|
||||||
|
for (var match of string.matchAll(regex)) {
|
||||||
|
matches.push(match);
|
||||||
|
}
|
||||||
|
expectedMatches = [
|
||||||
|
[
|
||||||
|
'test5',
|
||||||
|
'e',
|
||||||
|
'st5',
|
||||||
|
'5'
|
||||||
|
]
|
||||||
|
];
|
||||||
|
expectedMatches[0].index = 1;
|
||||||
|
expectedMatches[0].input = ' test5';
|
||||||
|
assert(deepEqual(matches, expectedMatches), "#2");
|
||||||
|
assert.sameValue(regex.lastIndex, 0, "#2 lastIndex");
|
||||||
|
|
||||||
|
// continue matching with a different string
|
||||||
|
string = ' test5test6';
|
||||||
|
matches = [];
|
||||||
|
for (var match of string.matchAll(regex)) {
|
||||||
|
matches.push(match);
|
||||||
|
}
|
||||||
|
var expectedMatches = [
|
||||||
|
[
|
||||||
|
'test5',
|
||||||
|
'e',
|
||||||
|
'st5',
|
||||||
|
'5'
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'test6',
|
||||||
|
'e',
|
||||||
|
'st6',
|
||||||
|
'6'
|
||||||
|
]
|
||||||
|
];
|
||||||
|
expectedMatches[0].index = 1;
|
||||||
|
expectedMatches[0].input = ' test5test6';
|
||||||
|
expectedMatches[1].index = 6;
|
||||||
|
expectedMatches[1].input = ' test5test6';
|
||||||
|
assert(deepEqual(matches, expectedMatches), "#3");
|
||||||
|
assert.sameValue(regex.lastIndex, 0, "#3 lastindex");
|
||||||
|
});
|
||||||
|
`
|
||||||
|
vm := New()
|
||||||
|
_, _ = vm.RunProgram(testLib())
|
||||||
|
_, _ = vm.RunProgram(testLibX())
|
||||||
|
v, err := vm.RunString(SCRIPT)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
var f func(bool) (*Object, error)
|
||||||
|
err = vm.ExportTo(v, &f)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = f(false)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = f(true)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRegexpOverrideSpecies(t *testing.T) {
|
||||||
|
const SCRIPT = `
|
||||||
|
Object.defineProperty(RegExp, Symbol.species, {
|
||||||
|
configurable: true,
|
||||||
|
value: function() {
|
||||||
|
throw "passed";
|
||||||
|
}
|
||||||
|
});
|
||||||
|
try {
|
||||||
|
"ab".split(/a/);
|
||||||
|
throw new Error("Expected error");
|
||||||
|
} catch(e) {
|
||||||
|
if (e !== "passed") {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
testScript(SCRIPT, _undefined, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRegexpSymbolMatchAllCallsIsRegexp(t *testing.T) {
|
||||||
|
// This is tc39's test/built-ins/RegExp/prototype/Symbol.matchAll/isregexp-this-throws.js
|
||||||
|
const SCRIPT = `
|
||||||
|
var a = new Object();
|
||||||
|
Object.defineProperty(a, Symbol.match, {
|
||||||
|
get: function() {
|
||||||
|
throw "passed";
|
||||||
|
}
|
||||||
|
});
|
||||||
|
try {
|
||||||
|
RegExp.prototype[Symbol.matchAll].call(a, '');
|
||||||
|
throw new Error("Expected error");
|
||||||
|
} catch(e) {
|
||||||
|
if (e !== "passed") {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
testScript(SCRIPT, _undefined, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRegexpMatchAllConstructor(t *testing.T) {
|
||||||
|
// This is tc39's test/built-ins/RegExp/prototype/Symbol.matchAll/species-constuctor.js
|
||||||
|
const SCRIPT = `
|
||||||
|
var callCount = 0;
|
||||||
|
var callArgs;
|
||||||
|
var regexp = /\d/u;
|
||||||
|
var obj = {}
|
||||||
|
Object.defineProperty(obj, Symbol.species, {
|
||||||
|
value: function() {
|
||||||
|
callCount++;
|
||||||
|
callArgs = arguments;
|
||||||
|
return /\w/g;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
regexp.constructor = obj;
|
||||||
|
var str = 'a*b';
|
||||||
|
var iter = regexp[Symbol.matchAll](str);
|
||||||
|
|
||||||
|
assert.sameValue(callCount, 1);
|
||||||
|
assert.sameValue(callArgs.length, 2);
|
||||||
|
assert.sameValue(callArgs[0], regexp);
|
||||||
|
assert.sameValue(callArgs[1], 'u');
|
||||||
|
|
||||||
|
var first = iter.next()
|
||||||
|
assert.sameValue(first.done, false);
|
||||||
|
assert.sameValue(first.value.length, 1);
|
||||||
|
assert.sameValue(first.value[0], "a");
|
||||||
|
var second = iter.next()
|
||||||
|
assert.sameValue(second.done, true);
|
||||||
|
`
|
||||||
|
testScriptWithTestLib(SCRIPT, _undefined, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRegexp2InvalidEscape(t *testing.T) {
|
||||||
|
testScript(`/(?=)\x0/.test("x0")`, valueTrue, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRegexpUnicodeEmptyMatch(t *testing.T) {
|
||||||
|
testScript(`/(0)0|/gu.exec("0\xef").length === 2`, valueTrue, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRegexpInvalidGroup(t *testing.T) {
|
||||||
|
const SCRIPT = `
|
||||||
|
["?", "(?)"].forEach(function(s) {
|
||||||
|
assert.throws(SyntaxError, function() {new RegExp(s)}, s);
|
||||||
|
});
|
||||||
|
`
|
||||||
|
testScriptWithTestLib(SCRIPT, _undefined, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRegexpLookbehindAssertion(t *testing.T) {
|
||||||
|
const SCRIPT = `
|
||||||
|
var re = /(?<=Jack|Tom)Sprat/;
|
||||||
|
assert(re.test("JackSprat"), "#1");
|
||||||
|
assert(!re.test("JohnSprat"), "#2");
|
||||||
|
|
||||||
|
re = /(?<!-)\d+/;
|
||||||
|
assert(re.test("3"), "#3");
|
||||||
|
assert(!re.test("-3"), "#4");
|
||||||
|
`
|
||||||
|
testScriptWithTestLib(SCRIPT, _undefined, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRegexpInvalidUTF8(t *testing.T) {
|
||||||
|
vm := New()
|
||||||
|
// Note that normally vm.ToValue() would replace invalid UTF-8 sequences with RuneError
|
||||||
|
_, err := vm.New(vm.Get("RegExp"), asciiString([]byte{0xAD}))
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("Expected error")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// this should not cause data races when run with -race
|
||||||
|
func TestRegexpConcurrentLiterals(t *testing.T) {
|
||||||
|
prg := MustCompile("test.js", `var r = /(?<!-)\d+/; r.test("");`, false)
|
||||||
|
go func() {
|
||||||
|
vm := New()
|
||||||
|
_, err := vm.RunProgram(prg)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
vm := New()
|
||||||
|
_, _ = vm.RunProgram(prg)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRegexpDotAll(t *testing.T) {
|
||||||
|
const SCRIPT = `
|
||||||
|
var re = /./s;
|
||||||
|
re.test("\r") && re.test("\n")
|
||||||
|
`
|
||||||
|
testScript(SCRIPT, valueTrue, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRegexpDotAllInGroup(t *testing.T) {
|
||||||
|
const SCRIPT = `
|
||||||
|
var re = /(.)/s;
|
||||||
|
re.test("\r") && re.test("\n")
|
||||||
|
`
|
||||||
|
testScript(SCRIPT, valueTrue, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRegexpNumSeparators(t *testing.T) {
|
||||||
|
const SCRIPT = `
|
||||||
|
const re = /(?<=a)\u{65}_/u;
|
||||||
|
assert(re.test("ae_") && !re.test("e_"));
|
||||||
|
|
||||||
|
assert.throws(SyntaxError, () => {
|
||||||
|
new RegExp("(?<=a)\\u{6_5}", "u");
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.throws(SyntaxError, () => {
|
||||||
|
new RegExp("a\\u{6_5}", "u");
|
||||||
|
});
|
||||||
|
|
||||||
|
`
|
||||||
|
testScriptWithTestLib(SCRIPT, _undefined, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRegexpUnicodeEscape(t *testing.T) {
|
||||||
|
const SCRIPT = `
|
||||||
|
assert.sameValue("u{0_2}".match(/\u{0_2}/)[0], "u{0_2}");
|
||||||
|
assert.sameValue("uu\x02".match(/\u{2}/u)[0], '\x02');
|
||||||
|
assert.sameValue("uu\x02".match(/\u{2}/)[0], "uu");
|
||||||
|
`
|
||||||
|
testScriptWithTestLib(SCRIPT, _undefined, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkRegexpSplitWithBackRef(b *testing.B) {
|
||||||
|
const SCRIPT = `
|
||||||
|
"aaaaaaaaaaaaaaaaaaaaaaaaa++bbbbbbbbbbbbbbbbbbbbbb+-ccccccccccccccccccccccc".split(/([+-])\1/)
|
||||||
|
`
|
||||||
|
b.StopTimer()
|
||||||
|
prg, err := Compile("test.js", SCRIPT, false)
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
vm := New()
|
||||||
|
b.StartTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
vm.RunProgram(prg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkRegexpMatch(b *testing.B) {
|
||||||
|
const SCRIPT = `
|
||||||
|
"a\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\r\
|
||||||
|
a\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\r\
|
||||||
|
a\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\r\
|
||||||
|
a\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\r\
|
||||||
|
a\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\r\
|
||||||
|
a\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\r\
|
||||||
|
a\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\r\
|
||||||
|
a\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\r\
|
||||||
|
a\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\r\
|
||||||
|
a\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\r\
|
||||||
|
".match(/[^\r\n]+/g)
|
||||||
|
`
|
||||||
|
b.StopTimer()
|
||||||
|
prg, err := Compile("test.js", SCRIPT, false)
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
vm := New()
|
||||||
|
b.StartTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
vm.RunProgram(prg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkRegexpMatchCache(b *testing.B) {
|
||||||
|
const SCRIPT = `
|
||||||
|
(function() {
|
||||||
|
var s = "a\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\r\
|
||||||
|
a\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\r\
|
||||||
|
a\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\r\
|
||||||
|
a\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\r\
|
||||||
|
a\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\r\
|
||||||
|
a\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\r\
|
||||||
|
a\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\r\
|
||||||
|
a\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\r\
|
||||||
|
a\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\r\
|
||||||
|
a\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\r\
|
||||||
|
a\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\r\
|
||||||
|
a\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\r\
|
||||||
|
a\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\r\
|
||||||
|
a\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\r\
|
||||||
|
a\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\r\
|
||||||
|
a\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\r\
|
||||||
|
a\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\r\
|
||||||
|
a\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\r\
|
||||||
|
a\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\r\
|
||||||
|
a\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\r\
|
||||||
|
a\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\r\
|
||||||
|
a\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\r\
|
||||||
|
a\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\r\
|
||||||
|
a\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\r\
|
||||||
|
a\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\r\
|
||||||
|
a\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\r\
|
||||||
|
a\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\r\
|
||||||
|
a\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\r\
|
||||||
|
a\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\r\
|
||||||
|
a\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\r\
|
||||||
|
a\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\r\
|
||||||
|
a\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\r\
|
||||||
|
a\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\r\
|
||||||
|
a\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\r\
|
||||||
|
a\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\r\
|
||||||
|
a\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\r\
|
||||||
|
a\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\r\
|
||||||
|
"
|
||||||
|
var r = /[^\r\n]+/g
|
||||||
|
while(r.exec(s)) {};
|
||||||
|
});
|
||||||
|
`
|
||||||
|
vm := New()
|
||||||
|
v, err := vm.RunString(SCRIPT)
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
if fn, ok := AssertFunction(v); ok {
|
||||||
|
b.ResetTimer()
|
||||||
|
b.ReportAllocs()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
fn(_undefined)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
b.Fatal("not a function")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkRegexpMatchAll(b *testing.B) {
|
||||||
|
const SCRIPT = `
|
||||||
|
(function() {
|
||||||
|
var s = "a\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\r\
|
||||||
|
a\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\r\
|
||||||
|
a\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\r\
|
||||||
|
a\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\r\
|
||||||
|
a\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\r\
|
||||||
|
a\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\r\
|
||||||
|
a\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\r\
|
||||||
|
a\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\r\
|
||||||
|
a\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\r\
|
||||||
|
a\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\r\
|
||||||
|
a\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\r\
|
||||||
|
a\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\r\
|
||||||
|
a\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\r\
|
||||||
|
a\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\r\
|
||||||
|
a\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\r\
|
||||||
|
a\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\r\
|
||||||
|
a\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\r\
|
||||||
|
a\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\r\
|
||||||
|
a\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\r\
|
||||||
|
a\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\r\
|
||||||
|
a\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\r\
|
||||||
|
a\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\r\
|
||||||
|
a\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\r\
|
||||||
|
a\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\r\
|
||||||
|
a\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\r\
|
||||||
|
a\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\r\
|
||||||
|
a\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\r\
|
||||||
|
a\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\r\
|
||||||
|
a\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\r\
|
||||||
|
a\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\r\
|
||||||
|
a\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\r\
|
||||||
|
a\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\r\
|
||||||
|
a\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\r\
|
||||||
|
a\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\r\
|
||||||
|
a\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\r\
|
||||||
|
a\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\r\
|
||||||
|
a\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\ra\nb\r\c\nd\r\e\n\f\rg\nh\r\
|
||||||
|
"
|
||||||
|
var r = /[^\r\n]+/g
|
||||||
|
for (var v of s.matchAll(r)) {}
|
||||||
|
});
|
||||||
|
`
|
||||||
|
vm := New()
|
||||||
|
v, err := vm.RunString(SCRIPT)
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
if fn, ok := AssertFunction(v); ok {
|
||||||
|
b.ResetTimer()
|
||||||
|
b.ReportAllocs()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
fn(_undefined)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
b.Fatal("not a function")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkRegexpSingleExec(b *testing.B) {
|
||||||
|
vm := New()
|
||||||
|
regexp := vm.Get("RegExp")
|
||||||
|
f := func(reStr, str string, b *testing.B) {
|
||||||
|
r, err := vm.New(regexp, vm.ToValue(reStr))
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
exec, ok := AssertFunction(r.Get("exec"))
|
||||||
|
if !ok {
|
||||||
|
b.Fatal("RegExp.exec is not a function")
|
||||||
|
}
|
||||||
|
arg := vm.ToValue(str)
|
||||||
|
b.ResetTimer()
|
||||||
|
b.ReportAllocs()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
_, err := exec(r, arg)
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
b.Run("Re-ASCII", func(b *testing.B) {
|
||||||
|
f("test", "aaaaaaaaaaaaaaaaaaaaaaaaa testing", b)
|
||||||
|
})
|
||||||
|
|
||||||
|
b.Run("Re2-ASCII", func(b *testing.B) {
|
||||||
|
f("(?=)test", "aaaaaaaaaaaaaaaaaaaaaaaaa testing", b)
|
||||||
|
})
|
||||||
|
|
||||||
|
b.Run("Re-Unicode", func(b *testing.B) {
|
||||||
|
f("test", "aaaaaaaaaaaaaaaaaaaaaaaaa testing 😀", b)
|
||||||
|
})
|
||||||
|
|
||||||
|
b.Run("Re2-Unicode", func(b *testing.B) {
|
||||||
|
f("(?=)test", "aaaaaaaaaaaaaaaaaaaaaaaaa testing 😀", b)
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
@ -17,14 +17,19 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/ssgo/u"
|
"apigo.cc/gojs/common"
|
||||||
"golang.org/x/text/collate"
|
"golang.org/x/text/collate"
|
||||||
|
|
||||||
"apigo.cc/gojs/common"
|
// added by Star
|
||||||
|
// js_ast "apigo.cc/gojs/goja/ast"
|
||||||
|
// "apigo.cc/gojs/goja/file"
|
||||||
|
// "apigo.cc/gojs/goja/parser"
|
||||||
|
// "apigo.cc/gojs/goja/unistring"
|
||||||
js_ast "apigo.cc/gojs/goja/ast"
|
js_ast "apigo.cc/gojs/goja/ast"
|
||||||
"apigo.cc/gojs/goja/file"
|
"apigo.cc/gojs/goja/file"
|
||||||
"apigo.cc/gojs/goja/parser"
|
"apigo.cc/gojs/goja/parser"
|
||||||
"apigo.cc/gojs/goja/unistring"
|
"apigo.cc/gojs/goja/unistring"
|
||||||
|
"github.com/ssgo/u"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -182,10 +187,12 @@ type RandSource func() float64
|
|||||||
type Now func() time.Time
|
type Now func() time.Time
|
||||||
|
|
||||||
type Runtime struct {
|
type Runtime struct {
|
||||||
goData map[string]any
|
// added by Star
|
||||||
Locker sync.Mutex
|
goData map[string]any
|
||||||
CallbackLocker sync.Mutex
|
Locker sync.Mutex
|
||||||
goDataLocker sync.RWMutex
|
CallbackLocker sync.Mutex
|
||||||
|
goDataLocker sync.RWMutex
|
||||||
|
|
||||||
global global
|
global global
|
||||||
globalObject *Object
|
globalObject *Object
|
||||||
stringSingleton *stringObject
|
stringSingleton *stringObject
|
||||||
@ -211,6 +218,7 @@ type Runtime struct {
|
|||||||
asyncContextTracker AsyncContextTracker
|
asyncContextTracker AsyncContextTracker
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ======>> added by Star
|
||||||
func (rt *Runtime) SetData(name string, value any) {
|
func (rt *Runtime) SetData(name string, value any) {
|
||||||
rt.goDataLocker.Lock()
|
rt.goDataLocker.Lock()
|
||||||
defer rt.goDataLocker.Unlock()
|
defer rt.goDataLocker.Unlock()
|
||||||
@ -229,6 +237,8 @@ func (rt *Runtime) GetData(name string) any {
|
|||||||
return rt.goData[name]
|
return rt.goData[name]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ======<< added by Star
|
||||||
|
|
||||||
type StackFrame struct {
|
type StackFrame struct {
|
||||||
prg *Program
|
prg *Program
|
||||||
funcName unistring.String
|
funcName unistring.String
|
||||||
@ -618,6 +628,8 @@ func (r *Runtime) NewGoError(err error) *Object {
|
|||||||
errStr := err.Error() + "|:|" + u.Json(callStacks)
|
errStr := err.Error() + "|:|" + u.Json(callStacks)
|
||||||
// errStr = strings.ReplaceAll(errStr, "GoError: ", "")
|
// errStr = strings.ReplaceAll(errStr, "GoError: ", "")
|
||||||
e := r.newError(r.getGoError(), errStr).(*Object)
|
e := r.newError(r.getGoError(), errStr).(*Object)
|
||||||
|
|
||||||
|
// e := r.newError(r.getGoError(), err.Error()).(*Object)
|
||||||
e.Set("value", err)
|
e.Set("value", err)
|
||||||
return e
|
return e
|
||||||
}
|
}
|
||||||
@ -1365,6 +1377,8 @@ func (r *Runtime) toBoolean(b bool) Value {
|
|||||||
// New creates an instance of a Javascript runtime that can be used to run code. Multiple instances may be created and
|
// New creates an instance of a Javascript runtime that can be used to run code. Multiple instances may be created and
|
||||||
// used simultaneously, however it is not possible to pass JS values across runtimes.
|
// used simultaneously, however it is not possible to pass JS values across runtimes.
|
||||||
func New() *Runtime {
|
func New() *Runtime {
|
||||||
|
// r := &Runtime{}
|
||||||
|
// added by Star
|
||||||
r := &Runtime{goData: make(map[string]any)}
|
r := &Runtime{goData: make(map[string]any)}
|
||||||
r.init()
|
r.init()
|
||||||
return r
|
return r
|
||||||
@ -1691,6 +1705,13 @@ Notes on individual types:
|
|||||||
Primitive types (numbers, string, bool) are converted to the corresponding JavaScript primitives. These values
|
Primitive types (numbers, string, bool) are converted to the corresponding JavaScript primitives. These values
|
||||||
are goroutine-safe and can be transferred between runtimes.
|
are goroutine-safe and can be transferred between runtimes.
|
||||||
|
|
||||||
|
# *big.Int
|
||||||
|
|
||||||
|
A *big.Int value is converted to a BigInt value. Note, because BigInt is immutable, but *big.Int isn't, the value is
|
||||||
|
copied. Export()'ing this value returns a *big.Int which is also a copy.
|
||||||
|
|
||||||
|
If the pointer value is nil, the resulting BigInt is 0n.
|
||||||
|
|
||||||
# Strings
|
# Strings
|
||||||
|
|
||||||
Because of the difference in internal string representation between ECMAScript (which uses UTF-16) and Go (which uses
|
Because of the difference in internal string representation between ECMAScript (which uses UTF-16) and Go (which uses
|
||||||
@ -1932,7 +1953,11 @@ func (r *Runtime) toValue(i interface{}, origValue reflect.Value) Value {
|
|||||||
case float64:
|
case float64:
|
||||||
return floatToValue(i)
|
return floatToValue(i)
|
||||||
case *big.Int:
|
case *big.Int:
|
||||||
return (*valueBigInt)(new(big.Int).Set(i))
|
v := new(big.Int)
|
||||||
|
if i != nil {
|
||||||
|
v.Set(i)
|
||||||
|
}
|
||||||
|
return (*valueBigInt)(v)
|
||||||
case map[string]interface{}:
|
case map[string]interface{}:
|
||||||
if i == nil {
|
if i == nil {
|
||||||
return _null
|
return _null
|
||||||
@ -2448,6 +2473,12 @@ func (r *Runtime) GlobalObject() *Object {
|
|||||||
return r.globalObject
|
return r.globalObject
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetGlobalObject sets the global object to the given object.
|
||||||
|
// Note, any existing references to the previous global object will continue to reference that object.
|
||||||
|
func (r *Runtime) SetGlobalObject(object *Object) {
|
||||||
|
r.globalObject = object
|
||||||
|
}
|
||||||
|
|
||||||
// Set the specified variable in the global context.
|
// Set the specified variable in the global context.
|
||||||
// Equivalent to running "name = value" in non-strict mode.
|
// Equivalent to running "name = value" in non-strict mode.
|
||||||
// The value is first converted using ToValue().
|
// The value is first converted using ToValue().
|
||||||
@ -2601,6 +2632,25 @@ func IsInfinity(v Value) bool {
|
|||||||
return v == _positiveInf || v == _negativeInf
|
return v == _positiveInf || v == _negativeInf
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func IsNumber(v Value) bool {
|
||||||
|
switch v.(type) {
|
||||||
|
case valueInt, valueFloat:
|
||||||
|
return true
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func IsBigInt(v Value) bool {
|
||||||
|
_, ok := v.(*valueBigInt)
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
|
func IsString(v Value) bool {
|
||||||
|
_, ok := v.(String)
|
||||||
|
return ok
|
||||||
|
}
|
||||||
|
|
||||||
// Undefined returns JS undefined value. Note if global 'undefined' property is changed this still returns the original value.
|
// Undefined returns JS undefined value. Note if global 'undefined' property is changed this still returns the original value.
|
||||||
func Undefined() Value {
|
func Undefined() Value {
|
||||||
return _undefined
|
return _undefined
|
||||||
|
|||||||
3268
goja/runtime_test.go
Normal file
3268
goja/runtime_test.go
Normal file
File diff suppressed because it is too large
Load Diff
1
goja/staticcheck.conf
Normal file
1
goja/staticcheck.conf
Normal file
@ -0,0 +1 @@
|
|||||||
|
checks = ["all", "-ST1000", "-ST1003", "-ST1005", "-ST1006", "-ST1012", "-ST1021", "-ST1020", "-ST1008"]
|
||||||
194
goja/string_test.go
Normal file
194
goja/string_test.go
Normal file
@ -0,0 +1,194 @@
|
|||||||
|
package goja
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
"unicode/utf16"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestStringOOBProperties(t *testing.T) {
|
||||||
|
const SCRIPT = `
|
||||||
|
var string = new String("str");
|
||||||
|
|
||||||
|
string[4] = 1;
|
||||||
|
string[4];
|
||||||
|
`
|
||||||
|
|
||||||
|
testScript(SCRIPT, valueInt(1), t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestImportedString(t *testing.T) {
|
||||||
|
vm := New()
|
||||||
|
|
||||||
|
testUnaryOp := func(a, expr string, result interface{}, t *testing.T) {
|
||||||
|
v, err := vm.RunString("a => " + expr)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
var fn func(a Value) (Value, error)
|
||||||
|
err = vm.ExportTo(v, &fn)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
for _, aa := range []Value{newStringValue(a), vm.ToValue(a)} {
|
||||||
|
res, err := fn(aa)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if res.Export() != result {
|
||||||
|
t.Fatalf("%s, a:%v(%T). expected: %v, actual: %v", expr, aa, aa, result, res)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
testBinaryOp := func(a, b, expr string, result interface{}, t *testing.T) {
|
||||||
|
v, err := vm.RunString("(a, b) => " + expr)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
var fn func(a, b Value) (Value, error)
|
||||||
|
err = vm.ExportTo(v, &fn)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
for _, aa := range []Value{newStringValue(a), vm.ToValue(a)} {
|
||||||
|
for _, bb := range []Value{newStringValue(b), vm.ToValue(b)} {
|
||||||
|
res, err := fn(aa, bb)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if res.Export() != result {
|
||||||
|
t.Fatalf("%s, a:%v(%T), b:%v(%T). expected: %v, actual: %v", expr, aa, aa, bb, bb, result, res)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
strs := []string{"shortAscii", "longlongAscii1234567890123456789", "short юникод", "long юникод 1234567890 юникод \U0001F600", "юникод", "Ascii", "long", "код"}
|
||||||
|
indexOfResults := [][]int{
|
||||||
|
/*
|
||||||
|
const strs = ["shortAscii", "longlongAscii1234567890123456789", "short юникод", "long юникод 1234567890 юникод \u{1F600}", "юникод", "Ascii", "long", "код"];
|
||||||
|
|
||||||
|
strs.forEach(a => {
|
||||||
|
console.log("{", strs.map(b => a.indexOf(b)).join(", "), "},");
|
||||||
|
});
|
||||||
|
*/
|
||||||
|
{0, -1, -1, -1, -1, 5, -1, -1},
|
||||||
|
{-1, 0, -1, -1, -1, 8, 0, -1},
|
||||||
|
{-1, -1, 0, -1, 6, -1, -1, 9},
|
||||||
|
{-1, -1, -1, 0, 5, -1, 0, 8},
|
||||||
|
{-1, -1, -1, -1, 0, -1, -1, 3},
|
||||||
|
{-1, -1, -1, -1, -1, 0, -1, -1},
|
||||||
|
{-1, -1, -1, -1, -1, -1, 0, -1},
|
||||||
|
{-1, -1, -1, -1, -1, -1, -1, 0},
|
||||||
|
}
|
||||||
|
|
||||||
|
lastIndexOfResults := [][]int{
|
||||||
|
/*
|
||||||
|
strs.forEach(a => {
|
||||||
|
console.log("{", strs.map(b => a.lastIndexOf(b)).join(", "), "},");
|
||||||
|
});
|
||||||
|
*/
|
||||||
|
{0, -1, -1, -1, -1, 5, -1, -1},
|
||||||
|
{-1, 0, -1, -1, -1, 8, 4, -1},
|
||||||
|
{-1, -1, 0, -1, 6, -1, -1, 9},
|
||||||
|
{-1, -1, -1, 0, 23, -1, 0, 26},
|
||||||
|
{-1, -1, -1, -1, 0, -1, -1, 3},
|
||||||
|
{-1, -1, -1, -1, -1, 0, -1, -1},
|
||||||
|
{-1, -1, -1, -1, -1, -1, 0, -1},
|
||||||
|
{-1, -1, -1, -1, -1, -1, -1, 0},
|
||||||
|
}
|
||||||
|
|
||||||
|
pad := func(s, p string, n int, start bool) string {
|
||||||
|
if n == 0 {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
if p == "" {
|
||||||
|
p = " "
|
||||||
|
}
|
||||||
|
var b strings.Builder
|
||||||
|
ss := utf16.Encode([]rune(s))
|
||||||
|
b.Grow(n)
|
||||||
|
n -= len(ss)
|
||||||
|
if !start {
|
||||||
|
b.WriteString(s)
|
||||||
|
}
|
||||||
|
if n > 0 {
|
||||||
|
pp := utf16.Encode([]rune(p))
|
||||||
|
for n > 0 {
|
||||||
|
if n > len(pp) {
|
||||||
|
b.WriteString(p)
|
||||||
|
n -= len(pp)
|
||||||
|
} else {
|
||||||
|
b.WriteString(string(utf16.Decode(pp[:n])))
|
||||||
|
n = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if start {
|
||||||
|
b.WriteString(s)
|
||||||
|
}
|
||||||
|
return b.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, a := range strs {
|
||||||
|
testUnaryOp(a, "JSON.parse(JSON.stringify(a))", a, t)
|
||||||
|
testUnaryOp(a, "a.length", int64(len(utf16.Encode([]rune(a)))), t)
|
||||||
|
for j, b := range strs {
|
||||||
|
testBinaryOp(a, b, "a === b", a == b, t)
|
||||||
|
testBinaryOp(a, b, "a == b", a == b, t)
|
||||||
|
testBinaryOp(a, b, "a + b", a+b, t)
|
||||||
|
testBinaryOp(a, b, "a > b", strings.Compare(a, b) > 0, t)
|
||||||
|
testBinaryOp(a, b, "`A${a}B${b}C`", "A"+a+"B"+b+"C", t)
|
||||||
|
testBinaryOp(a, b, "a.indexOf(b)", int64(indexOfResults[i][j]), t)
|
||||||
|
testBinaryOp(a, b, "a.lastIndexOf(b)", int64(lastIndexOfResults[i][j]), t)
|
||||||
|
testBinaryOp(a, b, "a.padStart(32, b)", pad(a, b, 32, true), t)
|
||||||
|
testBinaryOp(a, b, "a.padEnd(32, b)", pad(a, b, 32, false), t)
|
||||||
|
testBinaryOp(a, b, "a.replace(b, '')", strings.Replace(a, b, "", 1), t)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStringFromUTF16(t *testing.T) {
|
||||||
|
s := StringFromUTF16([]uint16{})
|
||||||
|
if s.Length() != 0 || !s.SameAs(asciiString("")) {
|
||||||
|
t.Fatal(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
s = StringFromUTF16([]uint16{0xD800})
|
||||||
|
if s.Length() != 1 || s.CharAt(0) != 0xD800 {
|
||||||
|
t.Fatal(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
s = StringFromUTF16([]uint16{'A', 'B'})
|
||||||
|
if !s.SameAs(asciiString("AB")) {
|
||||||
|
t.Fatal(s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStringBuilder(t *testing.T) {
|
||||||
|
t.Run("writeUTF8String-switch", func(t *testing.T) {
|
||||||
|
var sb StringBuilder
|
||||||
|
sb.WriteUTF8String("Head")
|
||||||
|
sb.WriteUTF8String("1ábc")
|
||||||
|
if res := sb.String().String(); res != "Head1ábc" {
|
||||||
|
t.Fatal(res)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkASCIIConcat(b *testing.B) {
|
||||||
|
vm := New()
|
||||||
|
|
||||||
|
b.ResetTimer()
|
||||||
|
b.ReportAllocs()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
_, err := vm.RunString(`{let result = "ab";
|
||||||
|
for (let i = 0 ; i < 10;i++) {
|
||||||
|
result += result;
|
||||||
|
}}`)
|
||||||
|
if err != nil {
|
||||||
|
b.Fatalf("Unexpected errors %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
19
goja/tc39_norace_test.go
Normal file
19
goja/tc39_norace_test.go
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
//go:build !race
|
||||||
|
// +build !race
|
||||||
|
|
||||||
|
package goja
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
// Prevent linter warnings about unused type
|
||||||
|
var _ = tc39Test{name: "", f: nil}
|
||||||
|
|
||||||
|
func (ctx *tc39TestCtx) runTest(name string, f func(t *testing.T)) {
|
||||||
|
ctx.t.Run(name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
f(t)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctx *tc39TestCtx) flush() {
|
||||||
|
}
|
||||||
32
goja/tc39_race_test.go
Normal file
32
goja/tc39_race_test.go
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
//go:build race
|
||||||
|
// +build race
|
||||||
|
|
||||||
|
package goja
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
tc39MaxTestGroupSize = 8000 // to prevent race detector complaining about too many goroutines
|
||||||
|
)
|
||||||
|
|
||||||
|
func (ctx *tc39TestCtx) runTest(name string, f func(t *testing.T)) {
|
||||||
|
ctx.testQueue = append(ctx.testQueue, tc39Test{name: name, f: f})
|
||||||
|
if len(ctx.testQueue) >= tc39MaxTestGroupSize {
|
||||||
|
ctx.flush()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctx *tc39TestCtx) flush() {
|
||||||
|
ctx.t.Run("tc39", func(t *testing.T) {
|
||||||
|
for _, tc := range ctx.testQueue {
|
||||||
|
tc := tc
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
tc.f(t)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
ctx.testQueue = ctx.testQueue[:0]
|
||||||
|
}
|
||||||
754
goja/tc39_test.go
Normal file
754
goja/tc39_test.go
Normal file
@ -0,0 +1,754 @@
|
|||||||
|
package goja
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"runtime/debug"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/Masterminds/semver/v3"
|
||||||
|
"gopkg.in/yaml.v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
tc39BASE = "testdata/test262"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
invalidFormatError = errors.New("Invalid file format")
|
||||||
|
|
||||||
|
ignorableTestError = newSymbol(stringEmpty)
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
skipPrefixes prefixList
|
||||||
|
|
||||||
|
skipList = map[string]bool{
|
||||||
|
|
||||||
|
// out-of-date (https://github.com/tc39/test262/issues/3407)
|
||||||
|
"test/language/expressions/prefix-increment/S11.4.4_A6_T3.js": true,
|
||||||
|
"test/language/expressions/prefix-increment/S11.4.4_A6_T2.js": true,
|
||||||
|
"test/language/expressions/prefix-increment/S11.4.4_A6_T1.js": true,
|
||||||
|
"test/language/expressions/prefix-decrement/S11.4.5_A6_T3.js": true,
|
||||||
|
"test/language/expressions/prefix-decrement/S11.4.5_A6_T2.js": true,
|
||||||
|
"test/language/expressions/prefix-decrement/S11.4.5_A6_T1.js": true,
|
||||||
|
"test/language/expressions/postfix-increment/S11.3.1_A6_T3.js": true,
|
||||||
|
"test/language/expressions/postfix-increment/S11.3.1_A6_T2.js": true,
|
||||||
|
"test/language/expressions/postfix-increment/S11.3.1_A6_T1.js": true,
|
||||||
|
"test/language/expressions/postfix-decrement/S11.3.2_A6_T3.js": true,
|
||||||
|
"test/language/expressions/postfix-decrement/S11.3.2_A6_T2.js": true,
|
||||||
|
"test/language/expressions/postfix-decrement/S11.3.2_A6_T1.js": true,
|
||||||
|
"test/language/expressions/compound-assignment/S11.13.2_A7.1_T4.js": true,
|
||||||
|
"test/language/expressions/compound-assignment/S11.13.2_A7.1_T2.js": true,
|
||||||
|
"test/language/expressions/compound-assignment/S11.13.2_A7.1_T1.js": true,
|
||||||
|
"test/language/expressions/compound-assignment/S11.13.2_A7.11_T4.js": true,
|
||||||
|
"test/language/expressions/compound-assignment/S11.13.2_A7.11_T2.js": true,
|
||||||
|
"test/language/expressions/compound-assignment/S11.13.2_A7.11_T1.js": true,
|
||||||
|
"test/language/expressions/compound-assignment/S11.13.2_A7.10_T4.js": true,
|
||||||
|
"test/language/expressions/compound-assignment/S11.13.2_A7.10_T2.js": true,
|
||||||
|
"test/language/expressions/compound-assignment/S11.13.2_A7.10_T1.js": true,
|
||||||
|
"test/language/expressions/compound-assignment/S11.13.2_A7.9_T4.js": true,
|
||||||
|
"test/language/expressions/compound-assignment/S11.13.2_A7.9_T2.js": true,
|
||||||
|
"test/language/expressions/compound-assignment/S11.13.2_A7.9_T1.js": true,
|
||||||
|
"test/language/expressions/compound-assignment/S11.13.2_A7.8_T4.js": true,
|
||||||
|
"test/language/expressions/compound-assignment/S11.13.2_A7.8_T2.js": true,
|
||||||
|
"test/language/expressions/compound-assignment/S11.13.2_A7.8_T1.js": true,
|
||||||
|
"test/language/expressions/compound-assignment/S11.13.2_A7.7_T4.js": true,
|
||||||
|
"test/language/expressions/compound-assignment/S11.13.2_A7.7_T2.js": true,
|
||||||
|
"test/language/expressions/compound-assignment/S11.13.2_A7.7_T1.js": true,
|
||||||
|
"test/language/expressions/compound-assignment/S11.13.2_A7.6_T4.js": true,
|
||||||
|
"test/language/expressions/compound-assignment/S11.13.2_A7.6_T2.js": true,
|
||||||
|
"test/language/expressions/compound-assignment/S11.13.2_A7.6_T1.js": true,
|
||||||
|
"test/language/expressions/compound-assignment/S11.13.2_A7.5_T4.js": true,
|
||||||
|
"test/language/expressions/compound-assignment/S11.13.2_A7.5_T2.js": true,
|
||||||
|
"test/language/expressions/compound-assignment/S11.13.2_A7.5_T1.js": true,
|
||||||
|
"test/language/expressions/compound-assignment/S11.13.2_A7.4_T4.js": true,
|
||||||
|
"test/language/expressions/compound-assignment/S11.13.2_A7.4_T2.js": true,
|
||||||
|
"test/language/expressions/compound-assignment/S11.13.2_A7.4_T1.js": true,
|
||||||
|
"test/language/expressions/compound-assignment/S11.13.2_A7.3_T4.js": true,
|
||||||
|
"test/language/expressions/compound-assignment/S11.13.2_A7.3_T2.js": true,
|
||||||
|
"test/language/expressions/compound-assignment/S11.13.2_A7.3_T1.js": true,
|
||||||
|
"test/language/expressions/compound-assignment/S11.13.2_A7.2_T4.js": true,
|
||||||
|
"test/language/expressions/compound-assignment/S11.13.2_A7.2_T2.js": true,
|
||||||
|
"test/language/expressions/compound-assignment/S11.13.2_A7.2_T1.js": true,
|
||||||
|
"test/language/expressions/assignment/S11.13.1_A7_T3.js": true,
|
||||||
|
|
||||||
|
// timezone
|
||||||
|
"test/built-ins/Date/prototype/toISOString/15.9.5.43-0-8.js": true,
|
||||||
|
"test/built-ins/Date/prototype/toISOString/15.9.5.43-0-9.js": true,
|
||||||
|
"test/built-ins/Date/prototype/toISOString/15.9.5.43-0-10.js": true,
|
||||||
|
|
||||||
|
// floating point date calculations
|
||||||
|
"test/built-ins/Date/UTC/fp-evaluation-order.js": true,
|
||||||
|
|
||||||
|
// quantifier integer limit in regexp
|
||||||
|
"test/built-ins/RegExp/quantifier-integer-limit.js": true,
|
||||||
|
|
||||||
|
// GetFunctionRealm
|
||||||
|
"test/built-ins/Function/internals/Construct/base-ctor-revoked-proxy.js": true,
|
||||||
|
|
||||||
|
// Uses deprecated __lookupGetter__/__lookupSetter__
|
||||||
|
"test/language/expressions/class/elements/private-getter-is-not-a-own-property.js": true,
|
||||||
|
"test/language/expressions/class/elements/private-setter-is-not-a-own-property.js": true,
|
||||||
|
"test/language/statements/class/elements/private-setter-is-not-a-own-property.js": true,
|
||||||
|
"test/language/statements/class/elements/private-getter-is-not-a-own-property.js": true,
|
||||||
|
|
||||||
|
// restricted unicode regexp syntax
|
||||||
|
"test/built-ins/RegExp/unicode_restricted_quantifiable_assertion.js": true,
|
||||||
|
"test/built-ins/RegExp/unicode_restricted_octal_escape.js": true,
|
||||||
|
"test/built-ins/RegExp/unicode_restricted_incomple_quantifier.js": true,
|
||||||
|
"test/built-ins/RegExp/unicode_restricted_incomplete_quantifier.js": true,
|
||||||
|
"test/built-ins/RegExp/unicode_restricted_identity_escape_x.js": true,
|
||||||
|
"test/built-ins/RegExp/unicode_restricted_identity_escape_u.js": true,
|
||||||
|
"test/built-ins/RegExp/unicode_restricted_identity_escape_c.js": true,
|
||||||
|
"test/built-ins/RegExp/unicode_restricted_identity_escape_alpha.js": true,
|
||||||
|
"test/built-ins/RegExp/unicode_restricted_identity_escape.js": true,
|
||||||
|
"test/built-ins/RegExp/unicode_restricted_brackets.js": true,
|
||||||
|
"test/built-ins/RegExp/unicode_restricted_character_class_escape.js": true,
|
||||||
|
"test/annexB/built-ins/RegExp/prototype/compile/pattern-string-invalid-u.js": true,
|
||||||
|
|
||||||
|
// Because goja parser works in UTF-8 it is not possible to pass strings containing invalid UTF-16 code points.
|
||||||
|
// This is mitigated by escaping them as \uXXXX, however because of this the RegExp source becomes
|
||||||
|
// `\uXXXX` instead of `<the actual UTF-16 code point of XXXX>`.
|
||||||
|
// The resulting RegExp will work exactly the same, but it causes these two tests to fail.
|
||||||
|
"test/annexB/built-ins/RegExp/RegExp-leading-escape-BMP.js": true,
|
||||||
|
"test/annexB/built-ins/RegExp/RegExp-trailing-escape-BMP.js": true,
|
||||||
|
"test/language/literals/regexp/S7.8.5_A1.4_T2.js": true,
|
||||||
|
"test/language/literals/regexp/S7.8.5_A1.1_T2.js": true,
|
||||||
|
"test/language/literals/regexp/S7.8.5_A2.1_T2.js": true,
|
||||||
|
"test/language/literals/regexp/S7.8.5_A2.4_T2.js": true,
|
||||||
|
|
||||||
|
// async generator
|
||||||
|
"test/language/expressions/optional-chaining/member-expression.js": true,
|
||||||
|
"test/language/expressions/class/elements/same-line-async-method-rs-static-async-generator-method-privatename-identifier-alt.js": true,
|
||||||
|
"test/language/expressions/class/elements/same-line-async-method-rs-static-async-generator-method-privatename-identifier.js": true,
|
||||||
|
"test/language/destructuring/binding/syntax/destructuring-object-parameters-function-arguments-length.js": true,
|
||||||
|
"test/language/destructuring/binding/syntax/destructuring-array-parameters-function-arguments-length.js": true,
|
||||||
|
"test/language/comments/hashbang/function-constructor.js": true,
|
||||||
|
"test/language/statements/class/elements/after-same-line-static-async-method-rs-static-async-generator-method-privatename-identifier.js": true,
|
||||||
|
"test/language/statements/class/elements/after-same-line-static-async-method-rs-static-async-generator-method-privatename-identifier-alt.js": true,
|
||||||
|
"test/language/statements/class/elements/same-line-async-method-rs-static-async-generator-method-privatename-identifier.js": true,
|
||||||
|
"test/language/statements/class/elements/same-line-async-method-rs-static-async-generator-method-privatename-identifier-alt.js": true,
|
||||||
|
"test/language/expressions/class/elements/after-same-line-static-async-method-rs-static-async-generator-method-privatename-identifier-alt.js": true,
|
||||||
|
"test/language/expressions/class/elements/after-same-line-static-async-method-rs-static-async-generator-method-privatename-identifier.js": true,
|
||||||
|
"test/built-ins/Object/seal/seal-asyncgeneratorfunction.js": true,
|
||||||
|
"test/language/statements/switch/scope-lex-async-generator.js": true,
|
||||||
|
"test/language/statements/class/elements/private-async-generator-method-name.js": true,
|
||||||
|
"test/language/expressions/class/elements/private-async-generator-method-name.js": true,
|
||||||
|
"test/language/expressions/async-generator/name.js": true,
|
||||||
|
"test/language/statements/class/elements/same-line-gen-rs-static-async-generator-method-privatename-identifier.js": true,
|
||||||
|
"test/language/statements/class/elements/same-line-gen-rs-static-async-generator-method-privatename-identifier-alt.js": true,
|
||||||
|
"test/language/statements/class/elements/new-sc-line-gen-rs-static-async-generator-method-privatename-identifier.js": true,
|
||||||
|
"test/language/statements/class/elements/new-sc-line-gen-rs-static-async-generator-method-privatename-identifier-alt.js": true,
|
||||||
|
"test/language/statements/class/elements/after-same-line-static-gen-rs-static-async-generator-method-privatename-identifier.js": true,
|
||||||
|
"test/language/statements/class/elements/after-same-line-static-gen-rs-static-async-generator-method-privatename-identifier-alt.js": true,
|
||||||
|
"test/language/statements/class/elements/after-same-line-gen-rs-static-async-generator-method-privatename-identifier.js": true,
|
||||||
|
"test/language/statements/class/elements/after-same-line-gen-rs-static-async-generator-method-privatename-identifier-alt.js": true,
|
||||||
|
"test/language/expressions/class/elements/same-line-gen-rs-static-async-generator-method-privatename-identifier.js": true,
|
||||||
|
"test/language/expressions/class/elements/same-line-gen-rs-static-async-generator-method-privatename-identifier-alt.js": true,
|
||||||
|
"test/language/expressions/class/elements/new-sc-line-gen-rs-static-async-generator-method-privatename-identifier.js": true,
|
||||||
|
"test/language/expressions/class/elements/new-sc-line-gen-rs-static-async-generator-method-privatename-identifier-alt.js": true,
|
||||||
|
"test/language/expressions/class/elements/after-same-line-static-gen-rs-static-async-generator-method-privatename-identifier.js": true,
|
||||||
|
"test/language/expressions/class/elements/after-same-line-static-gen-rs-static-async-generator-method-privatename-identifier-alt.js": true,
|
||||||
|
"test/language/expressions/class/elements/after-same-line-gen-rs-static-async-generator-method-privatename-identifier.js": true,
|
||||||
|
"test/language/expressions/class/elements/after-same-line-gen-rs-static-async-generator-method-privatename-identifier-alt.js": true,
|
||||||
|
"test/built-ins/GeneratorFunction/is-a-constructor.js": true,
|
||||||
|
|
||||||
|
// async iterator
|
||||||
|
"test/language/expressions/optional-chaining/iteration-statement-for-await-of.js": true,
|
||||||
|
|
||||||
|
// legacy number literals
|
||||||
|
"test/language/literals/numeric/non-octal-decimal-integer.js": true,
|
||||||
|
"test/language/literals/string/S7.8.4_A4.3_T2.js": true,
|
||||||
|
"test/language/literals/string/S7.8.4_A4.3_T1.js": true,
|
||||||
|
|
||||||
|
// Regexp
|
||||||
|
"test/language/literals/regexp/invalid-range-negative-lookbehind.js": true,
|
||||||
|
"test/language/literals/regexp/invalid-range-lookbehind.js": true,
|
||||||
|
"test/language/literals/regexp/invalid-optional-negative-lookbehind.js": true,
|
||||||
|
"test/language/literals/regexp/invalid-optional-lookbehind.js": true,
|
||||||
|
|
||||||
|
// unicode full case folding
|
||||||
|
"test/built-ins/RegExp/unicode_full_case_folding.js": true,
|
||||||
|
|
||||||
|
// FIXME bugs
|
||||||
|
|
||||||
|
// Left-hand side as a CoverParenthesizedExpression
|
||||||
|
"test/language/expressions/assignment/fn-name-lhs-cover.js": true,
|
||||||
|
|
||||||
|
// Character \ missing from character class [\c]
|
||||||
|
"test/annexB/built-ins/RegExp/RegExp-invalid-control-escape-character-class.js": true,
|
||||||
|
"test/annexB/built-ins/RegExp/RegExp-control-escape-russian-letter.js": true,
|
||||||
|
|
||||||
|
// Skip due to regexp named groups
|
||||||
|
"test/built-ins/String/prototype/replaceAll/searchValue-replacer-RegExp-call.js": true,
|
||||||
|
|
||||||
|
"test/built-ins/RegExp/nullable-quantifier.js": true,
|
||||||
|
"test/built-ins/RegExp/lookahead-quantifier-match-groups.js": true,
|
||||||
|
}
|
||||||
|
|
||||||
|
featuresBlackList = []string{
|
||||||
|
"async-iteration",
|
||||||
|
"Symbol.asyncIterator",
|
||||||
|
"resizable-arraybuffer",
|
||||||
|
"regexp-named-groups",
|
||||||
|
"regexp-duplicate-named-groups",
|
||||||
|
"regexp-unicode-property-escapes",
|
||||||
|
"regexp-match-indices",
|
||||||
|
"regexp-modifiers",
|
||||||
|
"RegExp.escape",
|
||||||
|
"legacy-regexp",
|
||||||
|
"tail-call-optimization",
|
||||||
|
"Temporal",
|
||||||
|
"import-assertions",
|
||||||
|
"dynamic-import",
|
||||||
|
"import.meta",
|
||||||
|
"Atomics",
|
||||||
|
"Atomics.waitAsync",
|
||||||
|
"Atomics.pause",
|
||||||
|
"FinalizationRegistry",
|
||||||
|
"WeakRef",
|
||||||
|
"__getter__",
|
||||||
|
"__setter__",
|
||||||
|
"ShadowRealm",
|
||||||
|
"SharedArrayBuffer",
|
||||||
|
"decorators",
|
||||||
|
"regexp-v-flag",
|
||||||
|
"iterator-helpers",
|
||||||
|
"symbols-as-weakmap-keys",
|
||||||
|
"uint8array-base64",
|
||||||
|
"String.prototype.toWellFormed",
|
||||||
|
"explicit-resource-management",
|
||||||
|
"set-methods",
|
||||||
|
"promise-try",
|
||||||
|
"promise-with-resolvers",
|
||||||
|
"array-grouping",
|
||||||
|
"Math.sumPrecise",
|
||||||
|
"Float16Array",
|
||||||
|
"arraybuffer-transfer",
|
||||||
|
"Array.fromAsync",
|
||||||
|
"String.prototype.isWellFormed",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
var goVersion *semver.Version
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
if info, ok := debug.ReadBuildInfo(); ok {
|
||||||
|
goVersion = semver.MustParse(strings.TrimPrefix(info.GoVersion, "go"))
|
||||||
|
} else {
|
||||||
|
panic("Could not read build info")
|
||||||
|
}
|
||||||
|
|
||||||
|
skip := func(prefixes ...string) {
|
||||||
|
for _, prefix := range prefixes {
|
||||||
|
skipPrefixes.Add(prefix)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if goVersion.LessThan(semver.MustParse("1.21")) {
|
||||||
|
skip(
|
||||||
|
// Go <1.21 only supports Unicode 13
|
||||||
|
"test/language/identifiers/start-unicode-14.",
|
||||||
|
"test/language/identifiers/part-unicode-14.",
|
||||||
|
"test/language/identifiers/start-unicode-15.",
|
||||||
|
"test/language/identifiers/part-unicode-15.",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
skip(
|
||||||
|
// generators and async generators (harness/hidden-constructors.js)
|
||||||
|
"test/built-ins/Async",
|
||||||
|
|
||||||
|
// async generators
|
||||||
|
"test/language/statements/class/elements/wrapped-in-sc-rs-static-async-generator-",
|
||||||
|
"test/language/statements/class/elements/same-line-method-rs-static-async-generator-",
|
||||||
|
"test/language/statements/class/elements/regular-definitions-rs-static-async-generator-",
|
||||||
|
"test/language/statements/class/elements/private-static-async-generator-",
|
||||||
|
"test/language/statements/class/elements/new-sc-line-method-rs-static-async-generator-",
|
||||||
|
"test/language/statements/class/elements/multiple-stacked-definitions-rs-static-async-generator-",
|
||||||
|
"test/language/statements/class/elements/new-no-sc-line-method-rs-static-async-generator-",
|
||||||
|
"test/language/statements/class/elements/multiple-definitions-rs-static-async-generator-",
|
||||||
|
"test/language/statements/class/elements/after-same-line-static-method-rs-static-async-generator-",
|
||||||
|
"test/language/statements/class/elements/after-same-line-method-rs-static-async-generator-",
|
||||||
|
"test/language/statements/class/elements/after-same-line-static-method-rs-static-async-generator-",
|
||||||
|
|
||||||
|
"test/language/expressions/class/elements/wrapped-in-sc-rs-static-async-generator-",
|
||||||
|
"test/language/expressions/class/elements/same-line-method-rs-static-async-generator-",
|
||||||
|
"test/language/expressions/class/elements/regular-definitions-rs-static-async-generator-",
|
||||||
|
"test/language/expressions/class/elements/private-static-async-generator-",
|
||||||
|
"test/language/expressions/class/elements/new-sc-line-method-rs-static-async-generator-",
|
||||||
|
"test/language/expressions/class/elements/multiple-stacked-definitions-rs-static-async-generator-",
|
||||||
|
"test/language/expressions/class/elements/new-no-sc-line-method-rs-static-async-generator-",
|
||||||
|
"test/language/expressions/class/elements/multiple-definitions-rs-static-async-generator-",
|
||||||
|
"test/language/expressions/class/elements/after-same-line-static-method-rs-static-async-generator-",
|
||||||
|
"test/language/expressions/class/elements/after-same-line-method-rs-static-async-generator-",
|
||||||
|
"test/language/expressions/class/elements/after-same-line-static-method-rs-static-async-generator-",
|
||||||
|
|
||||||
|
"test/language/eval-code/direct/async-gen-",
|
||||||
|
|
||||||
|
// restricted unicode regexp syntax
|
||||||
|
"test/language/literals/regexp/u-",
|
||||||
|
|
||||||
|
// legacy octal escape in strings in strict mode
|
||||||
|
"test/language/literals/string/legacy-octal-",
|
||||||
|
"test/language/literals/string/legacy-non-octal-",
|
||||||
|
|
||||||
|
// modules
|
||||||
|
"test/language/export/",
|
||||||
|
"test/language/import/",
|
||||||
|
"test/language/module-code/",
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
type tc39Test struct {
|
||||||
|
name string
|
||||||
|
f func(t *testing.T)
|
||||||
|
}
|
||||||
|
|
||||||
|
type tc39BenchmarkItem struct {
|
||||||
|
name string
|
||||||
|
duration time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
type tc39BenchmarkData []tc39BenchmarkItem
|
||||||
|
|
||||||
|
type tc39TestCtx struct {
|
||||||
|
base string
|
||||||
|
t *testing.T
|
||||||
|
prgCache map[string]*Program
|
||||||
|
prgCacheLock sync.Mutex
|
||||||
|
enableBench bool
|
||||||
|
benchmark tc39BenchmarkData
|
||||||
|
benchLock sync.Mutex
|
||||||
|
sabStub *Program
|
||||||
|
//lint:ignore U1000 Only used with race
|
||||||
|
testQueue []tc39Test
|
||||||
|
}
|
||||||
|
|
||||||
|
type TC39MetaNegative struct {
|
||||||
|
Phase, Type string
|
||||||
|
}
|
||||||
|
|
||||||
|
type tc39Meta struct {
|
||||||
|
Negative TC39MetaNegative
|
||||||
|
Includes []string
|
||||||
|
Flags []string
|
||||||
|
Features []string
|
||||||
|
Es5id string
|
||||||
|
Es6id string
|
||||||
|
Esid string
|
||||||
|
}
|
||||||
|
|
||||||
|
type prefixList struct {
|
||||||
|
prefixes map[int]map[string]struct{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pl *prefixList) Add(prefix string) {
|
||||||
|
l := pl.prefixes[len(prefix)]
|
||||||
|
if l == nil {
|
||||||
|
l = make(map[string]struct{})
|
||||||
|
if pl.prefixes == nil {
|
||||||
|
pl.prefixes = make(map[int]map[string]struct{})
|
||||||
|
}
|
||||||
|
pl.prefixes[len(prefix)] = l
|
||||||
|
}
|
||||||
|
l[prefix] = struct{}{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pl *prefixList) Match(s string) bool {
|
||||||
|
for l, prefixes := range pl.prefixes {
|
||||||
|
if len(s) >= l {
|
||||||
|
if _, exists := prefixes[s[:l]]; exists {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *tc39Meta) hasFlag(flag string) bool {
|
||||||
|
for _, f := range m.Flags {
|
||||||
|
if f == flag {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseTC39File(name string) (*tc39Meta, string, error) {
|
||||||
|
f, err := os.Open(name)
|
||||||
|
if err != nil {
|
||||||
|
return nil, "", err
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
b, err := io.ReadAll(f)
|
||||||
|
if err != nil {
|
||||||
|
return nil, "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
str := string(b)
|
||||||
|
metaStart := strings.Index(str, "/*---")
|
||||||
|
if metaStart == -1 {
|
||||||
|
return nil, "", invalidFormatError
|
||||||
|
} else {
|
||||||
|
metaStart += 5
|
||||||
|
}
|
||||||
|
metaEnd := strings.Index(str, "---*/")
|
||||||
|
if metaEnd == -1 || metaEnd <= metaStart {
|
||||||
|
return nil, "", invalidFormatError
|
||||||
|
}
|
||||||
|
|
||||||
|
var meta tc39Meta
|
||||||
|
err = yaml.Unmarshal([]byte(str[metaStart:metaEnd]), &meta)
|
||||||
|
if err != nil {
|
||||||
|
return nil, "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
if meta.Negative.Type != "" && meta.Negative.Phase == "" {
|
||||||
|
return nil, "", errors.New("negative type is set, but phase isn't")
|
||||||
|
}
|
||||||
|
|
||||||
|
return &meta, str, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*tc39TestCtx) detachArrayBuffer(call FunctionCall) Value {
|
||||||
|
if obj, ok := call.Argument(0).(*Object); ok {
|
||||||
|
if buf, ok := obj.self.(*arrayBufferObject); ok {
|
||||||
|
buf.detach()
|
||||||
|
return _undefined
|
||||||
|
}
|
||||||
|
}
|
||||||
|
panic(typeError("detachArrayBuffer() is called with incompatible argument"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*tc39TestCtx) throwIgnorableTestError(FunctionCall) Value {
|
||||||
|
panic(ignorableTestError)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctx *tc39TestCtx) runTC39Test(name, src string, meta *tc39Meta, t testing.TB) {
|
||||||
|
defer func() {
|
||||||
|
if x := recover(); x != nil {
|
||||||
|
panic(fmt.Sprintf("panic while running %s: %v", name, x))
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
vm := New()
|
||||||
|
_262 := vm.NewObject()
|
||||||
|
_262.Set("detachArrayBuffer", ctx.detachArrayBuffer)
|
||||||
|
_262.Set("createRealm", ctx.throwIgnorableTestError)
|
||||||
|
_262.Set("evalScript", func(call FunctionCall) Value {
|
||||||
|
script := call.Argument(0).String()
|
||||||
|
result, err := vm.RunString(script)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
})
|
||||||
|
vm.Set("$262", _262)
|
||||||
|
vm.Set("IgnorableTestError", ignorableTestError)
|
||||||
|
vm.RunProgram(ctx.sabStub)
|
||||||
|
var out []string
|
||||||
|
async := meta.hasFlag("async")
|
||||||
|
if async {
|
||||||
|
err := ctx.runFile(ctx.base, path.Join("harness", "doneprintHandle.js"), vm)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
vm.Set("print", func(msg string) {
|
||||||
|
out = append(out, msg)
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
vm.Set("print", t.Log)
|
||||||
|
}
|
||||||
|
|
||||||
|
err, early := ctx.runTC39Script(name, src, meta.Includes, vm)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
if meta.Negative.Type == "" {
|
||||||
|
if err, ok := err.(*Exception); ok {
|
||||||
|
if err.Value() == ignorableTestError {
|
||||||
|
t.Skip("Test threw IgnorableTestError")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
t.Fatalf("%s: %v", name, err)
|
||||||
|
} else {
|
||||||
|
if (meta.Negative.Phase == "early" || meta.Negative.Phase == "parse") && !early || meta.Negative.Phase == "runtime" && early {
|
||||||
|
t.Fatalf("%s: error %v happened at the wrong phase (expected %s)", name, err, meta.Negative.Phase)
|
||||||
|
}
|
||||||
|
var errType string
|
||||||
|
|
||||||
|
switch err := err.(type) {
|
||||||
|
case *Exception:
|
||||||
|
if o, ok := err.Value().(*Object); ok {
|
||||||
|
if c := o.Get("constructor"); c != nil {
|
||||||
|
if c, ok := c.(*Object); ok {
|
||||||
|
errType = c.Get("name").String()
|
||||||
|
} else {
|
||||||
|
t.Fatalf("%s: error constructor is not an object (%v)", name, o)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
t.Fatalf("%s: error does not have a constructor (%v)", name, o)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
t.Fatalf("%s: error is not an object (%v)", name, err.Value())
|
||||||
|
}
|
||||||
|
case *CompilerSyntaxError:
|
||||||
|
errType = "SyntaxError"
|
||||||
|
case *CompilerReferenceError:
|
||||||
|
errType = "ReferenceError"
|
||||||
|
default:
|
||||||
|
t.Fatalf("%s: error is not a JS error: %v", name, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if errType != meta.Negative.Type {
|
||||||
|
vm.vm.prg.dumpCode(t.Logf)
|
||||||
|
t.Fatalf("%s: unexpected error type (%s), expected (%s)", name, errType, meta.Negative.Type)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if meta.Negative.Type != "" {
|
||||||
|
vm.vm.prg.dumpCode(t.Logf)
|
||||||
|
t.Fatalf("%s: Expected error: %v", name, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if vm.vm.sp != 0 {
|
||||||
|
t.Fatalf("sp: %d", vm.vm.sp)
|
||||||
|
}
|
||||||
|
|
||||||
|
if l := len(vm.vm.iterStack); l > 0 {
|
||||||
|
t.Fatalf("iter stack is not empty: %d", l)
|
||||||
|
}
|
||||||
|
if async {
|
||||||
|
complete := false
|
||||||
|
for _, line := range out {
|
||||||
|
if strings.HasPrefix(line, "Test262:AsyncTestFailure:") {
|
||||||
|
t.Fatal(line)
|
||||||
|
} else if line == "Test262:AsyncTestComplete" {
|
||||||
|
complete = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !complete {
|
||||||
|
for _, line := range out {
|
||||||
|
t.Log(line)
|
||||||
|
}
|
||||||
|
t.Fatal("Test262:AsyncTestComplete was not printed")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctx *tc39TestCtx) runTC39File(name string, t testing.TB) {
|
||||||
|
if skipList[name] {
|
||||||
|
t.Skip("Excluded")
|
||||||
|
}
|
||||||
|
if skipPrefixes.Match(name) {
|
||||||
|
t.Skip("Excluded")
|
||||||
|
}
|
||||||
|
p := path.Join(ctx.base, name)
|
||||||
|
meta, src, err := parseTC39File(p)
|
||||||
|
if err != nil {
|
||||||
|
//t.Fatalf("Could not parse %s: %v", name, err)
|
||||||
|
t.Errorf("Could not parse %s: %v", name, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if meta.hasFlag("module") {
|
||||||
|
t.Skip("module")
|
||||||
|
}
|
||||||
|
if meta.Es5id == "" {
|
||||||
|
for _, feature := range meta.Features {
|
||||||
|
for _, bl := range featuresBlackList {
|
||||||
|
if feature == bl {
|
||||||
|
t.Skip("Blacklisted feature")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var startTime time.Time
|
||||||
|
if ctx.enableBench {
|
||||||
|
startTime = time.Now()
|
||||||
|
}
|
||||||
|
|
||||||
|
hasRaw := meta.hasFlag("raw")
|
||||||
|
|
||||||
|
if hasRaw || !meta.hasFlag("onlyStrict") {
|
||||||
|
//log.Printf("Running normal test: %s", name)
|
||||||
|
t.Logf("Running normal test: %s", name)
|
||||||
|
ctx.runTC39Test(name, src, meta, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !hasRaw && !meta.hasFlag("noStrict") {
|
||||||
|
//log.Printf("Running strict test: %s", name)
|
||||||
|
t.Logf("Running strict test: %s", name)
|
||||||
|
ctx.runTC39Test(name, "'use strict';\n"+src, meta, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
if ctx.enableBench {
|
||||||
|
ctx.benchLock.Lock()
|
||||||
|
ctx.benchmark = append(ctx.benchmark, tc39BenchmarkItem{
|
||||||
|
name: name,
|
||||||
|
duration: time.Since(startTime),
|
||||||
|
})
|
||||||
|
ctx.benchLock.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctx *tc39TestCtx) init() {
|
||||||
|
ctx.prgCache = make(map[string]*Program)
|
||||||
|
ctx.sabStub = MustCompile("sabStub.js", `
|
||||||
|
Object.defineProperty(this, "SharedArrayBuffer", {
|
||||||
|
get: function() {
|
||||||
|
throw IgnorableTestError;
|
||||||
|
}
|
||||||
|
});`,
|
||||||
|
false)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctx *tc39TestCtx) compile(base, name string) (*Program, error) {
|
||||||
|
ctx.prgCacheLock.Lock()
|
||||||
|
defer ctx.prgCacheLock.Unlock()
|
||||||
|
|
||||||
|
prg := ctx.prgCache[name]
|
||||||
|
if prg == nil {
|
||||||
|
fname := path.Join(base, name)
|
||||||
|
f, err := os.Open(fname)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
b, err := io.ReadAll(f)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
str := string(b)
|
||||||
|
prg, err = Compile(name, str, false)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
ctx.prgCache[name] = prg
|
||||||
|
}
|
||||||
|
|
||||||
|
return prg, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctx *tc39TestCtx) runFile(base, name string, vm *Runtime) error {
|
||||||
|
prg, err := ctx.compile(base, name)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err = vm.RunProgram(prg)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctx *tc39TestCtx) runTC39Script(name, src string, includes []string, vm *Runtime) (err error, early bool) {
|
||||||
|
early = true
|
||||||
|
err = ctx.runFile(ctx.base, path.Join("harness", "assert.js"), vm)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = ctx.runFile(ctx.base, path.Join("harness", "sta.js"), vm)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, include := range includes {
|
||||||
|
err = ctx.runFile(ctx.base, path.Join("harness", include), vm)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var p *Program
|
||||||
|
p, err = Compile(name, src, false)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
early = false
|
||||||
|
_, err = vm.RunProgram(p)
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctx *tc39TestCtx) runTC39Tests(name string) {
|
||||||
|
files, err := os.ReadDir(path.Join(ctx.base, name))
|
||||||
|
if err != nil {
|
||||||
|
ctx.t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, file := range files {
|
||||||
|
if file.Name()[0] == '.' {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if file.IsDir() {
|
||||||
|
ctx.runTC39Tests(path.Join(name, file.Name()))
|
||||||
|
} else {
|
||||||
|
fileName := file.Name()
|
||||||
|
if strings.HasSuffix(fileName, ".js") && !strings.HasSuffix(fileName, "_FIXTURE.js") {
|
||||||
|
name := path.Join(name, fileName)
|
||||||
|
ctx.runTest(name, func(t *testing.T) {
|
||||||
|
ctx.runTC39File(name, t)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTC39(t *testing.T) {
|
||||||
|
if testing.Short() {
|
||||||
|
t.Skip()
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := os.Stat(tc39BASE); err != nil {
|
||||||
|
t.Skipf("If you want to run tc39 tests, download them from https://github.com/tc39/test262 and put into %s. See .tc39_test262_checkout.sh for the latest working commit id. (%v)", tc39BASE, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx := &tc39TestCtx{
|
||||||
|
base: tc39BASE,
|
||||||
|
}
|
||||||
|
ctx.init()
|
||||||
|
//ctx.enableBench = true
|
||||||
|
|
||||||
|
t.Run("tc39", func(t *testing.T) {
|
||||||
|
ctx.t = t
|
||||||
|
//ctx.runTC39File("test/language/types/number/8.5.1.js", t)
|
||||||
|
ctx.runTC39Tests("test/language")
|
||||||
|
ctx.runTC39Tests("test/built-ins")
|
||||||
|
ctx.runTC39Tests("test/annexB/built-ins/String/prototype/substr")
|
||||||
|
ctx.runTC39Tests("test/annexB/built-ins/String/prototype/trimLeft")
|
||||||
|
ctx.runTC39Tests("test/annexB/built-ins/String/prototype/trimRight")
|
||||||
|
ctx.runTC39Tests("test/annexB/built-ins/escape")
|
||||||
|
ctx.runTC39Tests("test/annexB/built-ins/unescape")
|
||||||
|
ctx.runTC39Tests("test/annexB/built-ins/RegExp")
|
||||||
|
|
||||||
|
ctx.flush()
|
||||||
|
})
|
||||||
|
|
||||||
|
if ctx.enableBench {
|
||||||
|
sort.Slice(ctx.benchmark, func(i, j int) bool {
|
||||||
|
return ctx.benchmark[i].duration > ctx.benchmark[j].duration
|
||||||
|
})
|
||||||
|
bench := ctx.benchmark
|
||||||
|
if len(bench) > 50 {
|
||||||
|
bench = bench[:50]
|
||||||
|
}
|
||||||
|
for _, item := range bench {
|
||||||
|
fmt.Printf("%s\t%d\n", item.name, item.duration/time.Millisecond)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
529
goja/testdata/S15.10.2.12_A1_T1.js
vendored
Normal file
529
goja/testdata/S15.10.2.12_A1_T1.js
vendored
Normal file
File diff suppressed because one or more lines are too long
2
goja/token/Makefile
Normal file
2
goja/token/Makefile
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
token_const.go: tokenfmt
|
||||||
|
./$^ | gofmt > $@
|
||||||
171
goja/token/README.markdown
Normal file
171
goja/token/README.markdown
Normal file
@ -0,0 +1,171 @@
|
|||||||
|
# token
|
||||||
|
--
|
||||||
|
import "apigo.cc/gojs/goja/token"
|
||||||
|
|
||||||
|
Package token defines constants representing the lexical tokens of JavaScript
|
||||||
|
(ECMA5).
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
```go
|
||||||
|
const (
|
||||||
|
ILLEGAL
|
||||||
|
EOF
|
||||||
|
COMMENT
|
||||||
|
KEYWORD
|
||||||
|
|
||||||
|
STRING
|
||||||
|
BOOLEAN
|
||||||
|
NULL
|
||||||
|
NUMBER
|
||||||
|
IDENTIFIER
|
||||||
|
|
||||||
|
PLUS // +
|
||||||
|
MINUS // -
|
||||||
|
MULTIPLY // *
|
||||||
|
SLASH // /
|
||||||
|
REMAINDER // %
|
||||||
|
|
||||||
|
AND // &
|
||||||
|
OR // |
|
||||||
|
EXCLUSIVE_OR // ^
|
||||||
|
SHIFT_LEFT // <<
|
||||||
|
SHIFT_RIGHT // >>
|
||||||
|
UNSIGNED_SHIFT_RIGHT // >>>
|
||||||
|
AND_NOT // &^
|
||||||
|
|
||||||
|
ADD_ASSIGN // +=
|
||||||
|
SUBTRACT_ASSIGN // -=
|
||||||
|
MULTIPLY_ASSIGN // *=
|
||||||
|
QUOTIENT_ASSIGN // /=
|
||||||
|
REMAINDER_ASSIGN // %=
|
||||||
|
|
||||||
|
AND_ASSIGN // &=
|
||||||
|
OR_ASSIGN // |=
|
||||||
|
EXCLUSIVE_OR_ASSIGN // ^=
|
||||||
|
SHIFT_LEFT_ASSIGN // <<=
|
||||||
|
SHIFT_RIGHT_ASSIGN // >>=
|
||||||
|
UNSIGNED_SHIFT_RIGHT_ASSIGN // >>>=
|
||||||
|
AND_NOT_ASSIGN // &^=
|
||||||
|
|
||||||
|
LOGICAL_AND // &&
|
||||||
|
LOGICAL_OR // ||
|
||||||
|
INCREMENT // ++
|
||||||
|
DECREMENT // --
|
||||||
|
|
||||||
|
EQUAL // ==
|
||||||
|
STRICT_EQUAL // ===
|
||||||
|
LESS // <
|
||||||
|
GREATER // >
|
||||||
|
ASSIGN // =
|
||||||
|
NOT // !
|
||||||
|
|
||||||
|
BITWISE_NOT // ~
|
||||||
|
|
||||||
|
NOT_EQUAL // !=
|
||||||
|
STRICT_NOT_EQUAL // !==
|
||||||
|
LESS_OR_EQUAL // <=
|
||||||
|
GREATER_OR_EQUAL // >=
|
||||||
|
|
||||||
|
LEFT_PARENTHESIS // (
|
||||||
|
LEFT_BRACKET // [
|
||||||
|
LEFT_BRACE // {
|
||||||
|
COMMA // ,
|
||||||
|
PERIOD // .
|
||||||
|
|
||||||
|
RIGHT_PARENTHESIS // )
|
||||||
|
RIGHT_BRACKET // ]
|
||||||
|
RIGHT_BRACE // }
|
||||||
|
SEMICOLON // ;
|
||||||
|
COLON // :
|
||||||
|
QUESTION_MARK // ?
|
||||||
|
|
||||||
|
IF
|
||||||
|
IN
|
||||||
|
DO
|
||||||
|
|
||||||
|
VAR
|
||||||
|
FOR
|
||||||
|
NEW
|
||||||
|
TRY
|
||||||
|
|
||||||
|
THIS
|
||||||
|
ELSE
|
||||||
|
CASE
|
||||||
|
VOID
|
||||||
|
WITH
|
||||||
|
|
||||||
|
WHILE
|
||||||
|
BREAK
|
||||||
|
CATCH
|
||||||
|
THROW
|
||||||
|
|
||||||
|
RETURN
|
||||||
|
TYPEOF
|
||||||
|
DELETE
|
||||||
|
SWITCH
|
||||||
|
|
||||||
|
DEFAULT
|
||||||
|
FINALLY
|
||||||
|
|
||||||
|
FUNCTION
|
||||||
|
CONTINUE
|
||||||
|
DEBUGGER
|
||||||
|
|
||||||
|
INSTANCEOF
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
#### type Token
|
||||||
|
|
||||||
|
```go
|
||||||
|
type Token int
|
||||||
|
```
|
||||||
|
|
||||||
|
Token is the set of lexical tokens in JavaScript (ECMA5).
|
||||||
|
|
||||||
|
#### func IsKeyword
|
||||||
|
|
||||||
|
```go
|
||||||
|
func IsKeyword(literal string) (Token, bool)
|
||||||
|
```
|
||||||
|
IsKeyword returns the keyword token if literal is a keyword, a KEYWORD token if
|
||||||
|
the literal is a future keyword (const, let, class, super, ...), or 0 if the
|
||||||
|
literal is not a keyword.
|
||||||
|
|
||||||
|
If the literal is a keyword, IsKeyword returns a second value indicating if the
|
||||||
|
literal is considered a future keyword in strict-mode only.
|
||||||
|
|
||||||
|
7.6.1.2 Future Reserved Words:
|
||||||
|
|
||||||
|
const
|
||||||
|
class
|
||||||
|
enum
|
||||||
|
export
|
||||||
|
extends
|
||||||
|
import
|
||||||
|
super
|
||||||
|
|
||||||
|
7.6.1.2 Future Reserved Words (strict):
|
||||||
|
|
||||||
|
implements
|
||||||
|
interface
|
||||||
|
let
|
||||||
|
package
|
||||||
|
private
|
||||||
|
protected
|
||||||
|
public
|
||||||
|
static
|
||||||
|
|
||||||
|
#### func (Token) String
|
||||||
|
|
||||||
|
```go
|
||||||
|
func (tkn Token) String() string
|
||||||
|
```
|
||||||
|
String returns the string corresponding to the token. For operators, delimiters,
|
||||||
|
and keywords the string is the actual token string (e.g., for the token PLUS,
|
||||||
|
the String() is "+"). For all other tokens the string corresponds to the token
|
||||||
|
name (e.g. for the token IDENTIFIER, the string is "IDENTIFIER").
|
||||||
|
|
||||||
|
--
|
||||||
|
**godocdown** http://github.com/robertkrimen/godocdown
|
||||||
@ -44,6 +44,10 @@ const (
|
|||||||
INCREMENT // ++
|
INCREMENT // ++
|
||||||
DECREMENT // --
|
DECREMENT // --
|
||||||
|
|
||||||
|
LOGICAL_AND_ASSIGN // &&=
|
||||||
|
LOGICAL_OR_ASSIGN // ||=
|
||||||
|
COALESCE_ASSIGN // ??=
|
||||||
|
|
||||||
EQUAL // ==
|
EQUAL // ==
|
||||||
STRICT_EQUAL // ===
|
STRICT_EQUAL // ===
|
||||||
LESS // <
|
LESS // <
|
||||||
@ -173,6 +177,9 @@ var token2string = [...]string{
|
|||||||
COALESCE: "??",
|
COALESCE: "??",
|
||||||
INCREMENT: "++",
|
INCREMENT: "++",
|
||||||
DECREMENT: "--",
|
DECREMENT: "--",
|
||||||
|
LOGICAL_AND_ASSIGN: "&&=",
|
||||||
|
LOGICAL_OR_ASSIGN: "||=",
|
||||||
|
COALESCE_ASSIGN: "??=",
|
||||||
EQUAL: "==",
|
EQUAL: "==",
|
||||||
STRICT_EQUAL: "===",
|
STRICT_EQUAL: "===",
|
||||||
LESS: "<",
|
LESS: "<",
|
||||||
|
|||||||
222
goja/token/tokenfmt
Executable file
222
goja/token/tokenfmt
Executable file
@ -0,0 +1,222 @@
|
|||||||
|
#!/usr/bin/env perl
|
||||||
|
|
||||||
|
use strict;
|
||||||
|
use warnings;
|
||||||
|
|
||||||
|
my (%token, @order, @keywords);
|
||||||
|
|
||||||
|
{
|
||||||
|
my $keywords;
|
||||||
|
my @const;
|
||||||
|
push @const, <<_END_;
|
||||||
|
package token
|
||||||
|
|
||||||
|
const(
|
||||||
|
_ Token = iota
|
||||||
|
_END_
|
||||||
|
|
||||||
|
for (split m/\n/, <<_END_) {
|
||||||
|
ILLEGAL
|
||||||
|
EOF
|
||||||
|
COMMENT
|
||||||
|
KEYWORD
|
||||||
|
|
||||||
|
STRING
|
||||||
|
BOOLEAN
|
||||||
|
NULL
|
||||||
|
NUMBER
|
||||||
|
IDENTIFIER
|
||||||
|
|
||||||
|
PLUS +
|
||||||
|
MINUS -
|
||||||
|
MULTIPLY *
|
||||||
|
SLASH /
|
||||||
|
REMAINDER %
|
||||||
|
|
||||||
|
AND &
|
||||||
|
OR |
|
||||||
|
EXCLUSIVE_OR ^
|
||||||
|
SHIFT_LEFT <<
|
||||||
|
SHIFT_RIGHT >>
|
||||||
|
UNSIGNED_SHIFT_RIGHT >>>
|
||||||
|
AND_NOT &^
|
||||||
|
|
||||||
|
ADD_ASSIGN +=
|
||||||
|
SUBTRACT_ASSIGN -=
|
||||||
|
MULTIPLY_ASSIGN *=
|
||||||
|
QUOTIENT_ASSIGN /=
|
||||||
|
REMAINDER_ASSIGN %=
|
||||||
|
|
||||||
|
AND_ASSIGN &=
|
||||||
|
OR_ASSIGN |=
|
||||||
|
EXCLUSIVE_OR_ASSIGN ^=
|
||||||
|
SHIFT_LEFT_ASSIGN <<=
|
||||||
|
SHIFT_RIGHT_ASSIGN >>=
|
||||||
|
UNSIGNED_SHIFT_RIGHT_ASSIGN >>>=
|
||||||
|
AND_NOT_ASSIGN &^=
|
||||||
|
|
||||||
|
LOGICAL_AND &&
|
||||||
|
LOGICAL_OR ||
|
||||||
|
INCREMENT ++
|
||||||
|
DECREMENT --
|
||||||
|
|
||||||
|
EQUAL ==
|
||||||
|
STRICT_EQUAL ===
|
||||||
|
LESS <
|
||||||
|
GREATER >
|
||||||
|
ASSIGN =
|
||||||
|
NOT !
|
||||||
|
|
||||||
|
BITWISE_NOT ~
|
||||||
|
|
||||||
|
NOT_EQUAL !=
|
||||||
|
STRICT_NOT_EQUAL !==
|
||||||
|
LESS_OR_EQUAL <=
|
||||||
|
GREATER_OR_EQUAL <=
|
||||||
|
|
||||||
|
LEFT_PARENTHESIS (
|
||||||
|
LEFT_BRACKET [
|
||||||
|
LEFT_BRACE {
|
||||||
|
COMMA ,
|
||||||
|
PERIOD .
|
||||||
|
|
||||||
|
RIGHT_PARENTHESIS )
|
||||||
|
RIGHT_BRACKET ]
|
||||||
|
RIGHT_BRACE }
|
||||||
|
SEMICOLON ;
|
||||||
|
COLON :
|
||||||
|
QUESTION_MARK ?
|
||||||
|
|
||||||
|
firstKeyword
|
||||||
|
IF
|
||||||
|
IN
|
||||||
|
DO
|
||||||
|
|
||||||
|
VAR
|
||||||
|
FOR
|
||||||
|
NEW
|
||||||
|
TRY
|
||||||
|
|
||||||
|
THIS
|
||||||
|
ELSE
|
||||||
|
CASE
|
||||||
|
VOID
|
||||||
|
WITH
|
||||||
|
|
||||||
|
WHILE
|
||||||
|
BREAK
|
||||||
|
CATCH
|
||||||
|
THROW
|
||||||
|
|
||||||
|
RETURN
|
||||||
|
TYPEOF
|
||||||
|
DELETE
|
||||||
|
SWITCH
|
||||||
|
|
||||||
|
DEFAULT
|
||||||
|
FINALLY
|
||||||
|
|
||||||
|
FUNCTION
|
||||||
|
CONTINUE
|
||||||
|
DEBUGGER
|
||||||
|
|
||||||
|
INSTANCEOF
|
||||||
|
lastKeyword
|
||||||
|
_END_
|
||||||
|
chomp;
|
||||||
|
|
||||||
|
next if m/^\s*#/;
|
||||||
|
|
||||||
|
my ($name, $symbol) = m/(\w+)\s*(\S+)?/;
|
||||||
|
|
||||||
|
if (defined $symbol) {
|
||||||
|
push @order, $name;
|
||||||
|
push @const, "$name // $symbol";
|
||||||
|
$token{$name} = $symbol;
|
||||||
|
} elsif (defined $name) {
|
||||||
|
$keywords ||= $name eq 'firstKeyword';
|
||||||
|
|
||||||
|
push @const, $name;
|
||||||
|
#$const[-1] .= " Token = iota" if 2 == @const;
|
||||||
|
if ($name =~ m/^([A-Z]+)/) {
|
||||||
|
push @keywords, $name if $keywords;
|
||||||
|
push @order, $name;
|
||||||
|
if ($token{SEMICOLON}) {
|
||||||
|
$token{$name} = lc $1;
|
||||||
|
} else {
|
||||||
|
$token{$name} = $name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
push @const, "";
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
push @const, ")";
|
||||||
|
print join "\n", @const, "";
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
print <<_END_;
|
||||||
|
|
||||||
|
var token2string = [...]string{
|
||||||
|
_END_
|
||||||
|
for my $name (@order) {
|
||||||
|
print "$name: \"$token{$name}\",\n";
|
||||||
|
}
|
||||||
|
print <<_END_;
|
||||||
|
}
|
||||||
|
_END_
|
||||||
|
|
||||||
|
print <<_END_;
|
||||||
|
|
||||||
|
var keywordTable = map[string]_keyword{
|
||||||
|
_END_
|
||||||
|
for my $name (@keywords) {
|
||||||
|
print <<_END_
|
||||||
|
"@{[ lc $name ]}": _keyword{
|
||||||
|
token: $name,
|
||||||
|
},
|
||||||
|
_END_
|
||||||
|
}
|
||||||
|
|
||||||
|
for my $name (qw/
|
||||||
|
const
|
||||||
|
class
|
||||||
|
enum
|
||||||
|
export
|
||||||
|
extends
|
||||||
|
import
|
||||||
|
super
|
||||||
|
/) {
|
||||||
|
print <<_END_
|
||||||
|
"$name": _keyword{
|
||||||
|
token: KEYWORD,
|
||||||
|
futureKeyword: true,
|
||||||
|
},
|
||||||
|
_END_
|
||||||
|
}
|
||||||
|
|
||||||
|
for my $name (qw/
|
||||||
|
implements
|
||||||
|
interface
|
||||||
|
let
|
||||||
|
package
|
||||||
|
private
|
||||||
|
protected
|
||||||
|
public
|
||||||
|
static
|
||||||
|
/) {
|
||||||
|
print <<_END_
|
||||||
|
"$name": _keyword{
|
||||||
|
token: KEYWORD,
|
||||||
|
futureKeyword: true,
|
||||||
|
strict: true,
|
||||||
|
},
|
||||||
|
_END_
|
||||||
|
}
|
||||||
|
|
||||||
|
print <<_END_;
|
||||||
|
}
|
||||||
|
_END_
|
||||||
|
}
|
||||||
544
goja/typedarrays_test.go
Normal file
544
goja/typedarrays_test.go
Normal file
@ -0,0 +1,544 @@
|
|||||||
|
package goja
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestUint16ArrayObject(t *testing.T) {
|
||||||
|
vm := New()
|
||||||
|
buf := vm._newArrayBuffer(vm.global.ArrayBufferPrototype, nil)
|
||||||
|
buf.data = make([]byte, 16)
|
||||||
|
if nativeEndian == littleEndian {
|
||||||
|
buf.data[2] = 0xFE
|
||||||
|
buf.data[3] = 0xCA
|
||||||
|
} else {
|
||||||
|
buf.data[2] = 0xCA
|
||||||
|
buf.data[3] = 0xFE
|
||||||
|
}
|
||||||
|
a := vm.newUint16ArrayObject(buf, 1, 1, nil)
|
||||||
|
v := a.getIdx(valueInt(0), nil)
|
||||||
|
if v != valueInt(0xCAFE) {
|
||||||
|
t.Fatalf("v: %v", v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestArrayBufferGoWrapper(t *testing.T) {
|
||||||
|
vm := New()
|
||||||
|
data := []byte{0xAA, 0xBB}
|
||||||
|
buf := vm.NewArrayBuffer(data)
|
||||||
|
vm.Set("buf", buf)
|
||||||
|
_, err := vm.RunString(`
|
||||||
|
var a = new Uint8Array(buf);
|
||||||
|
if (a.length !== 2 || a[0] !== 0xAA || a[1] !== 0xBB) {
|
||||||
|
throw new Error(a);
|
||||||
|
}
|
||||||
|
`)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
ret, err := vm.RunString(`
|
||||||
|
var b = Uint8Array.of(0xCC, 0xDD);
|
||||||
|
b.buffer;
|
||||||
|
`)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
buf1 := ret.Export().(ArrayBuffer)
|
||||||
|
data1 := buf1.Bytes()
|
||||||
|
if len(data1) != 2 || data1[0] != 0xCC || data1[1] != 0xDD {
|
||||||
|
t.Fatal(data1)
|
||||||
|
}
|
||||||
|
if buf1.Detached() {
|
||||||
|
t.Fatal("buf1.Detached() returned true")
|
||||||
|
}
|
||||||
|
if !buf1.Detach() {
|
||||||
|
t.Fatal("buf1.Detach() returned false")
|
||||||
|
}
|
||||||
|
if !buf1.Detached() {
|
||||||
|
t.Fatal("buf1.Detached() returned false")
|
||||||
|
}
|
||||||
|
_, err = vm.RunString(`
|
||||||
|
if (b[0] !== undefined) {
|
||||||
|
throw new Error("b[0] !== undefined");
|
||||||
|
}
|
||||||
|
`)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTypedArrayIdx(t *testing.T) {
|
||||||
|
const SCRIPT = `
|
||||||
|
var a = new Uint8Array(1);
|
||||||
|
|
||||||
|
// 32-bit integer overflow, should not panic on 32-bit architectures
|
||||||
|
if (a[4294967297] !== undefined) {
|
||||||
|
throw new Error("4294967297");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Canonical non-integer
|
||||||
|
a["Infinity"] = 8;
|
||||||
|
if (a["Infinity"] !== undefined) {
|
||||||
|
throw new Error("Infinity");
|
||||||
|
}
|
||||||
|
a["NaN"] = 1;
|
||||||
|
if (a["NaN"] !== undefined) {
|
||||||
|
throw new Error("NaN");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Non-canonical integer
|
||||||
|
a["00"] = "00";
|
||||||
|
if (a["00"] !== "00") {
|
||||||
|
throw new Error("00");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Non-canonical non-integer
|
||||||
|
a["1e-3"] = "1e-3";
|
||||||
|
if (a["1e-3"] !== "1e-3") {
|
||||||
|
throw new Error("1e-3");
|
||||||
|
}
|
||||||
|
if (a["0.001"] !== undefined) {
|
||||||
|
throw new Error("0.001");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Negative zero
|
||||||
|
a["-0"] = 88;
|
||||||
|
if (a["-0"] !== undefined) {
|
||||||
|
throw new Error("-0");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (a[0] !== 0) {
|
||||||
|
throw new Error("0");
|
||||||
|
}
|
||||||
|
|
||||||
|
a["9007199254740992"] = 1;
|
||||||
|
if (a["9007199254740992"] !== undefined) {
|
||||||
|
throw new Error("9007199254740992");
|
||||||
|
}
|
||||||
|
a["-9007199254740992"] = 1;
|
||||||
|
if (a["-9007199254740992"] !== undefined) {
|
||||||
|
throw new Error("-9007199254740992");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Safe integer overflow, not canonical (Number("9007199254740993") === 9007199254740992)
|
||||||
|
a["9007199254740993"] = 1;
|
||||||
|
if (a["9007199254740993"] !== 1) {
|
||||||
|
throw new Error("9007199254740993");
|
||||||
|
}
|
||||||
|
a["-9007199254740993"] = 1;
|
||||||
|
if (a["-9007199254740993"] !== 1) {
|
||||||
|
throw new Error("-9007199254740993");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Safe integer overflow, canonical Number("9007199254740994") == 9007199254740994
|
||||||
|
a["9007199254740994"] = 1;
|
||||||
|
if (a["9007199254740994"] !== undefined) {
|
||||||
|
throw new Error("9007199254740994");
|
||||||
|
}
|
||||||
|
a["-9007199254740994"] = 1;
|
||||||
|
if (a["-9007199254740994"] !== undefined) {
|
||||||
|
throw new Error("-9007199254740994");
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
testScript(SCRIPT, _undefined, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTypedArraySetDetachedBuffer(t *testing.T) {
|
||||||
|
const SCRIPT = `
|
||||||
|
let sample = new Uint8Array([42]);
|
||||||
|
$DETACHBUFFER(sample.buffer);
|
||||||
|
sample[0] = 1;
|
||||||
|
|
||||||
|
assert.sameValue(sample[0], undefined, 'sample[0] = 1 is undefined');
|
||||||
|
sample['1.1'] = 1;
|
||||||
|
assert.sameValue(sample['1.1'], undefined, 'sample[\'1.1\'] = 1 is undefined');
|
||||||
|
sample['-0'] = 1;
|
||||||
|
assert.sameValue(sample['-0'], undefined, 'sample[\'-0\'] = 1 is undefined');
|
||||||
|
sample['-1'] = 1;
|
||||||
|
assert.sameValue(sample['-1'], undefined, 'sample[\'-1\'] = 1 is undefined');
|
||||||
|
sample['1'] = 1;
|
||||||
|
assert.sameValue(sample['1'], undefined, 'sample[\'1\'] = 1 is undefined');
|
||||||
|
sample['2'] = 1;
|
||||||
|
assert.sameValue(sample['2'], undefined, 'sample[\'2\'] = 1 is undefined');
|
||||||
|
`
|
||||||
|
vm := New()
|
||||||
|
vm.Set("$DETACHBUFFER", func(buf *ArrayBuffer) {
|
||||||
|
buf.Detach()
|
||||||
|
})
|
||||||
|
vm.testScriptWithTestLib(SCRIPT, _undefined, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTypedArrayDefinePropDetachedBuffer(t *testing.T) {
|
||||||
|
const SCRIPT = `
|
||||||
|
var desc = {
|
||||||
|
value: 0,
|
||||||
|
configurable: false,
|
||||||
|
enumerable: true,
|
||||||
|
writable: true
|
||||||
|
};
|
||||||
|
|
||||||
|
var obj = {
|
||||||
|
valueOf: function() {
|
||||||
|
throw new Error("valueOf() was called");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let sample = new Uint8Array(42);
|
||||||
|
$DETACHBUFFER(sample.buffer);
|
||||||
|
|
||||||
|
assert.sameValue(
|
||||||
|
Reflect.defineProperty(sample, "0", desc),
|
||||||
|
false,
|
||||||
|
'Reflect.defineProperty(sample, "0", {value: 0, configurable: false, enumerable: true, writable: true} ) must return false'
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.sameValue(
|
||||||
|
Reflect.defineProperty(sample, "-1", desc),
|
||||||
|
false,
|
||||||
|
'Reflect.defineProperty(sample, "-1", {value: 0, configurable: false, enumerable: true, writable: true} ) must return false'
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.sameValue(
|
||||||
|
Reflect.defineProperty(sample, "1.1", desc),
|
||||||
|
false,
|
||||||
|
'Reflect.defineProperty(sample, "1.1", {value: 0, configurable: false, enumerable: true, writable: true} ) must return false'
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.sameValue(
|
||||||
|
Reflect.defineProperty(sample, "-0", desc),
|
||||||
|
false,
|
||||||
|
'Reflect.defineProperty(sample, "-0", {value: 0, configurable: false, enumerable: true, writable: true} ) must return false'
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.sameValue(
|
||||||
|
Reflect.defineProperty(sample, "2", {
|
||||||
|
configurable: true,
|
||||||
|
enumerable: true,
|
||||||
|
writable: true,
|
||||||
|
value: obj
|
||||||
|
}),
|
||||||
|
false,
|
||||||
|
'Reflect.defineProperty(sample, "2", {configurable: true, enumerable: true, writable: true, value: obj}) must return false'
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.sameValue(
|
||||||
|
Reflect.defineProperty(sample, "3", {
|
||||||
|
configurable: false,
|
||||||
|
enumerable: false,
|
||||||
|
writable: true,
|
||||||
|
value: obj
|
||||||
|
}),
|
||||||
|
false,
|
||||||
|
'Reflect.defineProperty(sample, "3", {configurable: false, enumerable: false, writable: true, value: obj}) must return false'
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.sameValue(
|
||||||
|
Reflect.defineProperty(sample, "4", {
|
||||||
|
writable: false,
|
||||||
|
configurable: false,
|
||||||
|
enumerable: true,
|
||||||
|
value: obj
|
||||||
|
}),
|
||||||
|
false,
|
||||||
|
'Reflect.defineProperty("new TA(42)", "4", {writable: false, configurable: false, enumerable: true, value: obj}) must return false'
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.sameValue(
|
||||||
|
Reflect.defineProperty(sample, "42", desc),
|
||||||
|
false,
|
||||||
|
'Reflect.defineProperty(sample, "42", {value: 0, configurable: false, enumerable: true, writable: true} ) must return false'
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.sameValue(
|
||||||
|
Reflect.defineProperty(sample, "43", desc),
|
||||||
|
false,
|
||||||
|
'Reflect.defineProperty(sample, "43", {value: 0, configurable: false, enumerable: true, writable: true} ) must return false'
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.sameValue(
|
||||||
|
Reflect.defineProperty(sample, "5", {
|
||||||
|
get: function() {}
|
||||||
|
}),
|
||||||
|
false,
|
||||||
|
'Reflect.defineProperty(sample, "5", {get: function() {}}) must return false'
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.sameValue(
|
||||||
|
Reflect.defineProperty(sample, "6", {
|
||||||
|
configurable: false,
|
||||||
|
enumerable: true,
|
||||||
|
writable: true
|
||||||
|
}),
|
||||||
|
false,
|
||||||
|
'Reflect.defineProperty(sample, "6", {configurable: false, enumerable: true, writable: true}) must return false'
|
||||||
|
);
|
||||||
|
`
|
||||||
|
vm := New()
|
||||||
|
vm.Set("$DETACHBUFFER", func(buf *ArrayBuffer) {
|
||||||
|
buf.Detach()
|
||||||
|
})
|
||||||
|
vm.testScriptWithTestLib(SCRIPT, _undefined, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTypedArrayDefineProperty(t *testing.T) {
|
||||||
|
const SCRIPT = `
|
||||||
|
var a = new Uint8Array(1);
|
||||||
|
|
||||||
|
assert.throws(TypeError, function() {
|
||||||
|
Object.defineProperty(a, "1", {value: 1});
|
||||||
|
});
|
||||||
|
assert.sameValue(Reflect.defineProperty(a, "1", {value: 1}), false, "1");
|
||||||
|
|
||||||
|
assert.throws(TypeError, function() {
|
||||||
|
Object.defineProperty(a, "Infinity", {value: 8});
|
||||||
|
});
|
||||||
|
assert.sameValue(Reflect.defineProperty(a, "Infinity", {value: 8}), false, "Infinity");
|
||||||
|
|
||||||
|
Object.defineProperty(a, "test", {value: "passed"});
|
||||||
|
assert.sameValue(a.test, "passed", "string property");
|
||||||
|
|
||||||
|
assert.throws(TypeError, function() {
|
||||||
|
Object.defineProperty(a, "0", {value: 1, writable: false});
|
||||||
|
}, "define non-writable");
|
||||||
|
|
||||||
|
assert.throws(TypeError, function() {
|
||||||
|
Object.defineProperty(a, "0", {get() { return 1; }});
|
||||||
|
}, "define accessor");
|
||||||
|
|
||||||
|
var sample = new Uint8Array([42, 42]);
|
||||||
|
|
||||||
|
assert.sameValue(
|
||||||
|
Reflect.defineProperty(sample, "0", {
|
||||||
|
value: 8,
|
||||||
|
configurable: true,
|
||||||
|
enumerable: true,
|
||||||
|
writable: true
|
||||||
|
}),
|
||||||
|
true
|
||||||
|
);
|
||||||
|
|
||||||
|
assert.sameValue(sample[0], 8, "property value was set");
|
||||||
|
let descriptor0 = Object.getOwnPropertyDescriptor(sample, "0");
|
||||||
|
assert.sameValue(descriptor0.value, 8);
|
||||||
|
assert.sameValue(descriptor0.configurable, true, "configurable");
|
||||||
|
assert.sameValue(descriptor0.enumerable, true);
|
||||||
|
assert.sameValue(descriptor0.writable, true);
|
||||||
|
`
|
||||||
|
testScriptWithTestLib(SCRIPT, _undefined, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTypedArrayGetInvalidIndex(t *testing.T) {
|
||||||
|
const SCRIPT = `
|
||||||
|
var TypedArray = Object.getPrototypeOf(Int8Array);
|
||||||
|
var proto = TypedArray.prototype;
|
||||||
|
Object.defineProperty(proto, "1", {
|
||||||
|
get: function() {
|
||||||
|
throw new Error("OrdinaryGet was called!");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
var a = new Uint8Array(1);
|
||||||
|
assert.sameValue(a[1], undefined);
|
||||||
|
assert.sameValue(a["1"], undefined);
|
||||||
|
`
|
||||||
|
testScriptWithTestLib(SCRIPT, _undefined, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestExportArrayBufferToBytes(t *testing.T) {
|
||||||
|
vm := New()
|
||||||
|
bb := []byte("test")
|
||||||
|
ab := vm.NewArrayBuffer(bb)
|
||||||
|
var b []byte
|
||||||
|
err := vm.ExportTo(vm.ToValue(ab), &b)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if !bytes.Equal(b, bb) {
|
||||||
|
t.Fatal("Not equal")
|
||||||
|
}
|
||||||
|
|
||||||
|
err = vm.ExportTo(vm.ToValue(123), &b)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("expected error")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTypedArrayExport(t *testing.T) {
|
||||||
|
vm := New()
|
||||||
|
|
||||||
|
t.Run("uint8", func(t *testing.T) {
|
||||||
|
v, err := vm.RunString("new Uint8Array([1, 2])")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if a, ok := v.Export().([]uint8); ok {
|
||||||
|
if len(a) != 2 || a[0] != 1 || a[1] != 2 {
|
||||||
|
t.Fatal(a)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
t.Fatal("Wrong export type")
|
||||||
|
}
|
||||||
|
_, err = vm.RunString(`{
|
||||||
|
let a = new Uint8Array([1, 2]);
|
||||||
|
if (a[0] !== 1 || a[1] !== 2) {
|
||||||
|
throw new Error(a);
|
||||||
|
}
|
||||||
|
}`)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("uint8-slice", func(t *testing.T) {
|
||||||
|
v, err := vm.RunString(`{
|
||||||
|
const buf = new Uint8Array([1, 2]).buffer;
|
||||||
|
new Uint8Array(buf, 1, 1);
|
||||||
|
}`)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if a, ok := v.Export().([]uint8); ok {
|
||||||
|
if len(a) != 1 || a[0] != 2 {
|
||||||
|
t.Fatal(a)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
t.Fatal("Wrong export type")
|
||||||
|
}
|
||||||
|
_, err = vm.RunString(`{
|
||||||
|
let a = new Uint8Array([1, 2]);
|
||||||
|
if (a[0] !== 1 || a[1] !== 2) {
|
||||||
|
throw new Error(a);
|
||||||
|
}
|
||||||
|
}`)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("int8", func(t *testing.T) {
|
||||||
|
v, err := vm.RunString("new Int8Array([1, -2])")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if a, ok := v.Export().([]int8); ok {
|
||||||
|
if len(a) != 2 || a[0] != 1 || a[1] != -2 {
|
||||||
|
t.Fatal(a)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
t.Fatal("Wrong export type")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("uint16", func(t *testing.T) {
|
||||||
|
v, err := vm.RunString("new Uint16Array([1, 63000])")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if a, ok := v.Export().([]uint16); ok {
|
||||||
|
if len(a) != 2 || a[0] != 1 || a[1] != 63000 {
|
||||||
|
t.Fatal(a)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
t.Fatal("Wrong export type")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("int16", func(t *testing.T) {
|
||||||
|
v, err := vm.RunString("new Int16Array([1, -31000])")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if a, ok := v.Export().([]int16); ok {
|
||||||
|
if len(a) != 2 || a[0] != 1 || a[1] != -31000 {
|
||||||
|
t.Fatal(a)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
t.Fatal("Wrong export type")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("uint32", func(t *testing.T) {
|
||||||
|
v, err := vm.RunString("new Uint32Array([1, 123456])")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if a, ok := v.Export().([]uint32); ok {
|
||||||
|
if len(a) != 2 || a[0] != 1 || a[1] != 123456 {
|
||||||
|
t.Fatal(a)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
t.Fatal("Wrong export type")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("int32", func(t *testing.T) {
|
||||||
|
v, err := vm.RunString("new Int32Array([1, -123456])")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if a, ok := v.Export().([]int32); ok {
|
||||||
|
if len(a) != 2 || a[0] != 1 || a[1] != -123456 {
|
||||||
|
t.Fatal(a)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
t.Fatal("Wrong export type")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("float32", func(t *testing.T) {
|
||||||
|
v, err := vm.RunString("new Float32Array([1, -1.23456])")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if a, ok := v.Export().([]float32); ok {
|
||||||
|
if len(a) != 2 || a[0] != 1 || a[1] != -1.23456 {
|
||||||
|
t.Fatal(a)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
t.Fatal("Wrong export type")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("float64", func(t *testing.T) {
|
||||||
|
v, err := vm.RunString("new Float64Array([1, -1.23456789])")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if a, ok := v.Export().([]float64); ok {
|
||||||
|
if len(a) != 2 || a[0] != 1 || a[1] != -1.23456789 {
|
||||||
|
t.Fatal(a)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
t.Fatal("Wrong export type")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("bigint64", func(t *testing.T) {
|
||||||
|
v, err := vm.RunString("new BigInt64Array([18446744073709551617n, 2n])")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if a, ok := v.Export().([]int64); ok {
|
||||||
|
if len(a) != 2 || a[0] != 1 || a[1] != 2 {
|
||||||
|
t.Fatal(a)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
t.Fatal("Wrong export type")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("biguint64", func(t *testing.T) {
|
||||||
|
v, err := vm.RunString("new BigUint64Array([18446744073709551617n, 2n])")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if a, ok := v.Export().([]uint64); ok {
|
||||||
|
if len(a) != 2 || a[0] != 1 || a[1] != 2 {
|
||||||
|
t.Fatal(a)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
t.Fatal("Wrong export type")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
16
goja/unistring/string_test.go
Normal file
16
goja/unistring/string_test.go
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
package unistring
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
func TestString_AsUtf16(t *testing.T) {
|
||||||
|
const str = "más"
|
||||||
|
s := NewFromString(str)
|
||||||
|
|
||||||
|
if b := s.AsUtf16(); len(b) != 4 || b[0] != BOM {
|
||||||
|
t.Fatal(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.String() != str {
|
||||||
|
t.Fatal(s)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -53,6 +53,7 @@ var (
|
|||||||
reflectTypeArrayPtr = reflect.TypeOf((*[]interface{})(nil))
|
reflectTypeArrayPtr = reflect.TypeOf((*[]interface{})(nil))
|
||||||
reflectTypeString = reflect.TypeOf("")
|
reflectTypeString = reflect.TypeOf("")
|
||||||
reflectTypeFunc = reflect.TypeOf((func(FunctionCall) Value)(nil))
|
reflectTypeFunc = reflect.TypeOf((func(FunctionCall) Value)(nil))
|
||||||
|
reflectTypeCtor = reflect.TypeOf((func(ConstructorCall) *Object)(nil))
|
||||||
reflectTypeError = reflect.TypeOf((*error)(nil)).Elem()
|
reflectTypeError = reflect.TypeOf((*error)(nil)).Elem()
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -764,6 +765,8 @@ func (o *Object) baseObject(*Runtime) *Object {
|
|||||||
// Export the Object to a plain Go type.
|
// Export the Object to a plain Go type.
|
||||||
// If the Object is a wrapped Go value (created using ToValue()) returns the original value.
|
// If the Object is a wrapped Go value (created using ToValue()) returns the original value.
|
||||||
//
|
//
|
||||||
|
// If the Object is a class function, returns func(ConstructorCall) *Object. See the note about exceptions below.
|
||||||
|
//
|
||||||
// If the Object is a function, returns func(FunctionCall) Value. Note that exceptions thrown inside the function
|
// If the Object is a function, returns func(FunctionCall) Value. Note that exceptions thrown inside the function
|
||||||
// result in panics, which can also leave the Runtime in an unusable state. Therefore, these values should only
|
// result in panics, which can also leave the Runtime in an unusable state. Therefore, these values should only
|
||||||
// be used inside another ES function implemented in Go. For calling a function from Go, use AssertFunction() or
|
// be used inside another ES function implemented in Go. For calling a function from Go, use AssertFunction() or
|
||||||
|
|||||||
69
goja/vm.go
69
goja/vm.go
@ -259,7 +259,14 @@ type objStrRef struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (r *objStrRef) get() Value {
|
func (r *objStrRef) get() Value {
|
||||||
return r.base.self.getStr(r.name, r.this)
|
if v := r.base.self.getStr(r.name, r.this); v != nil {
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
if r.binding {
|
||||||
|
rt := r.base.runtime
|
||||||
|
panic(rt.newReferenceError(r.name))
|
||||||
|
}
|
||||||
|
return _undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *objStrRef) set(v Value) {
|
func (r *objStrRef) set(v Value) {
|
||||||
@ -3382,12 +3389,7 @@ var getValue _getValue
|
|||||||
|
|
||||||
func (_getValue) exec(vm *vm) {
|
func (_getValue) exec(vm *vm) {
|
||||||
ref := vm.refStack[len(vm.refStack)-1]
|
ref := vm.refStack[len(vm.refStack)-1]
|
||||||
if v := ref.get(); v != nil {
|
vm.push(nilSafe(ref.get()))
|
||||||
vm.push(v)
|
|
||||||
} else {
|
|
||||||
vm.throw(vm.r.newReferenceError(ref.refname()))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
vm.pc++
|
vm.pc++
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3404,6 +3406,17 @@ func (_putValue) exec(vm *vm) {
|
|||||||
vm.pc++
|
vm.pc++
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type _popRef struct{}
|
||||||
|
|
||||||
|
var popRef _popRef
|
||||||
|
|
||||||
|
func (_popRef) exec(vm *vm) {
|
||||||
|
l := len(vm.refStack) - 1
|
||||||
|
vm.refStack[l] = nil
|
||||||
|
vm.refStack = vm.refStack[:l]
|
||||||
|
vm.pc++
|
||||||
|
}
|
||||||
|
|
||||||
type _putValueP struct{}
|
type _putValueP struct{}
|
||||||
|
|
||||||
var putValueP _putValueP
|
var putValueP _putValueP
|
||||||
@ -4282,9 +4295,9 @@ func (b *bindGlobal) exec(vm *vm) {
|
|||||||
vm.pc++
|
vm.pc++
|
||||||
}
|
}
|
||||||
|
|
||||||
type jne int32
|
type jneP int32
|
||||||
|
|
||||||
func (j jne) exec(vm *vm) {
|
func (j jneP) exec(vm *vm) {
|
||||||
vm.sp--
|
vm.sp--
|
||||||
if !vm.stack[vm.sp].ToBoolean() {
|
if !vm.stack[vm.sp].ToBoolean() {
|
||||||
vm.pc += int(j)
|
vm.pc += int(j)
|
||||||
@ -4293,20 +4306,20 @@ func (j jne) exec(vm *vm) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type jeqP int32
|
||||||
|
|
||||||
|
func (j jeqP) exec(vm *vm) {
|
||||||
|
vm.sp--
|
||||||
|
if vm.stack[vm.sp].ToBoolean() {
|
||||||
|
vm.pc += int(j)
|
||||||
|
} else {
|
||||||
|
vm.pc++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
type jeq int32
|
type jeq int32
|
||||||
|
|
||||||
func (j jeq) exec(vm *vm) {
|
func (j jeq) exec(vm *vm) {
|
||||||
vm.sp--
|
|
||||||
if vm.stack[vm.sp].ToBoolean() {
|
|
||||||
vm.pc += int(j)
|
|
||||||
} else {
|
|
||||||
vm.pc++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type jeq1 int32
|
|
||||||
|
|
||||||
func (j jeq1) exec(vm *vm) {
|
|
||||||
if vm.stack[vm.sp-1].ToBoolean() {
|
if vm.stack[vm.sp-1].ToBoolean() {
|
||||||
vm.pc += int(j)
|
vm.pc += int(j)
|
||||||
} else {
|
} else {
|
||||||
@ -4315,9 +4328,9 @@ func (j jeq1) exec(vm *vm) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type jneq1 int32
|
type jne int32
|
||||||
|
|
||||||
func (j jneq1) exec(vm *vm) {
|
func (j jne) exec(vm *vm) {
|
||||||
if !vm.stack[vm.sp-1].ToBoolean() {
|
if !vm.stack[vm.sp-1].ToBoolean() {
|
||||||
vm.pc += int(j)
|
vm.pc += int(j)
|
||||||
} else {
|
} else {
|
||||||
@ -4387,6 +4400,18 @@ func (j jcoalesc) exec(vm *vm) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type jcoalescP int32
|
||||||
|
|
||||||
|
func (j jcoalescP) exec(vm *vm) {
|
||||||
|
vm.sp--
|
||||||
|
switch vm.stack[vm.sp] {
|
||||||
|
case _undefined, _null:
|
||||||
|
vm.pc++
|
||||||
|
default:
|
||||||
|
vm.pc += int(j)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
type _not struct{}
|
type _not struct{}
|
||||||
|
|
||||||
var not _not
|
var not _not
|
||||||
|
|||||||
286
goja/vm_test.go
Normal file
286
goja/vm_test.go
Normal file
@ -0,0 +1,286 @@
|
|||||||
|
package goja
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"apigo.cc/gojs/goja/file"
|
||||||
|
"apigo.cc/gojs/goja/parser"
|
||||||
|
"apigo.cc/gojs/goja/unistring"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestTaggedTemplateArgExport(t *testing.T) {
|
||||||
|
vm := New()
|
||||||
|
vm.Set("f", func(v Value) {
|
||||||
|
v.Export()
|
||||||
|
})
|
||||||
|
vm.RunString("f`test`")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestVM1(t *testing.T) {
|
||||||
|
r := &Runtime{}
|
||||||
|
r.init()
|
||||||
|
|
||||||
|
vm := r.vm
|
||||||
|
|
||||||
|
vm.prg = &Program{
|
||||||
|
src: file.NewFile("dummy", "", 1),
|
||||||
|
code: []instruction{
|
||||||
|
&bindGlobal{vars: []unistring.String{"v"}},
|
||||||
|
newObject,
|
||||||
|
setGlobal("v"),
|
||||||
|
loadVal{asciiString("test")},
|
||||||
|
loadVal{valueInt(3)},
|
||||||
|
loadVal{valueInt(2)},
|
||||||
|
add,
|
||||||
|
setElem,
|
||||||
|
pop,
|
||||||
|
loadDynamic("v"),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
vm.run()
|
||||||
|
|
||||||
|
rv := vm.pop()
|
||||||
|
|
||||||
|
if obj, ok := rv.(*Object); ok {
|
||||||
|
if v := obj.self.getStr("test", nil).ToInteger(); v != 5 {
|
||||||
|
t.Fatalf("Unexpected property value: %v", v)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
t.Fatalf("Unexpected result: %v", rv)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEvalVar(t *testing.T) {
|
||||||
|
const SCRIPT = `
|
||||||
|
function test() {
|
||||||
|
var a;
|
||||||
|
return eval("var a = 'yes'; var z = 'no'; a;") === "yes" && a === "yes";
|
||||||
|
}
|
||||||
|
test();
|
||||||
|
`
|
||||||
|
|
||||||
|
testScript(SCRIPT, valueTrue, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestResolveMixedStack1(t *testing.T) {
|
||||||
|
const SCRIPT = `
|
||||||
|
function test(arg) {
|
||||||
|
var a = 1;
|
||||||
|
var scope = {};
|
||||||
|
(function() {return arg})(); // move arguments to stash
|
||||||
|
with (scope) {
|
||||||
|
a++; // resolveMixedStack1 here
|
||||||
|
return a + arg;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
test(40);
|
||||||
|
`
|
||||||
|
|
||||||
|
testScript(SCRIPT, valueInt(42), t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewArrayFromIterClosed(t *testing.T) {
|
||||||
|
const SCRIPT = `
|
||||||
|
const [a, ...other] = [];
|
||||||
|
assert.sameValue(a, undefined);
|
||||||
|
assert(Array.isArray(other));
|
||||||
|
assert.sameValue(other.length, 0);
|
||||||
|
`
|
||||||
|
testScriptWithTestLib(SCRIPT, _undefined, t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkVmNOP2(b *testing.B) {
|
||||||
|
prg := []func(*vm){
|
||||||
|
//loadVal(0).exec,
|
||||||
|
//loadVal(1).exec,
|
||||||
|
//add.exec,
|
||||||
|
jump(1).exec,
|
||||||
|
}
|
||||||
|
|
||||||
|
r := &Runtime{}
|
||||||
|
r.init()
|
||||||
|
|
||||||
|
vm := r.vm
|
||||||
|
vm.prg = &Program{}
|
||||||
|
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
vm.pc = 0
|
||||||
|
for !vm.halted() {
|
||||||
|
prg[vm.pc](vm)
|
||||||
|
}
|
||||||
|
//vm.sp--
|
||||||
|
/*r := vm.pop()
|
||||||
|
if r.ToInteger() != 5 {
|
||||||
|
b.Fatalf("Unexpected result: %+v", r)
|
||||||
|
}
|
||||||
|
if vm.sp != 0 {
|
||||||
|
b.Fatalf("Unexpected sp: %d", vm.sp)
|
||||||
|
}*/
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkVmNOP(b *testing.B) {
|
||||||
|
r := &Runtime{}
|
||||||
|
r.init()
|
||||||
|
|
||||||
|
vm := r.vm
|
||||||
|
vm.prg = &Program{
|
||||||
|
code: []instruction{
|
||||||
|
jump(1),
|
||||||
|
//jump(1),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
vm.pc = 0
|
||||||
|
vm.run()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkVm1(b *testing.B) {
|
||||||
|
r := &Runtime{}
|
||||||
|
r.init()
|
||||||
|
|
||||||
|
vm := r.vm
|
||||||
|
|
||||||
|
//ins1 := loadVal1(0)
|
||||||
|
//ins2 := loadVal1(1)
|
||||||
|
|
||||||
|
vm.prg = &Program{
|
||||||
|
code: []instruction{
|
||||||
|
loadVal{valueInt(2)},
|
||||||
|
loadVal{valueInt(3)},
|
||||||
|
add,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
vm.pc = 0
|
||||||
|
vm.run()
|
||||||
|
r := vm.pop()
|
||||||
|
if r.ToInteger() != 5 {
|
||||||
|
b.Fatalf("Unexpected result: %+v", r)
|
||||||
|
}
|
||||||
|
if vm.sp != 0 {
|
||||||
|
b.Fatalf("Unexpected sp: %d", vm.sp)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkFib(b *testing.B) {
|
||||||
|
const TEST_FIB = `
|
||||||
|
function fib(n) {
|
||||||
|
if (n < 2) return n;
|
||||||
|
return fib(n - 2) + fib(n - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
fib(35);
|
||||||
|
`
|
||||||
|
b.StopTimer()
|
||||||
|
prg, err := parser.ParseFile(nil, "test.js", TEST_FIB, 0)
|
||||||
|
if err != nil {
|
||||||
|
b.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
c := newCompiler()
|
||||||
|
c.compile(prg, false, true, nil)
|
||||||
|
c.p.dumpCode(b.Logf)
|
||||||
|
|
||||||
|
r := &Runtime{}
|
||||||
|
r.init()
|
||||||
|
|
||||||
|
vm := r.vm
|
||||||
|
|
||||||
|
var expectedResult Value = valueInt(9227465)
|
||||||
|
|
||||||
|
b.StartTimer()
|
||||||
|
|
||||||
|
vm.prg = c.p
|
||||||
|
vm.run()
|
||||||
|
v := vm.result
|
||||||
|
|
||||||
|
b.Logf("stack size: %d", len(vm.stack))
|
||||||
|
b.Logf("stashAllocs: %d", vm.stashAllocs)
|
||||||
|
|
||||||
|
if !v.SameAs(expectedResult) {
|
||||||
|
b.Fatalf("Result: %+v, expected: %+v", v, expectedResult)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkEmptyLoop(b *testing.B) {
|
||||||
|
const SCRIPT = `
|
||||||
|
function f() {
|
||||||
|
for (var i = 0; i < 100; i++) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
f()
|
||||||
|
`
|
||||||
|
b.StopTimer()
|
||||||
|
vm := New()
|
||||||
|
prg := MustCompile("test.js", SCRIPT, false)
|
||||||
|
// prg.dumpCode(log.Printf)
|
||||||
|
b.StartTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
vm.RunProgram(prg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkVMAdd(b *testing.B) {
|
||||||
|
vm := &vm{}
|
||||||
|
vm.stack = append(vm.stack, nil, nil)
|
||||||
|
vm.sp = len(vm.stack)
|
||||||
|
|
||||||
|
var v1 Value = valueInt(3)
|
||||||
|
var v2 Value = valueInt(5)
|
||||||
|
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
vm.stack[0] = v1
|
||||||
|
vm.stack[1] = v2
|
||||||
|
add.exec(vm)
|
||||||
|
vm.sp++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkFuncCall(b *testing.B) {
|
||||||
|
const SCRIPT = `
|
||||||
|
function f(a, b, c, d) {
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
b.StopTimer()
|
||||||
|
|
||||||
|
vm := New()
|
||||||
|
prg := MustCompile("test.js", SCRIPT, false)
|
||||||
|
|
||||||
|
vm.RunProgram(prg)
|
||||||
|
if f, ok := AssertFunction(vm.Get("f")); ok {
|
||||||
|
b.StartTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
f(nil, nil, intToValue(1), intToValue(2), intToValue(3), intToValue(4), intToValue(5), intToValue(6))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
b.Fatal("f is not a function")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkAssertInt(b *testing.B) {
|
||||||
|
v := intToValue(42)
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
if i, ok := v.(valueInt); !ok || int64(i) != 42 {
|
||||||
|
b.Fatal()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkLoadVal(b *testing.B) {
|
||||||
|
var ins instruction
|
||||||
|
b.ReportAllocs()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
ins = loadVal{valueInt(1)}
|
||||||
|
_ = ins
|
||||||
|
}
|
||||||
|
}
|
||||||
13
goja_nodejs/LICENSE
Normal file
13
goja_nodejs/LICENSE
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
Copyright (c) 2016 Dmitry Panov
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
|
||||||
|
documentation files (the "Software"), to deal in the Software without restriction, including without limitation
|
||||||
|
the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
|
||||||
|
permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
|
||||||
|
WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
||||||
|
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
||||||
|
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
46
goja_nodejs/README.md
Normal file
46
goja_nodejs/README.md
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
Nodejs compatibility library for Goja
|
||||||
|
====
|
||||||
|
|
||||||
|
This is a collection of [Goja](https://apigo.cc/gojs/goja) modules that provide nodejs compatibility.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
```go
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"apigo.cc/gojs/goja"
|
||||||
|
"apigo.cc/gojs/goja_nodejs/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
registry := new(require.Registry) // this can be shared by multiple runtimes
|
||||||
|
|
||||||
|
runtime := goja.New()
|
||||||
|
req := registry.Enable(runtime)
|
||||||
|
|
||||||
|
runtime.RunString(`
|
||||||
|
var m = require("./m.js");
|
||||||
|
m.test();
|
||||||
|
`)
|
||||||
|
|
||||||
|
m, err := req.Require("./m.js")
|
||||||
|
_, _ = m, err
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Type Definitions
|
||||||
|
---
|
||||||
|
|
||||||
|
Type definitions are published to https://npmjs.com as @dop251/types-goja_nodejs-MODULE.
|
||||||
|
They only include what's been implemented so far.
|
||||||
|
|
||||||
|
To make use of them you need to install the appropriate modules and add `node_modules/@dop251` to `typeRoots` in `tsconfig.json`.
|
||||||
|
|
||||||
|
I didn't want to add those to DefinitelyTyped partly because I don't think they really belong there,
|
||||||
|
and partly because I'd like to fully control the release cycle, i.e. publish the modules by an automated CI job and
|
||||||
|
exactly at the same time as the Go code is released.
|
||||||
|
|
||||||
|
And the reason for splitting them into different packages is that the modules can be enabled or disabled individually, unlike in nodejs.
|
||||||
|
|
||||||
|
More modules will be added. Contributions welcome too.
|
||||||
129
goja_nodejs/assert.js
Normal file
129
goja_nodejs/assert.js
Normal file
@ -0,0 +1,129 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
const assert = {
|
||||||
|
_isSameValue(a, b) {
|
||||||
|
if (this._isNumber(a)) {
|
||||||
|
return this._numberEquals(a, b);
|
||||||
|
}
|
||||||
|
|
||||||
|
return a === b;
|
||||||
|
},
|
||||||
|
|
||||||
|
_isNumber(val) {
|
||||||
|
return typeof val === "number";
|
||||||
|
},
|
||||||
|
|
||||||
|
_toString(value) {
|
||||||
|
try {
|
||||||
|
if (value === 0 && 1 / value === -Infinity) {
|
||||||
|
return '-0';
|
||||||
|
}
|
||||||
|
|
||||||
|
return String(value);
|
||||||
|
} catch (err) {
|
||||||
|
if (err.name === 'TypeError') {
|
||||||
|
return Object.prototype.toString.call(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
_numberEquals(a, b, precision = 1e-6) {
|
||||||
|
if (!this._isNumber(b)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// Handle NaN vs. NaN
|
||||||
|
if (a !== a && b !== b) {
|
||||||
|
return true; // Both are NaN
|
||||||
|
}
|
||||||
|
// If only one is NaN, they're not equal
|
||||||
|
if (a !== a || b !== b) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (a === b) {
|
||||||
|
// Handle +/-0 vs. -/+0
|
||||||
|
return a !== 0 || 1 / a === 1 / b;
|
||||||
|
}
|
||||||
|
// Use relative error for larger numbers, absolute for smaller ones
|
||||||
|
if (Math.abs(a) > 1 || Math.abs(b) > 1) {
|
||||||
|
return Math.abs((a - b) / Math.max(Math.abs(a), Math.abs(b))) < precision;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Absolute error for small numbers
|
||||||
|
return Math.abs(a - b) < precision;
|
||||||
|
},
|
||||||
|
|
||||||
|
sameValue(actual, expected, message) {
|
||||||
|
if (assert._isSameValue(actual, expected)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (message === undefined) {
|
||||||
|
message = '';
|
||||||
|
} else {
|
||||||
|
message += ' ';
|
||||||
|
}
|
||||||
|
|
||||||
|
message += 'Expected SameValue(«' + assert._toString(actual) + '», «' + assert._toString(expected) + '») to be true';
|
||||||
|
|
||||||
|
throw new Error(message);
|
||||||
|
},
|
||||||
|
|
||||||
|
_throws(f, checks, message) {
|
||||||
|
if (message === undefined) {
|
||||||
|
message = '';
|
||||||
|
} else {
|
||||||
|
message += ' ';
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
f();
|
||||||
|
} catch (e) {
|
||||||
|
for (const check of checks) {
|
||||||
|
check(e, message);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
throw new Error(message + "No exception was thrown");
|
||||||
|
},
|
||||||
|
|
||||||
|
_sameErrorType(expected){
|
||||||
|
return function(e, message) {
|
||||||
|
assert.sameValue(e.constructor, expected, `${message}Wrong exception type was thrown:`);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
_sameErrorCode(expected){
|
||||||
|
return function(e, message) {
|
||||||
|
assert.sameValue(e.code, expected, `${message}Wrong exception code was thrown:`);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
_sameErrorMessage(expected){
|
||||||
|
return function(e, message) {
|
||||||
|
assert.sameValue(e.message, expected, `${message}Wrong exception message was thrown:`);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
throws(f, ctor, message) {
|
||||||
|
return this._throws(f, [
|
||||||
|
this._sameErrorType(ctor)
|
||||||
|
], message);
|
||||||
|
},
|
||||||
|
|
||||||
|
throwsNodeError(f, ctor, code, message) {
|
||||||
|
return this._throws(f, [
|
||||||
|
this._sameErrorType(ctor),
|
||||||
|
this._sameErrorCode(code)
|
||||||
|
], message);
|
||||||
|
},
|
||||||
|
|
||||||
|
throwsNodeErrorWithMessage(f, ctor, code, errorMessage, message) {
|
||||||
|
return this._throws(f, [
|
||||||
|
this._sameErrorType(ctor),
|
||||||
|
this._sameErrorCode(code),
|
||||||
|
this._sameErrorMessage(errorMessage)
|
||||||
|
], message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = assert;
|
||||||
@ -3,15 +3,19 @@ package buffer
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
|
"encoding/binary"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
|
"math"
|
||||||
|
"math/big"
|
||||||
"reflect"
|
"reflect"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"apigo.cc/gojs/goja"
|
"apigo.cc/gojs/goja"
|
||||||
"apigo.cc/gojs/goja_nodejs/errors"
|
"apigo.cc/gojs/goja_nodejs/errors"
|
||||||
|
"apigo.cc/gojs/goja_nodejs/goutil"
|
||||||
"apigo.cc/gojs/goja_nodejs/require"
|
"apigo.cc/gojs/goja_nodejs/require"
|
||||||
|
|
||||||
"apigo.cc/gojs/base64dec"
|
"github.com/dop251/base64dec"
|
||||||
"golang.org/x/text/encoding/unicode"
|
"golang.org/x/text/encoding/unicode"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -145,6 +149,7 @@ func (b *Buffer) ctor(call goja.ConstructorCall) (res *goja.Object) {
|
|||||||
type StringCodec interface {
|
type StringCodec interface {
|
||||||
DecodeAppend(string, []byte) []byte
|
DecodeAppend(string, []byte) []byte
|
||||||
Encode([]byte) string
|
Encode([]byte) string
|
||||||
|
Decode(s string) []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
type hexCodec struct{}
|
type hexCodec struct{}
|
||||||
@ -159,19 +164,27 @@ func (hexCodec) DecodeAppend(s string, b []byte) []byte {
|
|||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (hexCodec) Decode(s string) []byte {
|
||||||
|
n, _ := hex.DecodeString(s)
|
||||||
|
return n
|
||||||
|
}
|
||||||
func (hexCodec) Encode(b []byte) string {
|
func (hexCodec) Encode(b []byte) string {
|
||||||
return hex.EncodeToString(b)
|
return hex.EncodeToString(b)
|
||||||
}
|
}
|
||||||
|
|
||||||
type _utf8Codec struct{}
|
type _utf8Codec struct{}
|
||||||
|
|
||||||
func (_utf8Codec) DecodeAppend(s string, b []byte) []byte {
|
func (c _utf8Codec) DecodeAppend(s string, b []byte) []byte {
|
||||||
r, _ := unicode.UTF8.NewEncoder().String(s)
|
r := c.Decode(s)
|
||||||
dst, res := expandSlice(b, len(r))
|
dst, res := expandSlice(b, len(r))
|
||||||
copy(dst, r)
|
copy(dst, r)
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (_utf8Codec) Decode(s string) []byte {
|
||||||
|
r, _ := unicode.UTF8.NewEncoder().String(s)
|
||||||
|
return []byte(r)
|
||||||
|
}
|
||||||
func (_utf8Codec) Encode(b []byte) string {
|
func (_utf8Codec) Encode(b []byte) string {
|
||||||
r, _ := unicode.UTF8.NewDecoder().Bytes(b)
|
r, _ := unicode.UTF8.NewDecoder().Bytes(b)
|
||||||
return string(r)
|
return string(r)
|
||||||
@ -188,6 +201,10 @@ func (base64Codec) DecodeAppend(s string, b []byte) []byte {
|
|||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (base64Codec) Decode(s string) []byte {
|
||||||
|
res, _ := base64.StdEncoding.DecodeString(s)
|
||||||
|
return res
|
||||||
|
}
|
||||||
func (base64Codec) Encode(b []byte) string {
|
func (base64Codec) Encode(b []byte) string {
|
||||||
return base64.StdEncoding.EncodeToString(b)
|
return base64.StdEncoding.EncodeToString(b)
|
||||||
}
|
}
|
||||||
@ -313,18 +330,6 @@ func (b *Buffer) from(call goja.FunctionCall) goja.Value {
|
|||||||
return b._from(call.Arguments...)
|
return b._from(call.Arguments...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func isNumber(v goja.Value) bool {
|
|
||||||
switch v.ExportType() {
|
|
||||||
case reflectTypeInt, reflectTypeFloat:
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func isString(v goja.Value) bool {
|
|
||||||
return v.ExportType() == reflectTypeString
|
|
||||||
}
|
|
||||||
|
|
||||||
func StringCodecByName(name string) StringCodec {
|
func StringCodecByName(name string) StringCodec {
|
||||||
return stringCodecs[name]
|
return stringCodecs[name]
|
||||||
}
|
}
|
||||||
@ -356,18 +361,18 @@ func (b *Buffer) fill(buf []byte, fill string, enc goja.Value) []byte {
|
|||||||
func (b *Buffer) alloc(call goja.FunctionCall) goja.Value {
|
func (b *Buffer) alloc(call goja.FunctionCall) goja.Value {
|
||||||
arg0 := call.Argument(0)
|
arg0 := call.Argument(0)
|
||||||
size := -1
|
size := -1
|
||||||
if isNumber(arg0) {
|
if goja.IsNumber(arg0) {
|
||||||
size = int(arg0.ToInteger())
|
size = int(arg0.ToInteger())
|
||||||
}
|
}
|
||||||
if size < 0 {
|
if size < 0 {
|
||||||
panic(errors.NewTypeError(b.r, errors.ErrCodeInvalidArgType, "The \"size\" argument must be of type number."))
|
panic(errors.NewArgumentNotNumberTypeError(b.r, "size"))
|
||||||
}
|
}
|
||||||
fill := call.Argument(1)
|
fill := call.Argument(1)
|
||||||
buf := make([]byte, size)
|
buf := make([]byte, size)
|
||||||
if !goja.IsUndefined(fill) {
|
if !goja.IsUndefined(fill) {
|
||||||
if isString(fill) {
|
if goja.IsString(fill) {
|
||||||
var enc goja.Value
|
var enc goja.Value
|
||||||
if a := call.Argument(2); isString(a) {
|
if a := call.Argument(2); goja.IsString(a) {
|
||||||
enc = a
|
enc = a
|
||||||
} else {
|
} else {
|
||||||
enc = goja.Undefined()
|
enc = goja.Undefined()
|
||||||
@ -391,7 +396,27 @@ func (b *Buffer) alloc(call goja.FunctionCall) goja.Value {
|
|||||||
func (b *Buffer) proto_toString(call goja.FunctionCall) goja.Value {
|
func (b *Buffer) proto_toString(call goja.FunctionCall) goja.Value {
|
||||||
bb := Bytes(b.r, call.This)
|
bb := Bytes(b.r, call.This)
|
||||||
codec := b.getStringCodec(call.Argument(0))
|
codec := b.getStringCodec(call.Argument(0))
|
||||||
return b.r.ToValue(codec.Encode(bb))
|
start := goutil.CoercedIntegerArgument(call, 1, 0, 0)
|
||||||
|
|
||||||
|
// Node's Buffer class makes this zero if it is negative
|
||||||
|
if start < 0 {
|
||||||
|
start = 0
|
||||||
|
} else if start >= int64(len(bb)) {
|
||||||
|
// returns an empty string if start is beyond the length of the buffer
|
||||||
|
return b.r.ToValue("")
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOTE that Node will default to the length of the buffer, but uses 0 for type mismatch defaults
|
||||||
|
end := goutil.CoercedIntegerArgument(call, 2, int64(len(bb)), 0)
|
||||||
|
if end < 0 || start >= end {
|
||||||
|
// returns an empty string if end < 0 or start >= end
|
||||||
|
return b.r.ToValue("")
|
||||||
|
} else if end > int64(len(bb)) {
|
||||||
|
// and Node ensures you don't go past the Buffer
|
||||||
|
end = int64(len(bb))
|
||||||
|
}
|
||||||
|
|
||||||
|
return b.r.ToValue(codec.Encode(bb[start:end]))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Buffer) proto_equals(call goja.FunctionCall) goja.Value {
|
func (b *Buffer) proto_equals(call goja.FunctionCall) goja.Value {
|
||||||
@ -404,6 +429,642 @@ func (b *Buffer) proto_equals(call goja.FunctionCall) goja.Value {
|
|||||||
panic(errors.NewTypeError(b.r, errors.ErrCodeInvalidArgType, "The \"otherBuffer\" argument must be an instance of Buffer or Uint8Array."))
|
panic(errors.NewTypeError(b.r, errors.ErrCodeInvalidArgType, "The \"otherBuffer\" argument must be an instance of Buffer or Uint8Array."))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// readBigInt64BE reads a big-endian 64-bit signed integer from the buffer
|
||||||
|
func (b *Buffer) readBigInt64BE(call goja.FunctionCall) goja.Value {
|
||||||
|
bb := Bytes(b.r, call.This)
|
||||||
|
offset := b.getOffsetArgument(call, 0, bb, 8)
|
||||||
|
value := int64(binary.BigEndian.Uint64(bb[offset : offset+8]))
|
||||||
|
|
||||||
|
return b.r.ToValue(big.NewInt(value))
|
||||||
|
}
|
||||||
|
|
||||||
|
// readBigInt64LE reads a little-endian 64-bit signed integer from the buffer
|
||||||
|
func (b *Buffer) readBigInt64LE(call goja.FunctionCall) goja.Value {
|
||||||
|
bb := Bytes(b.r, call.This)
|
||||||
|
offset := b.getOffsetArgument(call, 0, bb, 8)
|
||||||
|
value := int64(binary.LittleEndian.Uint64(bb[offset : offset+8]))
|
||||||
|
|
||||||
|
return b.r.ToValue(big.NewInt(value))
|
||||||
|
}
|
||||||
|
|
||||||
|
// readBigUInt64BE reads a big-endian 64-bit unsigned integer from the buffer
|
||||||
|
func (b *Buffer) readBigUInt64BE(call goja.FunctionCall) goja.Value {
|
||||||
|
bb := Bytes(b.r, call.This)
|
||||||
|
offset := b.getOffsetArgument(call, 0, bb, 8)
|
||||||
|
value := binary.BigEndian.Uint64(bb[offset : offset+8])
|
||||||
|
|
||||||
|
return b.r.ToValue(new(big.Int).SetUint64(value))
|
||||||
|
}
|
||||||
|
|
||||||
|
// readBigUInt64LE reads a little-endian 64-bit unsigned integer from the buffer
|
||||||
|
func (b *Buffer) readBigUInt64LE(call goja.FunctionCall) goja.Value {
|
||||||
|
bb := Bytes(b.r, call.This)
|
||||||
|
offset := b.getOffsetArgument(call, 0, bb, 8)
|
||||||
|
value := binary.LittleEndian.Uint64(bb[offset : offset+8])
|
||||||
|
|
||||||
|
return b.r.ToValue(new(big.Int).SetUint64(value))
|
||||||
|
}
|
||||||
|
|
||||||
|
// readDoubleBE reads a big-endian 64-bit floating-point number from the buffer
|
||||||
|
func (b *Buffer) readDoubleBE(call goja.FunctionCall) goja.Value {
|
||||||
|
bb := Bytes(b.r, call.This)
|
||||||
|
offset := b.getOffsetArgument(call, 0, bb, 8)
|
||||||
|
value := binary.BigEndian.Uint64(bb[offset : offset+8])
|
||||||
|
|
||||||
|
return b.r.ToValue(math.Float64frombits(value))
|
||||||
|
}
|
||||||
|
|
||||||
|
// readDoubleLE reads a little-endian 64-bit floating-point number from the buffer
|
||||||
|
func (b *Buffer) readDoubleLE(call goja.FunctionCall) goja.Value {
|
||||||
|
bb := Bytes(b.r, call.This)
|
||||||
|
offset := b.getOffsetArgument(call, 0, bb, 8)
|
||||||
|
value := binary.LittleEndian.Uint64(bb[offset : offset+8])
|
||||||
|
|
||||||
|
return b.r.ToValue(math.Float64frombits(value))
|
||||||
|
}
|
||||||
|
|
||||||
|
// readFloatBE reads a big-endian 32-bit floating-point number from the buffer
|
||||||
|
func (b *Buffer) readFloatBE(call goja.FunctionCall) goja.Value {
|
||||||
|
bb := Bytes(b.r, call.This)
|
||||||
|
offset := b.getOffsetArgument(call, 0, bb, 4)
|
||||||
|
value := binary.BigEndian.Uint32(bb[offset : offset+4])
|
||||||
|
|
||||||
|
return b.r.ToValue(math.Float32frombits(value))
|
||||||
|
}
|
||||||
|
|
||||||
|
// readFloatLE reads a little-endian 32-bit floating-point number from the buffer
|
||||||
|
func (b *Buffer) readFloatLE(call goja.FunctionCall) goja.Value {
|
||||||
|
bb := Bytes(b.r, call.This)
|
||||||
|
offset := b.getOffsetArgument(call, 0, bb, 4)
|
||||||
|
value := binary.LittleEndian.Uint32(bb[offset : offset+4])
|
||||||
|
|
||||||
|
return b.r.ToValue(math.Float32frombits(value))
|
||||||
|
}
|
||||||
|
|
||||||
|
// readInt8 reads an 8-bit signed integer from the buffer
|
||||||
|
func (b *Buffer) readInt8(call goja.FunctionCall) goja.Value {
|
||||||
|
bb := Bytes(b.r, call.This)
|
||||||
|
offset := b.getOffsetArgument(call, 0, bb, 1)
|
||||||
|
value := int8(bb[offset])
|
||||||
|
|
||||||
|
return b.r.ToValue(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// readInt16BE reads a big-endian 16-bit signed integer from the buffer
|
||||||
|
func (b *Buffer) readInt16BE(call goja.FunctionCall) goja.Value {
|
||||||
|
bb := Bytes(b.r, call.This)
|
||||||
|
offset := b.getOffsetArgument(call, 0, bb, 2)
|
||||||
|
value := int16(binary.BigEndian.Uint16(bb[offset : offset+2]))
|
||||||
|
|
||||||
|
return b.r.ToValue(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// readInt16LE reads a little-endian 16-bit signed integer from the buffer
|
||||||
|
func (b *Buffer) readInt16LE(call goja.FunctionCall) goja.Value {
|
||||||
|
bb := Bytes(b.r, call.This)
|
||||||
|
offset := b.getOffsetArgument(call, 0, bb, 2)
|
||||||
|
value := int16(binary.LittleEndian.Uint16(bb[offset : offset+2]))
|
||||||
|
|
||||||
|
return b.r.ToValue(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// readInt32BE reads a big-endian 32-bit signed integer from the buffer
|
||||||
|
func (b *Buffer) readInt32BE(call goja.FunctionCall) goja.Value {
|
||||||
|
bb := Bytes(b.r, call.This)
|
||||||
|
offset := b.getOffsetArgument(call, 0, bb, 4)
|
||||||
|
value := int32(binary.BigEndian.Uint32(bb[offset : offset+4]))
|
||||||
|
|
||||||
|
return b.r.ToValue(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// readInt32LE reads a little-endian 32-bit signed integer from the buffer
|
||||||
|
func (b *Buffer) readInt32LE(call goja.FunctionCall) goja.Value {
|
||||||
|
bb := Bytes(b.r, call.This)
|
||||||
|
offset := b.getOffsetArgument(call, 0, bb, 4)
|
||||||
|
value := int32(binary.LittleEndian.Uint32(bb[offset : offset+4]))
|
||||||
|
|
||||||
|
return b.r.ToValue(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// readIntBE reads a big-endian signed integer of variable byte length
|
||||||
|
func (b *Buffer) readIntBE(call goja.FunctionCall) goja.Value {
|
||||||
|
bb := Bytes(b.r, call.This)
|
||||||
|
offset, byteLength := b.getVariableLengthReadArguments(call, bb)
|
||||||
|
|
||||||
|
var value int64
|
||||||
|
for i := int64(0); i < byteLength; i++ {
|
||||||
|
value = (value << 8) | int64(bb[offset+i])
|
||||||
|
}
|
||||||
|
|
||||||
|
value = signExtend(value, byteLength)
|
||||||
|
|
||||||
|
return b.r.ToValue(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// readIntLE reads a little-endian signed integer of variable byte length
|
||||||
|
func (b *Buffer) readIntLE(call goja.FunctionCall) goja.Value {
|
||||||
|
bb := Bytes(b.r, call.This)
|
||||||
|
offset, byteLength := b.getVariableLengthReadArguments(call, bb)
|
||||||
|
|
||||||
|
var value int64
|
||||||
|
for i := byteLength - 1; i >= 0; i-- {
|
||||||
|
value = (value << 8) | int64(bb[offset+i])
|
||||||
|
}
|
||||||
|
|
||||||
|
value = signExtend(value, byteLength)
|
||||||
|
|
||||||
|
return b.r.ToValue(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// readUInt8 reads an 8-bit unsigned integer from the buffer
|
||||||
|
func (b *Buffer) readUInt8(call goja.FunctionCall) goja.Value {
|
||||||
|
bb := Bytes(b.r, call.This)
|
||||||
|
offset := b.getOffsetArgument(call, 0, bb, 1)
|
||||||
|
value := bb[offset]
|
||||||
|
|
||||||
|
return b.r.ToValue(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// readUInt16BE reads a big-endian 16-bit unsigned integer from the buffer
|
||||||
|
func (b *Buffer) readUInt16BE(call goja.FunctionCall) goja.Value {
|
||||||
|
bb := Bytes(b.r, call.This)
|
||||||
|
offset := b.getOffsetArgument(call, 0, bb, 2)
|
||||||
|
value := binary.BigEndian.Uint16(bb[offset : offset+2])
|
||||||
|
|
||||||
|
return b.r.ToValue(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// readUInt16LE reads a little-endian 16-bit unsigned integer from the buffer
|
||||||
|
func (b *Buffer) readUInt16LE(call goja.FunctionCall) goja.Value {
|
||||||
|
bb := Bytes(b.r, call.This)
|
||||||
|
offset := b.getOffsetArgument(call, 0, bb, 2)
|
||||||
|
value := binary.LittleEndian.Uint16(bb[offset : offset+2])
|
||||||
|
|
||||||
|
return b.r.ToValue(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// readUInt32BE reads a big-endian 32-bit unsigned integer from the buffer
|
||||||
|
func (b *Buffer) readUInt32BE(call goja.FunctionCall) goja.Value {
|
||||||
|
bb := Bytes(b.r, call.This)
|
||||||
|
offset := b.getOffsetArgument(call, 0, bb, 4)
|
||||||
|
value := binary.BigEndian.Uint32(bb[offset : offset+4])
|
||||||
|
|
||||||
|
return b.r.ToValue(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// readUInt32LE reads a little-endian 32-bit unsigned integer from the buffer
|
||||||
|
func (b *Buffer) readUInt32LE(call goja.FunctionCall) goja.Value {
|
||||||
|
bb := Bytes(b.r, call.This)
|
||||||
|
offset := b.getOffsetArgument(call, 0, bb, 4)
|
||||||
|
value := binary.LittleEndian.Uint32(bb[offset : offset+4])
|
||||||
|
|
||||||
|
return b.r.ToValue(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// readUIntBE reads a big-endian unsigned integer of variable byte length
|
||||||
|
func (b *Buffer) readUIntBE(call goja.FunctionCall) goja.Value {
|
||||||
|
bb := Bytes(b.r, call.This)
|
||||||
|
offset, byteLength := b.getVariableLengthReadArguments(call, bb)
|
||||||
|
|
||||||
|
var value uint64
|
||||||
|
for i := int64(0); i < byteLength; i++ {
|
||||||
|
value = (value << 8) | uint64(bb[offset+i])
|
||||||
|
}
|
||||||
|
|
||||||
|
return b.r.ToValue(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// readUIntLE reads a little-endian unsigned integer of variable byte length
|
||||||
|
func (b *Buffer) readUIntLE(call goja.FunctionCall) goja.Value {
|
||||||
|
bb := Bytes(b.r, call.This)
|
||||||
|
offset, byteLength := b.getVariableLengthReadArguments(call, bb)
|
||||||
|
|
||||||
|
var value uint64
|
||||||
|
for i := byteLength - 1; i >= 0; i-- {
|
||||||
|
value = (value << 8) | uint64(bb[offset+i])
|
||||||
|
}
|
||||||
|
|
||||||
|
return b.r.ToValue(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// write will write a string to the Buffer at offset according to the character encoding. The length parameter is
|
||||||
|
// the number of bytes to write. If buffer did not contain enough space to fit the entire string, only part of string
|
||||||
|
// will be written.
|
||||||
|
func (b *Buffer) write(call goja.FunctionCall) goja.Value {
|
||||||
|
bb := Bytes(b.r, call.This)
|
||||||
|
str := goutil.RequiredStringArgument(b.r, call, "string", 0)
|
||||||
|
// note that we are passing in zero for numBytes, since the length parameter, which depends on offset,
|
||||||
|
// will dictate the number of bytes
|
||||||
|
offset := b.getOffsetArgument(call, 1, bb, 0)
|
||||||
|
// the length defaults to the size of the buffer - offset
|
||||||
|
maxLength := int64(len(bb)) - offset
|
||||||
|
length := goutil.OptionalIntegerArgument(b.r, call, "length", 2, maxLength)
|
||||||
|
codec := b.getStringCodec(call.Argument(3))
|
||||||
|
|
||||||
|
raw := codec.Decode(str)
|
||||||
|
if int64(len(raw)) < length {
|
||||||
|
// make sure we only write up to raw bytes
|
||||||
|
length = int64(len(raw))
|
||||||
|
}
|
||||||
|
n := copy(bb[offset:], raw[:length])
|
||||||
|
return b.r.ToValue(n)
|
||||||
|
}
|
||||||
|
|
||||||
|
// writeBigInt64BE writes a big-endian 64-bit signed integer to the buffer
|
||||||
|
func (b *Buffer) writeBigInt64BE(call goja.FunctionCall) goja.Value {
|
||||||
|
bb := Bytes(b.r, call.This)
|
||||||
|
value := goutil.RequiredBigIntArgument(b.r, call, "value", 0)
|
||||||
|
offset := b.getOffsetArgument(call, 1, bb, 8)
|
||||||
|
|
||||||
|
intValue := value.Int64()
|
||||||
|
binary.BigEndian.PutUint64(bb[offset:offset+8], uint64(intValue))
|
||||||
|
|
||||||
|
return b.r.ToValue(offset + 8)
|
||||||
|
}
|
||||||
|
|
||||||
|
// writeBigInt64LE writes a little-endian 64-bit signed integer to the buffer
|
||||||
|
func (b *Buffer) writeBigInt64LE(call goja.FunctionCall) goja.Value {
|
||||||
|
bb := Bytes(b.r, call.This)
|
||||||
|
value := goutil.RequiredBigIntArgument(b.r, call, "value", 0)
|
||||||
|
offset := b.getOffsetArgument(call, 1, bb, 8)
|
||||||
|
|
||||||
|
intValue := value.Int64()
|
||||||
|
binary.LittleEndian.PutUint64(bb[offset:offset+8], uint64(intValue))
|
||||||
|
|
||||||
|
return b.r.ToValue(offset + 8)
|
||||||
|
}
|
||||||
|
|
||||||
|
// writeBigUInt64BE writes a big-endian 64-bit unsigned integer to the buffer
|
||||||
|
func (b *Buffer) writeBigUInt64BE(call goja.FunctionCall) goja.Value {
|
||||||
|
bb := Bytes(b.r, call.This)
|
||||||
|
value := goutil.RequiredBigIntArgument(b.r, call, "value", 0)
|
||||||
|
offset := b.getOffsetArgument(call, 1, bb, 8)
|
||||||
|
|
||||||
|
uintValue := value.Uint64()
|
||||||
|
binary.BigEndian.PutUint64(bb[offset:offset+8], uintValue)
|
||||||
|
|
||||||
|
return b.r.ToValue(offset + 8)
|
||||||
|
}
|
||||||
|
|
||||||
|
// writeBigUInt64LE writes a little-endian 64-bit unsigned integer to the buffer
|
||||||
|
func (b *Buffer) writeBigUInt64LE(call goja.FunctionCall) goja.Value {
|
||||||
|
bb := Bytes(b.r, call.This)
|
||||||
|
value := goutil.RequiredBigIntArgument(b.r, call, "value", 0)
|
||||||
|
offset := b.getOffsetArgument(call, 1, bb, 8)
|
||||||
|
|
||||||
|
uintValue := value.Uint64()
|
||||||
|
binary.LittleEndian.PutUint64(bb[offset:offset+8], uintValue)
|
||||||
|
|
||||||
|
return b.r.ToValue(offset + 8)
|
||||||
|
}
|
||||||
|
|
||||||
|
// writeDoubleBE writes a big-endian 64-bit double to the buffer
|
||||||
|
func (b *Buffer) writeDoubleBE(call goja.FunctionCall) goja.Value {
|
||||||
|
bb := Bytes(b.r, call.This)
|
||||||
|
value := goutil.RequiredFloatArgument(b.r, call, "value", 0)
|
||||||
|
offset := b.getOffsetArgument(call, 1, bb, 8)
|
||||||
|
|
||||||
|
bits := math.Float64bits(value)
|
||||||
|
binary.BigEndian.PutUint64(bb[offset:offset+8], bits)
|
||||||
|
|
||||||
|
return b.r.ToValue(offset + 8)
|
||||||
|
}
|
||||||
|
|
||||||
|
// writeDoubleLE writes a little-endian 64-bit double to the buffer
|
||||||
|
func (b *Buffer) writeDoubleLE(call goja.FunctionCall) goja.Value {
|
||||||
|
bb := Bytes(b.r, call.This)
|
||||||
|
value := goutil.RequiredFloatArgument(b.r, call, "value", 0)
|
||||||
|
offset := b.getOffsetArgument(call, 1, bb, 8)
|
||||||
|
|
||||||
|
bits := math.Float64bits(value)
|
||||||
|
binary.LittleEndian.PutUint64(bb[offset:offset+8], bits)
|
||||||
|
|
||||||
|
return b.r.ToValue(offset + 8)
|
||||||
|
}
|
||||||
|
|
||||||
|
// writeFloatBE writes a big-endian 32-bit float to the buffer
|
||||||
|
func (b *Buffer) writeFloatBE(call goja.FunctionCall) goja.Value {
|
||||||
|
bb := Bytes(b.r, call.This)
|
||||||
|
value := goutil.RequiredFloatArgument(b.r, call, "value", 0)
|
||||||
|
offset := b.getOffsetArgument(call, 1, bb, 4)
|
||||||
|
|
||||||
|
b.ensureWithinFloat32Range(value)
|
||||||
|
|
||||||
|
bits := math.Float32bits(float32(value))
|
||||||
|
binary.BigEndian.PutUint32(bb[offset:offset+4], bits)
|
||||||
|
|
||||||
|
return b.r.ToValue(offset + 4)
|
||||||
|
}
|
||||||
|
|
||||||
|
// writeFloatLE writes a little-endian 32-bit floating-point number to the buffer
|
||||||
|
func (b *Buffer) writeFloatLE(call goja.FunctionCall) goja.Value {
|
||||||
|
bb := Bytes(b.r, call.This)
|
||||||
|
value := goutil.RequiredFloatArgument(b.r, call, "value", 0)
|
||||||
|
offset := b.getOffsetArgument(call, 1, bb, 4)
|
||||||
|
|
||||||
|
b.ensureWithinFloat32Range(value)
|
||||||
|
|
||||||
|
bits := math.Float32bits(float32(value))
|
||||||
|
binary.LittleEndian.PutUint32(bb[offset:offset+4], bits)
|
||||||
|
|
||||||
|
return b.r.ToValue(offset + 4)
|
||||||
|
}
|
||||||
|
|
||||||
|
// writeInt8 writes an 8-bit signed integer to the buffer
|
||||||
|
func (b *Buffer) writeInt8(call goja.FunctionCall) goja.Value {
|
||||||
|
bb := Bytes(b.r, call.This)
|
||||||
|
value := goutil.RequiredIntegerArgument(b.r, call, "value", 0)
|
||||||
|
offset := b.getOffsetArgument(call, 1, bb, 1)
|
||||||
|
|
||||||
|
if value < math.MinInt8 || value > math.MaxInt8 {
|
||||||
|
panic(errors.NewArgumentOutOfRangeError(b.r, "value", value))
|
||||||
|
}
|
||||||
|
|
||||||
|
bb[offset] = byte(int8(value))
|
||||||
|
|
||||||
|
return b.r.ToValue(offset + 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// writeInt16BE writes a big-endian 16-bit signed integer to the buffer
|
||||||
|
func (b *Buffer) writeInt16BE(call goja.FunctionCall) goja.Value {
|
||||||
|
bb := Bytes(b.r, call.This)
|
||||||
|
value := goutil.RequiredIntegerArgument(b.r, call, "value", 0)
|
||||||
|
offset := b.getOffsetArgument(call, 1, bb, 2)
|
||||||
|
|
||||||
|
b.ensureWithinInt16Range(value)
|
||||||
|
|
||||||
|
binary.BigEndian.PutUint16(bb[offset:offset+2], uint16(value))
|
||||||
|
|
||||||
|
return b.r.ToValue(offset + 2)
|
||||||
|
}
|
||||||
|
|
||||||
|
// writeInt16LE writes a little-endian 16-bit signed integer to the buffer
|
||||||
|
func (b *Buffer) writeInt16LE(call goja.FunctionCall) goja.Value {
|
||||||
|
bb := Bytes(b.r, call.This)
|
||||||
|
value := goutil.RequiredIntegerArgument(b.r, call, "value", 0)
|
||||||
|
offset := b.getOffsetArgument(call, 1, bb, 2)
|
||||||
|
|
||||||
|
b.ensureWithinInt16Range(value)
|
||||||
|
|
||||||
|
binary.LittleEndian.PutUint16(bb[offset:offset+2], uint16(value))
|
||||||
|
|
||||||
|
return b.r.ToValue(offset + 2)
|
||||||
|
}
|
||||||
|
|
||||||
|
// writeInt32BE writes a big-endian 32-bit signed integer to the buffer
|
||||||
|
func (b *Buffer) writeInt32BE(call goja.FunctionCall) goja.Value {
|
||||||
|
bb := Bytes(b.r, call.This)
|
||||||
|
value := goutil.RequiredIntegerArgument(b.r, call, "value", 0)
|
||||||
|
offset := b.getOffsetArgument(call, 1, bb, 4)
|
||||||
|
|
||||||
|
b.ensureWithinInt32Range(value)
|
||||||
|
|
||||||
|
binary.BigEndian.PutUint32(bb[offset:offset+4], uint32(value))
|
||||||
|
|
||||||
|
return b.r.ToValue(offset + 4)
|
||||||
|
}
|
||||||
|
|
||||||
|
// writeInt32LE writes a little-endian 32-bit signed integer to the buffer
|
||||||
|
func (b *Buffer) writeInt32LE(call goja.FunctionCall) goja.Value {
|
||||||
|
bb := Bytes(b.r, call.This)
|
||||||
|
value := goutil.RequiredIntegerArgument(b.r, call, "value", 0)
|
||||||
|
offset := b.getOffsetArgument(call, 1, bb, 4)
|
||||||
|
|
||||||
|
b.ensureWithinInt32Range(value)
|
||||||
|
|
||||||
|
binary.LittleEndian.PutUint32(bb[offset:offset+4], uint32(value))
|
||||||
|
|
||||||
|
return b.r.ToValue(offset + 4)
|
||||||
|
}
|
||||||
|
|
||||||
|
// writeIntBE writes a big-endian signed integer of variable byte length
|
||||||
|
func (b *Buffer) writeIntBE(call goja.FunctionCall) goja.Value {
|
||||||
|
bb := Bytes(b.r, call.This)
|
||||||
|
value := goutil.RequiredIntegerArgument(b.r, call, "value", 0)
|
||||||
|
offset, byteLength := b.getVariableLengthWriteArguments(call, bb)
|
||||||
|
|
||||||
|
b.ensureWithinIntRange(byteLength, value)
|
||||||
|
|
||||||
|
// Write bytes in big-endian order (most significant byte first)
|
||||||
|
for i := int64(0); i < byteLength; i++ {
|
||||||
|
shift := uint(8 * (byteLength - 1 - i))
|
||||||
|
bb[offset+i] = byte(value >> shift)
|
||||||
|
}
|
||||||
|
|
||||||
|
return b.r.ToValue(offset + byteLength)
|
||||||
|
}
|
||||||
|
|
||||||
|
// writeIntLE writes a little-endian signed integer of variable byte length
|
||||||
|
func (b *Buffer) writeIntLE(call goja.FunctionCall) goja.Value {
|
||||||
|
bb := Bytes(b.r, call.This)
|
||||||
|
value := goutil.RequiredIntegerArgument(b.r, call, "value", 0)
|
||||||
|
offset, byteLength := b.getVariableLengthWriteArguments(call, bb)
|
||||||
|
|
||||||
|
b.ensureWithinIntRange(byteLength, value)
|
||||||
|
|
||||||
|
// Write bytes in little-endian order
|
||||||
|
for i := int64(0); i < byteLength; i++ {
|
||||||
|
shift := uint(8 * i)
|
||||||
|
bb[offset+i] = byte(value >> shift)
|
||||||
|
}
|
||||||
|
|
||||||
|
return b.r.ToValue(offset + byteLength)
|
||||||
|
}
|
||||||
|
|
||||||
|
// writeUInt8 writes an 8-bit unsigned integer to the buffer
|
||||||
|
func (b *Buffer) writeUInt8(call goja.FunctionCall) goja.Value {
|
||||||
|
bb := Bytes(b.r, call.This)
|
||||||
|
value := goutil.RequiredIntegerArgument(b.r, call, "value", 0)
|
||||||
|
offset := b.getOffsetArgument(call, 1, bb, 1)
|
||||||
|
|
||||||
|
if value < 0 || value > 255 {
|
||||||
|
panic(errors.NewArgumentOutOfRangeError(b.r, "value", value))
|
||||||
|
}
|
||||||
|
|
||||||
|
bb[offset] = uint8(value)
|
||||||
|
|
||||||
|
return b.r.ToValue(offset + 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// writeUInt16BE writes a big-endian 16-bit unsigned integer to the buffer
|
||||||
|
func (b *Buffer) writeUInt16BE(call goja.FunctionCall) goja.Value {
|
||||||
|
bb := Bytes(b.r, call.This)
|
||||||
|
value := goutil.RequiredIntegerArgument(b.r, call, "value", 0)
|
||||||
|
offset := b.getOffsetArgument(call, 1, bb, 2)
|
||||||
|
|
||||||
|
b.ensureWithinUInt16Range(value)
|
||||||
|
|
||||||
|
binary.BigEndian.PutUint16(bb[offset:offset+2], uint16(value))
|
||||||
|
|
||||||
|
return b.r.ToValue(offset + 2)
|
||||||
|
}
|
||||||
|
|
||||||
|
// writeUInt16LE writes a little-endian 16-bit unsigned integer to the buffer
|
||||||
|
func (b *Buffer) writeUInt16LE(call goja.FunctionCall) goja.Value {
|
||||||
|
bb := Bytes(b.r, call.This)
|
||||||
|
value := goutil.RequiredIntegerArgument(b.r, call, "value", 0)
|
||||||
|
offset := b.getOffsetArgument(call, 1, bb, 2)
|
||||||
|
|
||||||
|
b.ensureWithinUInt16Range(value)
|
||||||
|
|
||||||
|
binary.LittleEndian.PutUint16(bb[offset:offset+2], uint16(value))
|
||||||
|
|
||||||
|
return b.r.ToValue(offset + 2)
|
||||||
|
}
|
||||||
|
|
||||||
|
// writeUInt32BE writes a big-endian 32-bit unsigned integer to the buffer
|
||||||
|
func (b *Buffer) writeUInt32BE(call goja.FunctionCall) goja.Value {
|
||||||
|
bb := Bytes(b.r, call.This)
|
||||||
|
value := goutil.RequiredIntegerArgument(b.r, call, "value", 0)
|
||||||
|
offset := b.getOffsetArgument(call, 1, bb, 4)
|
||||||
|
|
||||||
|
b.ensureWithinUInt32Range(value)
|
||||||
|
|
||||||
|
binary.BigEndian.PutUint32(bb[offset:offset+4], uint32(value))
|
||||||
|
|
||||||
|
return b.r.ToValue(offset + 4)
|
||||||
|
}
|
||||||
|
|
||||||
|
// writeUInt32LE writes a little-endian 32-bit unsigned integer to the buffer
|
||||||
|
func (b *Buffer) writeUInt32LE(call goja.FunctionCall) goja.Value {
|
||||||
|
bb := Bytes(b.r, call.This)
|
||||||
|
value := goutil.RequiredIntegerArgument(b.r, call, "value", 0)
|
||||||
|
offset := b.getOffsetArgument(call, 1, bb, 4)
|
||||||
|
|
||||||
|
b.ensureWithinUInt32Range(value)
|
||||||
|
|
||||||
|
binary.LittleEndian.PutUint32(bb[offset:offset+4], uint32(value))
|
||||||
|
|
||||||
|
return b.r.ToValue(offset + 4)
|
||||||
|
}
|
||||||
|
|
||||||
|
// writeUIntBE writes a big-endian unsigned integer of variable byte length
|
||||||
|
func (b *Buffer) writeUIntBE(call goja.FunctionCall) goja.Value {
|
||||||
|
bb := Bytes(b.r, call.This)
|
||||||
|
value := goutil.RequiredIntegerArgument(b.r, call, "value", 0)
|
||||||
|
offset, byteLength := b.getVariableLengthWriteArguments(call, bb)
|
||||||
|
|
||||||
|
b.ensureWithinUIntRange(byteLength, value)
|
||||||
|
|
||||||
|
// Write the bytes in big-endian order (most significant byte first)
|
||||||
|
for i := int64(0); i < byteLength; i++ {
|
||||||
|
shift := (byteLength - 1 - i) * 8
|
||||||
|
bb[offset+i] = byte(value >> shift)
|
||||||
|
}
|
||||||
|
|
||||||
|
return b.r.ToValue(offset + byteLength)
|
||||||
|
}
|
||||||
|
|
||||||
|
// writeUIntLE writes a little-endian unsigned integer of variable byte length
|
||||||
|
func (b *Buffer) writeUIntLE(call goja.FunctionCall) goja.Value {
|
||||||
|
bb := Bytes(b.r, call.This)
|
||||||
|
value := goutil.RequiredIntegerArgument(b.r, call, "value", 0)
|
||||||
|
offset, byteLength := b.getVariableLengthWriteArguments(call, bb)
|
||||||
|
|
||||||
|
b.ensureWithinUIntRange(byteLength, value)
|
||||||
|
|
||||||
|
// Write the bytes in little-endian order
|
||||||
|
for i := int64(0); i < byteLength; i++ {
|
||||||
|
shift := uint(8 * i)
|
||||||
|
bb[offset+i] = byte(value >> shift)
|
||||||
|
}
|
||||||
|
|
||||||
|
return b.r.ToValue(offset + byteLength)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Buffer) getOffsetArgument(call goja.FunctionCall, argIndex int, bb []byte, numBytes int64) int64 {
|
||||||
|
offset := goutil.OptionalIntegerArgument(b.r, call, "offset", argIndex, 0)
|
||||||
|
|
||||||
|
if offset < 0 || offset+numBytes > int64(len(bb)) {
|
||||||
|
panic(errors.NewArgumentOutOfRangeError(b.r, "offset", offset))
|
||||||
|
}
|
||||||
|
|
||||||
|
return offset
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Buffer) getVariableLengthReadArguments(call goja.FunctionCall, bb []byte) (int64, int64) {
|
||||||
|
return b.getVariableLengthArguments(call, bb, 0, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Buffer) getVariableLengthWriteArguments(call goja.FunctionCall, bb []byte) (int64, int64) {
|
||||||
|
return b.getVariableLengthArguments(call, bb, 1, 2)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Buffer) getVariableLengthArguments(call goja.FunctionCall, bb []byte, offsetArgIndex, byteLengthArgIndex int) (int64, int64) {
|
||||||
|
offset := goutil.RequiredIntegerArgument(b.r, call, "offset", offsetArgIndex)
|
||||||
|
byteLength := goutil.RequiredIntegerArgument(b.r, call, "byteLength", byteLengthArgIndex)
|
||||||
|
|
||||||
|
if byteLength < 1 || byteLength > 6 {
|
||||||
|
panic(errors.NewArgumentOutOfRangeError(b.r, "byteLength", byteLength))
|
||||||
|
}
|
||||||
|
if offset < 0 || offset+byteLength > int64(len(bb)) {
|
||||||
|
panic(errors.NewArgumentOutOfRangeError(b.r, "offset", offset))
|
||||||
|
}
|
||||||
|
|
||||||
|
return offset, byteLength
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Buffer) ensureWithinFloat32Range(value float64) {
|
||||||
|
if value < -math.MaxFloat32 || value > math.MaxFloat32 {
|
||||||
|
panic(errors.NewArgumentOutOfRangeError(b.r, "value", value))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Buffer) ensureWithinInt16Range(value int64) {
|
||||||
|
if value < math.MinInt16 || value > math.MaxInt16 {
|
||||||
|
panic(errors.NewArgumentOutOfRangeError(b.r, "value", value))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Buffer) ensureWithinInt32Range(value int64) {
|
||||||
|
if value < math.MinInt32 || value > math.MaxInt32 {
|
||||||
|
panic(errors.NewArgumentOutOfRangeError(b.r, "value", value))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ensureWithinIntRange checks to make sure that value is within the integer range
|
||||||
|
// defined by the byteLength. Note that byteLength can be at most 6 bytes, so a
|
||||||
|
// 48 bit integer is the largest possible value.
|
||||||
|
func (b *Buffer) ensureWithinIntRange(byteLength, value int64) {
|
||||||
|
// Calculate the valid range for the given byte length
|
||||||
|
bits := byteLength * 8
|
||||||
|
minValue := -(int64(1) << (bits - 1))
|
||||||
|
maxValue := (int64(1) << (bits - 1)) - 1
|
||||||
|
|
||||||
|
if value < minValue || value > maxValue {
|
||||||
|
panic(errors.NewArgumentOutOfRangeError(b.r, "value", value))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Buffer) ensureWithinUInt16Range(value int64) {
|
||||||
|
if value < 0 || value > math.MaxUint16 {
|
||||||
|
panic(errors.NewArgumentOutOfRangeError(b.r, "value", value))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *Buffer) ensureWithinUInt32Range(value int64) {
|
||||||
|
if value < 0 || value > math.MaxUint32 {
|
||||||
|
panic(errors.NewArgumentOutOfRangeError(b.r, "value", value))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ensureWithinUIntRange checks to make sure that value is within the unsigned integer
|
||||||
|
// range defined by the byteLength. Note that byteLength can be at most 6 bytes, so a
|
||||||
|
// 48 bit unsigned integer is the largest possible value.
|
||||||
|
func (b *Buffer) ensureWithinUIntRange(byteLength, value int64) {
|
||||||
|
// Validate that the value is within the valid range for the given byteLength
|
||||||
|
maxValue := (int64(1) << (8 * byteLength)) - 1
|
||||||
|
if value < 0 || value > maxValue {
|
||||||
|
panic(errors.NewArgumentOutOfRangeError(b.r, "value", value))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func signExtend(value int64, numBytes int64) int64 {
|
||||||
|
// we don't have to turn this to a uint64 first because numBytes < 8 so
|
||||||
|
// the sign bit will never pushed out of the int64 range
|
||||||
|
return (value << (64 - 8*numBytes)) >> (64 - 8*numBytes)
|
||||||
|
}
|
||||||
|
|
||||||
func Require(runtime *goja.Runtime, module *goja.Object) {
|
func Require(runtime *goja.Runtime, module *goja.Object) {
|
||||||
b := &Buffer{r: runtime}
|
b := &Buffer{r: runtime}
|
||||||
uint8Array := runtime.Get("Uint8Array")
|
uint8Array := runtime.Get("Uint8Array")
|
||||||
@ -425,6 +1086,103 @@ func Require(runtime *goja.Runtime, module *goja.Object) {
|
|||||||
proto.DefineDataProperty("constructor", ctor, goja.FLAG_TRUE, goja.FLAG_TRUE, goja.FLAG_FALSE)
|
proto.DefineDataProperty("constructor", ctor, goja.FLAG_TRUE, goja.FLAG_TRUE, goja.FLAG_FALSE)
|
||||||
proto.Set("equals", b.proto_equals)
|
proto.Set("equals", b.proto_equals)
|
||||||
proto.Set("toString", b.proto_toString)
|
proto.Set("toString", b.proto_toString)
|
||||||
|
proto.Set("readBigInt64BE", b.readBigInt64BE)
|
||||||
|
proto.Set("readBigInt64LE", b.readBigInt64LE)
|
||||||
|
proto.Set("readBigUInt64BE", b.readBigUInt64BE)
|
||||||
|
// aliases for readBigUInt64BE
|
||||||
|
proto.Set("readBigUint64BE", b.readBigUInt64BE)
|
||||||
|
|
||||||
|
proto.Set("readBigUInt64LE", b.readBigUInt64LE)
|
||||||
|
// aliases for readBigUInt64LE
|
||||||
|
proto.Set("readBigUint64LE", b.readBigUInt64LE)
|
||||||
|
|
||||||
|
proto.Set("readDoubleBE", b.readDoubleBE)
|
||||||
|
proto.Set("readDoubleLE", b.readDoubleLE)
|
||||||
|
proto.Set("readFloatBE", b.readFloatBE)
|
||||||
|
proto.Set("readFloatLE", b.readFloatLE)
|
||||||
|
proto.Set("readInt8", b.readInt8)
|
||||||
|
proto.Set("readInt16BE", b.readInt16BE)
|
||||||
|
proto.Set("readInt16LE", b.readInt16LE)
|
||||||
|
proto.Set("readInt32BE", b.readInt32BE)
|
||||||
|
proto.Set("readInt32LE", b.readInt32LE)
|
||||||
|
proto.Set("readIntBE", b.readIntBE)
|
||||||
|
proto.Set("readIntLE", b.readIntLE)
|
||||||
|
proto.Set("readUInt8", b.readUInt8)
|
||||||
|
// aliases for readUInt8
|
||||||
|
proto.Set("readUint8", b.readUInt8)
|
||||||
|
|
||||||
|
proto.Set("readUInt16BE", b.readUInt16BE)
|
||||||
|
// aliases for readUInt16BE
|
||||||
|
proto.Set("readUint16BE", b.readUInt16BE)
|
||||||
|
|
||||||
|
proto.Set("readUInt16LE", b.readUInt16LE)
|
||||||
|
// aliases for readUInt16LE
|
||||||
|
proto.Set("readUint16LE", b.readUInt16LE)
|
||||||
|
|
||||||
|
proto.Set("readUInt32BE", b.readUInt32BE)
|
||||||
|
// aliases for readUInt32BE
|
||||||
|
proto.Set("readUint32BE", b.readUInt32BE)
|
||||||
|
|
||||||
|
proto.Set("readUInt32LE", b.readUInt32LE)
|
||||||
|
// aliases for readUInt32LE
|
||||||
|
proto.Set("readUint32LE", b.readUInt32LE)
|
||||||
|
|
||||||
|
proto.Set("readUIntBE", b.readUIntBE)
|
||||||
|
// aliases for readUIntBE
|
||||||
|
proto.Set("readUintBE", b.readUIntBE)
|
||||||
|
|
||||||
|
proto.Set("readUIntLE", b.readUIntLE)
|
||||||
|
// aliases for readUIntLE
|
||||||
|
proto.Set("readUintLE", b.readUIntLE)
|
||||||
|
proto.Set("write", b.write)
|
||||||
|
proto.Set("writeBigInt64BE", b.writeBigInt64BE)
|
||||||
|
proto.Set("writeBigInt64LE", b.writeBigInt64LE)
|
||||||
|
proto.Set("writeBigUInt64BE", b.writeBigUInt64BE)
|
||||||
|
// aliases for writeBigUInt64BE
|
||||||
|
proto.Set("writeBigUint64BE", b.writeBigUInt64BE)
|
||||||
|
|
||||||
|
proto.Set("writeBigUInt64LE", b.writeBigUInt64LE)
|
||||||
|
// aliases for writeBigUInt64LE
|
||||||
|
proto.Set("writeBigUint64LE", b.writeBigUInt64LE)
|
||||||
|
|
||||||
|
proto.Set("writeDoubleBE", b.writeDoubleBE)
|
||||||
|
proto.Set("writeDoubleLE", b.writeDoubleLE)
|
||||||
|
proto.Set("writeFloatBE", b.writeFloatBE)
|
||||||
|
proto.Set("writeFloatLE", b.writeFloatLE)
|
||||||
|
proto.Set("writeInt8", b.writeInt8)
|
||||||
|
proto.Set("writeInt16BE", b.writeInt16BE)
|
||||||
|
proto.Set("writeInt16LE", b.writeInt16LE)
|
||||||
|
proto.Set("writeInt32BE", b.writeInt32BE)
|
||||||
|
proto.Set("writeInt32LE", b.writeInt32LE)
|
||||||
|
proto.Set("writeIntBE", b.writeIntBE)
|
||||||
|
proto.Set("writeIntLE", b.writeIntLE)
|
||||||
|
proto.Set("writeUInt8", b.writeUInt8)
|
||||||
|
// aliases for writeUInt8
|
||||||
|
proto.Set("writeUint8", b.writeUInt8)
|
||||||
|
|
||||||
|
proto.Set("writeUInt16BE", b.writeUInt16BE)
|
||||||
|
// aliases for writeUInt16BE
|
||||||
|
proto.Set("writeUint16BE", b.writeUInt16BE)
|
||||||
|
|
||||||
|
proto.Set("writeUInt16LE", b.writeUInt16LE)
|
||||||
|
// aliases for writeUInt16LE
|
||||||
|
proto.Set("writeUint16LE", b.writeUInt16LE)
|
||||||
|
|
||||||
|
proto.Set("writeUInt32BE", b.writeUInt32BE)
|
||||||
|
// aliases for writeUInt32BE
|
||||||
|
proto.Set("writeUint32BE", b.writeUInt32BE)
|
||||||
|
|
||||||
|
proto.Set("writeUInt32LE", b.writeUInt32LE)
|
||||||
|
// aliases for writeUInt32LE
|
||||||
|
proto.Set("writeUint32LE", b.writeUInt32LE)
|
||||||
|
|
||||||
|
proto.Set("writeUIntBE", b.writeUIntBE)
|
||||||
|
// aliases for writeUIntBE
|
||||||
|
proto.Set("writeUintBE", b.writeUIntBE)
|
||||||
|
|
||||||
|
proto.Set("writeUIntLE", b.writeUIntLE)
|
||||||
|
// aliases for writeUIntLE
|
||||||
|
proto.Set("writeUintLE", b.writeUIntLE)
|
||||||
|
|
||||||
ctor.Set("prototype", proto)
|
ctor.Set("prototype", proto)
|
||||||
ctor.Set("poolSize", 8192)
|
ctor.Set("poolSize", 8192)
|
||||||
|
|||||||
2430
goja_nodejs/buffer/buffer_test.go
Normal file
2430
goja_nodejs/buffer/buffer_test.go
Normal file
File diff suppressed because it is too large
Load Diff
34
goja_nodejs/buffer/testdata/assertions.js
vendored
Normal file
34
goja_nodejs/buffer/testdata/assertions.js
vendored
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
/**
|
||||||
|
* Assertion helper functions for Buffer tests
|
||||||
|
*/
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
const assert = require("../../assert.js");
|
||||||
|
|
||||||
|
function assertValueRead(actual, expected) {
|
||||||
|
assert.sameValue(actual, expected, "value read does not match; ")
|
||||||
|
}
|
||||||
|
|
||||||
|
function assertBytesWritten(actual, expected) {
|
||||||
|
assert.sameValue(actual, expected, "bytesWritten does not match; ")
|
||||||
|
}
|
||||||
|
|
||||||
|
function assertBufferWriteRead(buffer, writeMethod, readMethod, value, offset = 0) {
|
||||||
|
const bytesWritten = buffer[writeMethod](value, offset);
|
||||||
|
const bytesPerElement = getBufferElementSize(writeMethod);
|
||||||
|
assertBytesWritten(bytesWritten, offset + bytesPerElement);
|
||||||
|
|
||||||
|
const readValue = buffer[readMethod](offset);
|
||||||
|
assertValueRead(readValue, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
// getBufferElementSize determines the number of bytes per type based on method name
|
||||||
|
function getBufferElementSize(methodName) {
|
||||||
|
if (methodName.includes('64')) return 8;
|
||||||
|
if (methodName.includes('Double')) return 8;
|
||||||
|
if (methodName.includes('32')) return 4;
|
||||||
|
if (methodName.includes('Float')) return 4;
|
||||||
|
if (methodName.includes('16')) return 2;
|
||||||
|
if (methodName.includes('8')) return 1;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
10
goja_nodejs/buffer/types/README.md
Normal file
10
goja_nodejs/buffer/types/README.md
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
## Type definitions for the goja_nodejs buffer module.
|
||||||
|
|
||||||
|
This package contains type definitions which only include features
|
||||||
|
currently implemented by the goja_nodejs buffer module.
|
||||||
|
|
||||||
|
### Install
|
||||||
|
|
||||||
|
```shell
|
||||||
|
npm install --save-dev @dop251/types-goja_nodejs-buffer
|
||||||
|
```
|
||||||
210
goja_nodejs/buffer/types/buffer.buffer.d.ts
vendored
Normal file
210
goja_nodejs/buffer/types/buffer.buffer.d.ts
vendored
Normal file
@ -0,0 +1,210 @@
|
|||||||
|
declare module "buffer" {
|
||||||
|
type ImplicitArrayBuffer<T extends WithImplicitCoercion<ArrayBufferLike>> = T extends
|
||||||
|
{ valueOf(): infer V extends ArrayBufferLike } ? V : T;
|
||||||
|
global {
|
||||||
|
interface BufferConstructor {
|
||||||
|
// see buffer.d.ts for implementation shared with all TypeScript versions
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allocates a new buffer containing the given {str}.
|
||||||
|
*
|
||||||
|
* @param str String to store in buffer.
|
||||||
|
* @param encoding encoding to use, optional. Default is 'utf8'
|
||||||
|
* @deprecated since v10.0.0 - Use `Buffer.from(string[, encoding])` instead.
|
||||||
|
*/
|
||||||
|
new(str: string, encoding?: BufferEncoding): Buffer<ArrayBuffer>;
|
||||||
|
/**
|
||||||
|
* Allocates a new buffer containing the given {array} of octets.
|
||||||
|
*
|
||||||
|
* @param array The octets to store.
|
||||||
|
* @deprecated since v10.0.0 - Use `Buffer.from(array)` instead.
|
||||||
|
*/
|
||||||
|
new(array: ArrayLike<number>): Buffer<ArrayBuffer>;
|
||||||
|
/**
|
||||||
|
* Produces a Buffer backed by the same allocated memory as
|
||||||
|
* the given {ArrayBuffer}/{SharedArrayBuffer}.
|
||||||
|
*
|
||||||
|
* @param arrayBuffer The ArrayBuffer with which to share memory.
|
||||||
|
* @deprecated since v10.0.0 - Use `Buffer.from(arrayBuffer[, byteOffset[, length]])` instead.
|
||||||
|
*/
|
||||||
|
new<TArrayBuffer extends ArrayBufferLike = ArrayBuffer>(arrayBuffer: TArrayBuffer): Buffer<TArrayBuffer>;
|
||||||
|
/**
|
||||||
|
* Allocates a new `Buffer` using an `array` of bytes in the range `0` – `255`.
|
||||||
|
* Array entries outside that range will be truncated to fit into it.
|
||||||
|
*
|
||||||
|
* ```js
|
||||||
|
* import { Buffer } from 'node:buffer';
|
||||||
|
*
|
||||||
|
* // Creates a new Buffer containing the UTF-8 bytes of the string 'buffer'.
|
||||||
|
* const buf = Buffer.from([0x62, 0x75, 0x66, 0x66, 0x65, 0x72]);
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* If `array` is an `Array`-like object (that is, one with a `length` property of
|
||||||
|
* type `number`), it is treated as if it is an array, unless it is a `Buffer` or
|
||||||
|
* a `Uint8Array`. This means all other `TypedArray` variants get treated as an
|
||||||
|
* `Array`. To create a `Buffer` from the bytes backing a `TypedArray`, use
|
||||||
|
* `Buffer.copyBytesFrom()`.
|
||||||
|
*
|
||||||
|
* A `TypeError` will be thrown if `array` is not an `Array` or another type
|
||||||
|
* appropriate for `Buffer.from()` variants.
|
||||||
|
*
|
||||||
|
* `Buffer.from(array)` and `Buffer.from(string)` may also use the internal
|
||||||
|
* `Buffer` pool like `Buffer.allocUnsafe()` does.
|
||||||
|
* @since v5.10.0
|
||||||
|
*/
|
||||||
|
from(array: WithImplicitCoercion<ArrayLike<number>>): Buffer<ArrayBuffer>;
|
||||||
|
/**
|
||||||
|
* This creates a view of the `ArrayBuffer` without copying the underlying
|
||||||
|
* memory. For example, when passed a reference to the `.buffer` property of a
|
||||||
|
* `TypedArray` instance, the newly created `Buffer` will share the same
|
||||||
|
* allocated memory as the `TypedArray`'s underlying `ArrayBuffer`.
|
||||||
|
*
|
||||||
|
* ```js
|
||||||
|
* import { Buffer } from 'node:buffer';
|
||||||
|
*
|
||||||
|
* const arr = new Uint16Array(2);
|
||||||
|
*
|
||||||
|
* arr[0] = 5000;
|
||||||
|
* arr[1] = 4000;
|
||||||
|
*
|
||||||
|
* // Shares memory with `arr`.
|
||||||
|
* const buf = Buffer.from(arr.buffer);
|
||||||
|
*
|
||||||
|
* console.log(buf);
|
||||||
|
* // Prints: <Buffer 88 13 a0 0f>
|
||||||
|
*
|
||||||
|
* // Changing the original Uint16Array changes the Buffer also.
|
||||||
|
* arr[1] = 6000;
|
||||||
|
*
|
||||||
|
* console.log(buf);
|
||||||
|
* // Prints: <Buffer 88 13 70 17>
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* The optional `byteOffset` and `length` arguments specify a memory range within
|
||||||
|
* the `arrayBuffer` that will be shared by the `Buffer`.
|
||||||
|
*
|
||||||
|
* ```js
|
||||||
|
* import { Buffer } from 'node:buffer';
|
||||||
|
*
|
||||||
|
* const ab = new ArrayBuffer(10);
|
||||||
|
* const buf = Buffer.from(ab, 0, 2);
|
||||||
|
*
|
||||||
|
* console.log(buf.length);
|
||||||
|
* // Prints: 2
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* A `TypeError` will be thrown if `arrayBuffer` is not an `ArrayBuffer` or a
|
||||||
|
* `SharedArrayBuffer` or another type appropriate for `Buffer.from()`
|
||||||
|
* variants.
|
||||||
|
*
|
||||||
|
* It is important to remember that a backing `ArrayBuffer` can cover a range
|
||||||
|
* of memory that extends beyond the bounds of a `TypedArray` view. A new
|
||||||
|
* `Buffer` created using the `buffer` property of a `TypedArray` may extend
|
||||||
|
* beyond the range of the `TypedArray`:
|
||||||
|
*
|
||||||
|
* ```js
|
||||||
|
* import { Buffer } from 'node:buffer';
|
||||||
|
*
|
||||||
|
* const arrA = Uint8Array.from([0x63, 0x64, 0x65, 0x66]); // 4 elements
|
||||||
|
* const arrB = new Uint8Array(arrA.buffer, 1, 2); // 2 elements
|
||||||
|
* console.log(arrA.buffer === arrB.buffer); // true
|
||||||
|
*
|
||||||
|
* const buf = Buffer.from(arrB.buffer);
|
||||||
|
* console.log(buf);
|
||||||
|
* // Prints: <Buffer 63 64 65 66>
|
||||||
|
* ```
|
||||||
|
* @since v5.10.0
|
||||||
|
* @param arrayBuffer An `ArrayBuffer`, `SharedArrayBuffer`, for example the
|
||||||
|
* `.buffer` property of a `TypedArray`.
|
||||||
|
* @param byteOffset Index of first byte to expose. **Default:** `0`.
|
||||||
|
* @param length Number of bytes to expose. **Default:**
|
||||||
|
* `arrayBuffer.byteLength - byteOffset`.
|
||||||
|
*/
|
||||||
|
from<TArrayBuffer extends WithImplicitCoercion<ArrayBufferLike>>(
|
||||||
|
arrayBuffer: TArrayBuffer,
|
||||||
|
byteOffset?: number,
|
||||||
|
length?: number,
|
||||||
|
): Buffer<ImplicitArrayBuffer<TArrayBuffer>>;
|
||||||
|
/**
|
||||||
|
* Creates a new `Buffer` containing `string`. The `encoding` parameter identifies
|
||||||
|
* the character encoding to be used when converting `string` into bytes.
|
||||||
|
*
|
||||||
|
* ```js
|
||||||
|
* import { Buffer } from 'node:buffer';
|
||||||
|
*
|
||||||
|
* const buf1 = Buffer.from('this is a tést');
|
||||||
|
* const buf2 = Buffer.from('7468697320697320612074c3a97374', 'hex');
|
||||||
|
*
|
||||||
|
* console.log(buf1.toString());
|
||||||
|
* // Prints: this is a tést
|
||||||
|
* console.log(buf2.toString());
|
||||||
|
* // Prints: this is a tést
|
||||||
|
* console.log(buf1.toString('latin1'));
|
||||||
|
* // Prints: this is a tést
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* A `TypeError` will be thrown if `string` is not a string or another type
|
||||||
|
* appropriate for `Buffer.from()` variants.
|
||||||
|
*
|
||||||
|
* `Buffer.from(string)` may also use the internal `Buffer` pool like
|
||||||
|
* `Buffer.allocUnsafe()` does.
|
||||||
|
* @since v5.10.0
|
||||||
|
* @param string A string to encode.
|
||||||
|
* @param encoding The encoding of `string`. **Default:** `'utf8'`.
|
||||||
|
*/
|
||||||
|
from(string: WithImplicitCoercion<string>, encoding?: BufferEncoding): Buffer<ArrayBuffer>;
|
||||||
|
/**
|
||||||
|
* Allocates a new `Buffer` of `size` bytes. If `fill` is `undefined`, the`Buffer` will be zero-filled.
|
||||||
|
*
|
||||||
|
* ```js
|
||||||
|
* import { Buffer } from 'node:buffer';
|
||||||
|
*
|
||||||
|
* const buf = Buffer.alloc(5);
|
||||||
|
*
|
||||||
|
* console.log(buf);
|
||||||
|
* // Prints: <Buffer 00 00 00 00 00>
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* If `size` is larger than {@link constants.MAX_LENGTH} or smaller than 0, `ERR_OUT_OF_RANGE` is thrown.
|
||||||
|
*
|
||||||
|
* If `fill` is specified, the allocated `Buffer` will be initialized by calling `buf.fill(fill)`.
|
||||||
|
*
|
||||||
|
* ```js
|
||||||
|
* import { Buffer } from 'node:buffer';
|
||||||
|
*
|
||||||
|
* const buf = Buffer.alloc(5, 'a');
|
||||||
|
*
|
||||||
|
* console.log(buf);
|
||||||
|
* // Prints: <Buffer 61 61 61 61 61>
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* If both `fill` and `encoding` are specified, the allocated `Buffer` will be
|
||||||
|
* initialized by calling `buf.fill(fill, encoding)`.
|
||||||
|
*
|
||||||
|
* ```js
|
||||||
|
* import { Buffer } from 'node:buffer';
|
||||||
|
*
|
||||||
|
* const buf = Buffer.alloc(11, 'aGVsbG8gd29ybGQ=', 'base64');
|
||||||
|
*
|
||||||
|
* console.log(buf);
|
||||||
|
* // Prints: <Buffer 68 65 6c 6c 6f 20 77 6f 72 6c 64>
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* Calling `Buffer.alloc()` can be measurably slower than the alternative `Buffer.allocUnsafe()` but ensures that the newly created `Buffer` instance
|
||||||
|
* contents will never contain sensitive data from previous allocations, including
|
||||||
|
* data that might not have been allocated for `Buffer`s.
|
||||||
|
*
|
||||||
|
* A `TypeError` will be thrown if `size` is not a number.
|
||||||
|
* @since v5.10.0
|
||||||
|
* @param size The desired length of the new `Buffer`.
|
||||||
|
* @param [fill=0] A value to pre-fill the new `Buffer` with.
|
||||||
|
* @param [encoding='utf8'] If `fill` is a string, this is its encoding.
|
||||||
|
*/
|
||||||
|
alloc(size: number, fill?: string | Uint8Array | number, encoding?: BufferEncoding): Buffer<ArrayBuffer>;
|
||||||
|
}
|
||||||
|
interface Buffer<TArrayBuffer extends ArrayBufferLike = ArrayBufferLike> extends Uint8Array<TArrayBuffer> {
|
||||||
|
// see buffer.d.ts for implementation shared with all TypeScript versions
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
1318
goja_nodejs/buffer/types/buffer.d.ts
vendored
Normal file
1318
goja_nodejs/buffer/types/buffer.d.ts
vendored
Normal file
File diff suppressed because it is too large
Load Diff
19
goja_nodejs/buffer/types/package.json
Normal file
19
goja_nodejs/buffer/types/package.json
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"name": "@dop251/types-goja_nodejs-buffer",
|
||||||
|
"version": "0.0.1-rc2",
|
||||||
|
"types": "buffer.d.ts",
|
||||||
|
"scripts": {
|
||||||
|
"test": "tsc"
|
||||||
|
},
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "git+https://apigo.cc/gojs/goja_nodejs.git"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@dop251/types-goja_nodejs-global": "0.0.1-rc2"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"typescript": "next"
|
||||||
|
},
|
||||||
|
"private": false
|
||||||
|
}
|
||||||
16
goja_nodejs/buffer/types/tsconfig.json
Normal file
16
goja_nodejs/buffer/types/tsconfig.json
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"module": "commonjs",
|
||||||
|
"target": "esnext",
|
||||||
|
"lib": [
|
||||||
|
"es6",
|
||||||
|
"dom"
|
||||||
|
],
|
||||||
|
"noImplicitAny": true,
|
||||||
|
"noImplicitThis": true,
|
||||||
|
"strictNullChecks": true,
|
||||||
|
"strictFunctionTypes": true,
|
||||||
|
"noEmit": true,
|
||||||
|
"forceConsistentCasingInFileNames": true
|
||||||
|
}
|
||||||
|
}
|
||||||
78
goja_nodejs/console/module_test.go
Normal file
78
goja_nodejs/console/module_test.go
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
package console
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"apigo.cc/gojs/goja"
|
||||||
|
"apigo.cc/gojs/goja_nodejs/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestConsole(t *testing.T) {
|
||||||
|
vm := goja.New()
|
||||||
|
|
||||||
|
new(require.Registry).Enable(vm)
|
||||||
|
Enable(vm)
|
||||||
|
|
||||||
|
if c := vm.Get("console"); c == nil {
|
||||||
|
t.Fatal("console not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := vm.RunString("console.log('')"); err != nil {
|
||||||
|
t.Fatal("console.log() error", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := vm.RunString("console.error('')"); err != nil {
|
||||||
|
t.Fatal("console.error() error", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := vm.RunString("console.warn('')"); err != nil {
|
||||||
|
t.Fatal("console.warn() error", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := vm.RunString("console.info('')"); err != nil {
|
||||||
|
t.Fatal("console.info() error", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := vm.RunString("console.debug('')"); err != nil {
|
||||||
|
t.Fatal("console.debug() error", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestConsoleWithPrinter(t *testing.T) {
|
||||||
|
var stdoutStr, stderrStr string
|
||||||
|
|
||||||
|
printer := StdPrinter{
|
||||||
|
StdoutPrint: func(s string) { stdoutStr += s },
|
||||||
|
StderrPrint: func(s string) { stderrStr += s },
|
||||||
|
}
|
||||||
|
|
||||||
|
vm := goja.New()
|
||||||
|
|
||||||
|
registry := new(require.Registry)
|
||||||
|
registry.Enable(vm)
|
||||||
|
registry.RegisterNativeModule(ModuleName, RequireWithPrinter(printer))
|
||||||
|
Enable(vm)
|
||||||
|
|
||||||
|
if c := vm.Get("console"); c == nil {
|
||||||
|
t.Fatal("console not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := vm.RunString(`
|
||||||
|
console.log('a')
|
||||||
|
console.error('b')
|
||||||
|
console.warn('c')
|
||||||
|
console.debug('d')
|
||||||
|
console.info('e')
|
||||||
|
`)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if want := "ade"; stdoutStr != want {
|
||||||
|
t.Fatalf("Unexpected stdout output: got %q, want %q", stdoutStr, want)
|
||||||
|
}
|
||||||
|
|
||||||
|
if want := "bc"; stderrStr != want {
|
||||||
|
t.Fatalf("Unexpected stderr output: got %q, want %q", stderrStr, want)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -11,6 +11,7 @@ const (
|
|||||||
ErrCodeInvalidArgValue = "ERR_INVALID_ARG_VALUE"
|
ErrCodeInvalidArgValue = "ERR_INVALID_ARG_VALUE"
|
||||||
ErrCodeInvalidThis = "ERR_INVALID_THIS"
|
ErrCodeInvalidThis = "ERR_INVALID_THIS"
|
||||||
ErrCodeMissingArgs = "ERR_MISSING_ARGS"
|
ErrCodeMissingArgs = "ERR_MISSING_ARGS"
|
||||||
|
ErrCodeOutOfRange = "ERR_OUT_OF_RANGE"
|
||||||
)
|
)
|
||||||
|
|
||||||
func error_toString(call goja.FunctionCall, r *goja.Runtime) goja.Value {
|
func error_toString(call goja.FunctionCall, r *goja.Runtime) goja.Value {
|
||||||
@ -50,6 +51,11 @@ func NewTypeError(r *goja.Runtime, code string, params ...interface{}) *goja.Obj
|
|||||||
return e
|
return e
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func NewRangeError(r *goja.Runtime, code string, params ...interface{}) *goja.Object {
|
||||||
|
ctor, _ := r.Get("RangeError").(*goja.Object)
|
||||||
|
return NewError(r, ctor, code, params...)
|
||||||
|
}
|
||||||
|
|
||||||
func NewError(r *goja.Runtime, ctor *goja.Object, code string, args ...interface{}) *goja.Object {
|
func NewError(r *goja.Runtime, ctor *goja.Object, code string, args ...interface{}) *goja.Object {
|
||||||
if ctor == nil {
|
if ctor == nil {
|
||||||
ctor, _ = r.Get("Error").(*goja.Object)
|
ctor, _ = r.Get("Error").(*goja.Object)
|
||||||
@ -69,3 +75,23 @@ func NewError(r *goja.Runtime, ctor *goja.Object, code string, args ...interface
|
|||||||
addProps(r, o, code)
|
addProps(r, o, code)
|
||||||
return o
|
return o
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func NewArgumentNotBigIntTypeError(r *goja.Runtime, name string) *goja.Object {
|
||||||
|
return NewNotCorrectTypeError(r, name, "BigInt")
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewArgumentNotStringTypeError(r *goja.Runtime, name string) *goja.Object {
|
||||||
|
return NewNotCorrectTypeError(r, name, "string")
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewArgumentNotNumberTypeError(r *goja.Runtime, name string) *goja.Object {
|
||||||
|
return NewNotCorrectTypeError(r, name, "number")
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewNotCorrectTypeError(r *goja.Runtime, name, _type string) *goja.Object {
|
||||||
|
return NewTypeError(r, ErrCodeInvalidArgType, "The \"%s\" argument must be of type %s.", name, _type)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewArgumentOutOfRangeError(r *goja.Runtime, name string, v any) *goja.Object {
|
||||||
|
return NewRangeError(r, ErrCodeOutOfRange, "The value of \"%s\" %v is out of range.", name, v)
|
||||||
|
}
|
||||||
|
|||||||
641
goja_nodejs/eventloop/eventloop_test.go
Normal file
641
goja_nodejs/eventloop/eventloop_test.go
Normal file
@ -0,0 +1,641 @@
|
|||||||
|
package eventloop
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"sync/atomic"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"apigo.cc/gojs/goja"
|
||||||
|
|
||||||
|
"go.uber.org/goleak"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestRun(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
const SCRIPT = `
|
||||||
|
var calledAt;
|
||||||
|
setTimeout(function() {
|
||||||
|
calledAt = now();
|
||||||
|
}, 1000);
|
||||||
|
`
|
||||||
|
|
||||||
|
loop := NewEventLoop()
|
||||||
|
prg, err := goja.Compile("main.js", SCRIPT, false)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
startTime := time.Now()
|
||||||
|
loop.Run(func(vm *goja.Runtime) {
|
||||||
|
vm.Set("now", time.Now)
|
||||||
|
_, err = vm.RunProgram(prg)
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
var calledAt time.Time
|
||||||
|
loop.Run(func(vm *goja.Runtime) {
|
||||||
|
err = vm.ExportTo(vm.Get("calledAt"), &calledAt)
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if calledAt.IsZero() {
|
||||||
|
t.Fatal("Not called")
|
||||||
|
}
|
||||||
|
if dur := calledAt.Sub(startTime); dur < time.Second {
|
||||||
|
t.Fatal(dur)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStart(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
const SCRIPT = `
|
||||||
|
var calledAt;
|
||||||
|
setTimeout(function() {
|
||||||
|
calledAt = now();
|
||||||
|
}, 1000);
|
||||||
|
`
|
||||||
|
|
||||||
|
prg, err := goja.Compile("main.js", SCRIPT, false)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
loop := NewEventLoop()
|
||||||
|
startTime := time.Now()
|
||||||
|
loop.Start()
|
||||||
|
|
||||||
|
loop.RunOnLoop(func(vm *goja.Runtime) {
|
||||||
|
vm.Set("now", time.Now)
|
||||||
|
vm.RunProgram(prg)
|
||||||
|
})
|
||||||
|
|
||||||
|
time.Sleep(2 * time.Second)
|
||||||
|
if remainingJobs := loop.Stop(); remainingJobs != 0 {
|
||||||
|
t.Fatal(remainingJobs)
|
||||||
|
}
|
||||||
|
|
||||||
|
var calledAt time.Time
|
||||||
|
loop.Run(func(vm *goja.Runtime) {
|
||||||
|
err = vm.ExportTo(vm.Get("calledAt"), &calledAt)
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if calledAt.IsZero() {
|
||||||
|
t.Fatal("Not called")
|
||||||
|
}
|
||||||
|
if dur := calledAt.Sub(startTime); dur < time.Second {
|
||||||
|
t.Fatal(dur)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStartInForeground(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
const SCRIPT = `
|
||||||
|
var calledAt;
|
||||||
|
setTimeout(function() {
|
||||||
|
calledAt = now();
|
||||||
|
}, 1000);
|
||||||
|
`
|
||||||
|
|
||||||
|
prg, err := goja.Compile("main.js", SCRIPT, false)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
loop := NewEventLoop()
|
||||||
|
startTime := time.Now()
|
||||||
|
go loop.StartInForeground()
|
||||||
|
|
||||||
|
loop.RunOnLoop(func(vm *goja.Runtime) {
|
||||||
|
vm.Set("now", time.Now)
|
||||||
|
vm.RunProgram(prg)
|
||||||
|
})
|
||||||
|
|
||||||
|
time.Sleep(2 * time.Second)
|
||||||
|
if remainingJobs := loop.Stop(); remainingJobs != 0 {
|
||||||
|
t.Fatal(remainingJobs)
|
||||||
|
}
|
||||||
|
|
||||||
|
var calledAt time.Time
|
||||||
|
loop.Run(func(vm *goja.Runtime) {
|
||||||
|
err = vm.ExportTo(vm.Get("calledAt"), &calledAt)
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if calledAt.IsZero() {
|
||||||
|
t.Fatal("Not called")
|
||||||
|
}
|
||||||
|
if dur := calledAt.Sub(startTime); dur < time.Second {
|
||||||
|
t.Fatal(dur)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInterval(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
const SCRIPT = `
|
||||||
|
var count = 0;
|
||||||
|
var t = setInterval(function(times) {
|
||||||
|
console.log("tick");
|
||||||
|
if (++count > times) {
|
||||||
|
clearInterval(t);
|
||||||
|
}
|
||||||
|
}, 1000, 2);
|
||||||
|
console.log("Started");
|
||||||
|
`
|
||||||
|
|
||||||
|
loop := NewEventLoop()
|
||||||
|
prg, err := goja.Compile("main.js", SCRIPT, false)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
loop.Run(func(vm *goja.Runtime) {
|
||||||
|
_, err = vm.RunProgram(prg)
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var count int64
|
||||||
|
loop.Run(func(vm *goja.Runtime) {
|
||||||
|
count = vm.Get("count").ToInteger()
|
||||||
|
})
|
||||||
|
if count != 3 {
|
||||||
|
t.Fatal(count)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestImmediate(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
const SCRIPT = `
|
||||||
|
let log = [];
|
||||||
|
function cb(arg) {
|
||||||
|
log.push(arg);
|
||||||
|
}
|
||||||
|
var i;
|
||||||
|
var t = setImmediate(function() {
|
||||||
|
cb("tick");
|
||||||
|
setImmediate(cb, "tick 2");
|
||||||
|
i = setImmediate(cb, "should not run")
|
||||||
|
});
|
||||||
|
setImmediate(function() {
|
||||||
|
clearImmediate(i);
|
||||||
|
});
|
||||||
|
cb("Started");
|
||||||
|
`
|
||||||
|
|
||||||
|
loop := NewEventLoop()
|
||||||
|
prg, err := goja.Compile("main.js", SCRIPT, false)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
loop.Run(func(vm *goja.Runtime) {
|
||||||
|
_, err = vm.RunProgram(prg)
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
loop.Run(func(vm *goja.Runtime) {
|
||||||
|
_, err = vm.RunString(`
|
||||||
|
if (log.length != 3) {
|
||||||
|
throw new Error("Invalid log length: " + log);
|
||||||
|
}
|
||||||
|
if (log[0] !== "Started" || log[1] !== "tick" || log[2] !== "tick 2") {
|
||||||
|
throw new Error("Invalid log: " + log);
|
||||||
|
}
|
||||||
|
`)
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRunNoSchedule(t *testing.T) {
|
||||||
|
loop := NewEventLoop()
|
||||||
|
fired := false
|
||||||
|
loop.Run(func(vm *goja.Runtime) { // should not hang
|
||||||
|
fired = true
|
||||||
|
// do not schedule anything
|
||||||
|
})
|
||||||
|
|
||||||
|
if !fired {
|
||||||
|
t.Fatal("Not fired")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRunWithConsole(t *testing.T) {
|
||||||
|
const SCRIPT = `
|
||||||
|
console.log("Started");
|
||||||
|
`
|
||||||
|
|
||||||
|
loop := NewEventLoop()
|
||||||
|
prg, err := goja.Compile("main.js", SCRIPT, false)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
loop.Run(func(vm *goja.Runtime) {
|
||||||
|
_, err = vm.RunProgram(prg)
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("Call to console.log generated an error", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
loop = NewEventLoop(EnableConsole(true))
|
||||||
|
prg, err = goja.Compile("main.js", SCRIPT, false)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
loop.Run(func(vm *goja.Runtime) {
|
||||||
|
_, err = vm.RunProgram(prg)
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal("Call to console.log generated an error", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRunNoConsole(t *testing.T) {
|
||||||
|
const SCRIPT = `
|
||||||
|
console.log("Started");
|
||||||
|
`
|
||||||
|
|
||||||
|
loop := NewEventLoop(EnableConsole(false))
|
||||||
|
prg, err := goja.Compile("main.js", SCRIPT, false)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
loop.Run(func(vm *goja.Runtime) {
|
||||||
|
_, err = vm.RunProgram(prg)
|
||||||
|
})
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("Call to console.log did not generate an error", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestClearIntervalRace(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
const SCRIPT = `
|
||||||
|
console.log("calling setInterval");
|
||||||
|
var t = setInterval(function() {
|
||||||
|
console.log("tick");
|
||||||
|
}, 500);
|
||||||
|
console.log("calling sleep");
|
||||||
|
sleep(2000);
|
||||||
|
console.log("calling clearInterval");
|
||||||
|
clearInterval(t);
|
||||||
|
`
|
||||||
|
|
||||||
|
loop := NewEventLoop()
|
||||||
|
prg, err := goja.Compile("main.js", SCRIPT, false)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
// Should not hang
|
||||||
|
loop.Run(func(vm *goja.Runtime) {
|
||||||
|
vm.Set("sleep", func(ms int) {
|
||||||
|
<-time.After(time.Duration(ms) * time.Millisecond)
|
||||||
|
})
|
||||||
|
vm.RunProgram(prg)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNativeTimeout(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
fired := false
|
||||||
|
loop := NewEventLoop()
|
||||||
|
loop.SetTimeout(func(*goja.Runtime) {
|
||||||
|
fired = true
|
||||||
|
}, 1*time.Second)
|
||||||
|
loop.Run(func(*goja.Runtime) {
|
||||||
|
// do not schedule anything
|
||||||
|
})
|
||||||
|
if !fired {
|
||||||
|
t.Fatal("Not fired")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNativeClearTimeout(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
fired := false
|
||||||
|
loop := NewEventLoop()
|
||||||
|
timer := loop.SetTimeout(func(*goja.Runtime) {
|
||||||
|
fired = true
|
||||||
|
}, 2*time.Second)
|
||||||
|
loop.SetTimeout(func(*goja.Runtime) {
|
||||||
|
loop.ClearTimeout(timer)
|
||||||
|
}, 1*time.Second)
|
||||||
|
loop.Run(func(*goja.Runtime) {
|
||||||
|
// do not schedule anything
|
||||||
|
})
|
||||||
|
if fired {
|
||||||
|
t.Fatal("Cancelled timer fired!")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNativeInterval(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
count := 0
|
||||||
|
loop := NewEventLoop()
|
||||||
|
var i *Interval
|
||||||
|
i = loop.SetInterval(func(*goja.Runtime) {
|
||||||
|
t.Log("tick")
|
||||||
|
count++
|
||||||
|
if count > 2 {
|
||||||
|
loop.ClearInterval(i)
|
||||||
|
}
|
||||||
|
}, 1*time.Second)
|
||||||
|
loop.Run(func(*goja.Runtime) {
|
||||||
|
// do not schedule anything
|
||||||
|
})
|
||||||
|
if count != 3 {
|
||||||
|
t.Fatal("Expected interval to fire 3 times, got", count)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNativeClearInterval(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
count := 0
|
||||||
|
loop := NewEventLoop()
|
||||||
|
loop.Run(func(*goja.Runtime) {
|
||||||
|
i := loop.SetInterval(func(*goja.Runtime) {
|
||||||
|
t.Log("tick")
|
||||||
|
count++
|
||||||
|
}, 500*time.Millisecond)
|
||||||
|
<-time.After(2 * time.Second)
|
||||||
|
loop.ClearInterval(i)
|
||||||
|
})
|
||||||
|
if count != 0 {
|
||||||
|
t.Fatal("Expected interval to fire 0 times, got", count)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSetAndClearOnStoppedLoop(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
loop := NewEventLoop()
|
||||||
|
timeout := loop.SetTimeout(func(runtime *goja.Runtime) {
|
||||||
|
panic("must not run")
|
||||||
|
}, 1*time.Millisecond)
|
||||||
|
loop.ClearTimeout(timeout)
|
||||||
|
loop.Start()
|
||||||
|
time.Sleep(10 * time.Millisecond)
|
||||||
|
loop.Terminate()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSetTimeoutConcurrent(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
loop := NewEventLoop()
|
||||||
|
loop.Start()
|
||||||
|
ch := make(chan struct{}, 1)
|
||||||
|
loop.SetTimeout(func(*goja.Runtime) {
|
||||||
|
ch <- struct{}{}
|
||||||
|
}, 100*time.Millisecond)
|
||||||
|
<-ch
|
||||||
|
loop.Stop()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestClearTimeoutConcurrent(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
loop := NewEventLoop()
|
||||||
|
loop.Start()
|
||||||
|
timer := loop.SetTimeout(func(*goja.Runtime) {
|
||||||
|
}, 100*time.Millisecond)
|
||||||
|
loop.ClearTimeout(timer)
|
||||||
|
loop.Stop()
|
||||||
|
if c := loop.jobCount; c != 0 {
|
||||||
|
t.Fatalf("jobCount: %d", c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestClearIntervalConcurrent(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
loop := NewEventLoop()
|
||||||
|
loop.Start()
|
||||||
|
ch := make(chan struct{}, 1)
|
||||||
|
i := loop.SetInterval(func(*goja.Runtime) {
|
||||||
|
ch <- struct{}{}
|
||||||
|
}, 500*time.Millisecond)
|
||||||
|
|
||||||
|
<-ch
|
||||||
|
loop.ClearInterval(i)
|
||||||
|
loop.Stop()
|
||||||
|
if c := loop.jobCount; c != 0 {
|
||||||
|
t.Fatalf("jobCount: %d", c)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRunOnStoppedLoop(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
loop := NewEventLoop()
|
||||||
|
var failed int32
|
||||||
|
done := make(chan struct{})
|
||||||
|
go func() {
|
||||||
|
for atomic.LoadInt32(&failed) == 0 {
|
||||||
|
loop.Start()
|
||||||
|
time.Sleep(10 * time.Millisecond)
|
||||||
|
loop.Stop()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
go func() {
|
||||||
|
for atomic.LoadInt32(&failed) == 0 {
|
||||||
|
loop.RunOnLoop(func(*goja.Runtime) {
|
||||||
|
if !loop.running {
|
||||||
|
atomic.StoreInt32(&failed, 1)
|
||||||
|
close(done)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
})
|
||||||
|
time.Sleep(10 * time.Millisecond)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
select {
|
||||||
|
case <-done:
|
||||||
|
case <-time.After(5 * time.Second):
|
||||||
|
}
|
||||||
|
if atomic.LoadInt32(&failed) != 0 {
|
||||||
|
t.Fatal("running job on stopped loop")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPromise(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
const SCRIPT = `
|
||||||
|
let result;
|
||||||
|
const p = new Promise((resolve, reject) => {
|
||||||
|
setTimeout(() => {resolve("passed")}, 500);
|
||||||
|
});
|
||||||
|
p.then(value => {
|
||||||
|
result = value;
|
||||||
|
});
|
||||||
|
`
|
||||||
|
|
||||||
|
loop := NewEventLoop()
|
||||||
|
prg, err := goja.Compile("main.js", SCRIPT, false)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
loop.Run(func(vm *goja.Runtime) {
|
||||||
|
_, err = vm.RunProgram(prg)
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
loop.Run(func(vm *goja.Runtime) {
|
||||||
|
result := vm.Get("result")
|
||||||
|
if !result.SameAs(vm.ToValue("passed")) {
|
||||||
|
err = fmt.Errorf("unexpected result: %v", result)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPromiseNative(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
const SCRIPT = `
|
||||||
|
let result;
|
||||||
|
p.then(value => {
|
||||||
|
result = value;
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
`
|
||||||
|
|
||||||
|
loop := NewEventLoop()
|
||||||
|
prg, err := goja.Compile("main.js", SCRIPT, false)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
ch := make(chan error)
|
||||||
|
loop.Start()
|
||||||
|
defer loop.Stop()
|
||||||
|
|
||||||
|
loop.RunOnLoop(func(vm *goja.Runtime) {
|
||||||
|
vm.Set("done", func() {
|
||||||
|
ch <- nil
|
||||||
|
})
|
||||||
|
p, resolve, _ := vm.NewPromise()
|
||||||
|
vm.Set("p", p)
|
||||||
|
_, err = vm.RunProgram(prg)
|
||||||
|
if err != nil {
|
||||||
|
ch <- err
|
||||||
|
return
|
||||||
|
}
|
||||||
|
go func() {
|
||||||
|
time.Sleep(500 * time.Millisecond)
|
||||||
|
loop.RunOnLoop(func(*goja.Runtime) {
|
||||||
|
resolve("passed")
|
||||||
|
})
|
||||||
|
}()
|
||||||
|
})
|
||||||
|
err = <-ch
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
loop.RunOnLoop(func(vm *goja.Runtime) {
|
||||||
|
result := vm.Get("result")
|
||||||
|
if !result.SameAs(vm.ToValue("passed")) {
|
||||||
|
ch <- fmt.Errorf("unexpected result: %v", result)
|
||||||
|
} else {
|
||||||
|
ch <- nil
|
||||||
|
}
|
||||||
|
})
|
||||||
|
err = <-ch
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEventLoop_StopNoWait(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
loop := NewEventLoop()
|
||||||
|
var ran int32
|
||||||
|
loop.Run(func(runtime *goja.Runtime) {
|
||||||
|
loop.SetTimeout(func(*goja.Runtime) {
|
||||||
|
atomic.StoreInt32(&ran, 1)
|
||||||
|
}, 5*time.Second)
|
||||||
|
|
||||||
|
loop.SetTimeout(func(*goja.Runtime) {
|
||||||
|
loop.StopNoWait()
|
||||||
|
}, 500*time.Millisecond)
|
||||||
|
})
|
||||||
|
|
||||||
|
if atomic.LoadInt32(&ran) != 0 {
|
||||||
|
t.Fatal("ran != 0")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEventLoop_ClearRunningTimeout(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
const SCRIPT = `
|
||||||
|
var called = 0;
|
||||||
|
let aTimer;
|
||||||
|
function a() {
|
||||||
|
if (++called > 5) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (aTimer) {
|
||||||
|
clearTimeout(aTimer);
|
||||||
|
}
|
||||||
|
console.log("ok");
|
||||||
|
aTimer = setTimeout(a, 500);
|
||||||
|
}
|
||||||
|
a();`
|
||||||
|
|
||||||
|
prg, err := goja.Compile("main.js", SCRIPT, false)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
loop := NewEventLoop()
|
||||||
|
|
||||||
|
loop.Run(func(vm *goja.Runtime) {
|
||||||
|
_, err = vm.RunProgram(prg)
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var called int64
|
||||||
|
loop.Run(func(vm *goja.Runtime) {
|
||||||
|
called = vm.Get("called").ToInteger()
|
||||||
|
})
|
||||||
|
if called != 6 {
|
||||||
|
t.Fatal(called)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEventLoop_Terminate(t *testing.T) {
|
||||||
|
defer goleak.VerifyNone(t)
|
||||||
|
|
||||||
|
loop := NewEventLoop()
|
||||||
|
loop.Start()
|
||||||
|
interval := loop.SetInterval(func(vm *goja.Runtime) {}, 10*time.Millisecond)
|
||||||
|
time.Sleep(500 * time.Millisecond)
|
||||||
|
loop.ClearInterval(interval)
|
||||||
|
loop.Terminate()
|
||||||
|
|
||||||
|
if loop.SetTimeout(func(*goja.Runtime) {}, time.Millisecond) != nil {
|
||||||
|
t.Fatal("was able to SetTimeout()")
|
||||||
|
}
|
||||||
|
if loop.SetInterval(func(*goja.Runtime) {}, time.Millisecond) != nil {
|
||||||
|
t.Fatal("was able to SetInterval()")
|
||||||
|
}
|
||||||
|
if loop.RunOnLoop(func(*goja.Runtime) {}) {
|
||||||
|
t.Fatal("was able to RunOnLoop()")
|
||||||
|
}
|
||||||
|
|
||||||
|
ch := make(chan struct{})
|
||||||
|
loop.Start()
|
||||||
|
if !loop.RunOnLoop(func(runtime *goja.Runtime) {
|
||||||
|
close(ch)
|
||||||
|
}) {
|
||||||
|
t.Fatal("RunOnLoop() has failed after restart")
|
||||||
|
}
|
||||||
|
<-ch
|
||||||
|
loop.Terminate()
|
||||||
|
}
|
||||||
3
goja_nodejs/global-types/README.md
Normal file
3
goja_nodejs/global-types/README.md
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
## Core type definitions for goja_nodejs.
|
||||||
|
|
||||||
|
This package is used by other type definition packages for goja_nodejs. You probably do not need to install it directly.
|
||||||
16
goja_nodejs/global-types/globals.d.ts
vendored
Normal file
16
goja_nodejs/global-types/globals.d.ts
vendored
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
export {};
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
namespace GojaNodeJS {
|
||||||
|
interface Iterator<T, TReturn = any, TNext = any> extends IteratorObject<T, TReturn, TNext> {
|
||||||
|
[Symbol.iterator](): GojaNodeJS.Iterator<T, TReturn, TNext>;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Polyfill for TS 5.6's instrinsic BuiltinIteratorReturn type, required for DOM-compatible iterators
|
||||||
|
type BuiltinIteratorReturn = ReturnType<any[][typeof Symbol.iterator]> extends
|
||||||
|
globalThis.Iterator<any, infer TReturn> ? TReturn
|
||||||
|
: any;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
16
goja_nodejs/global-types/package.json
Normal file
16
goja_nodejs/global-types/package.json
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
"name": "@dop251/types-goja_nodejs-global",
|
||||||
|
"version": "0.0.1-rc2",
|
||||||
|
"types": "globals.d.ts",
|
||||||
|
"scripts": {
|
||||||
|
"test": "tsc --noEmit"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"typescript": "next"
|
||||||
|
},
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "git+https://apigo.cc/gojs/goja_nodejs.git"
|
||||||
|
},
|
||||||
|
"private": false
|
||||||
|
}
|
||||||
18
goja_nodejs/global-types/tsconfig.json
Normal file
18
goja_nodejs/global-types/tsconfig.json
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
{
|
||||||
|
"files": ["globals.d.ts"],
|
||||||
|
"compilerOptions": {
|
||||||
|
"module": "commonjs",
|
||||||
|
"target": "esnext",
|
||||||
|
"lib": [
|
||||||
|
"es6",
|
||||||
|
"dom"
|
||||||
|
],
|
||||||
|
"noImplicitAny": true,
|
||||||
|
"noImplicitThis": true,
|
||||||
|
"strictNullChecks": true,
|
||||||
|
"strictFunctionTypes": true,
|
||||||
|
"types": [],
|
||||||
|
"noEmit": true,
|
||||||
|
"forceConsistentCasingInFileNames": true
|
||||||
|
}
|
||||||
|
}
|
||||||
84
goja_nodejs/goutil/argtypes.go
Normal file
84
goja_nodejs/goutil/argtypes.go
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
package goutil
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math/big"
|
||||||
|
|
||||||
|
"apigo.cc/gojs/goja"
|
||||||
|
"apigo.cc/gojs/goja_nodejs/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
func RequiredIntegerArgument(r *goja.Runtime, call goja.FunctionCall, name string, argIndex int) int64 {
|
||||||
|
arg := call.Argument(argIndex)
|
||||||
|
if goja.IsNumber(arg) {
|
||||||
|
return arg.ToInteger()
|
||||||
|
}
|
||||||
|
if goja.IsUndefined(arg) {
|
||||||
|
panic(errors.NewTypeError(r, errors.ErrCodeInvalidArgType, "The \"%s\" argument is required.", name))
|
||||||
|
}
|
||||||
|
|
||||||
|
panic(errors.NewArgumentNotNumberTypeError(r, name))
|
||||||
|
}
|
||||||
|
|
||||||
|
func RequiredFloatArgument(r *goja.Runtime, call goja.FunctionCall, name string, argIndex int) float64 {
|
||||||
|
arg := call.Argument(argIndex)
|
||||||
|
if goja.IsNumber(arg) {
|
||||||
|
return arg.ToFloat()
|
||||||
|
}
|
||||||
|
if goja.IsUndefined(arg) {
|
||||||
|
panic(errors.NewTypeError(r, errors.ErrCodeInvalidArgType, "The \"%s\" argument is required.", name))
|
||||||
|
}
|
||||||
|
|
||||||
|
panic(errors.NewArgumentNotNumberTypeError(r, name))
|
||||||
|
}
|
||||||
|
|
||||||
|
func CoercedIntegerArgument(call goja.FunctionCall, argIndex int, defaultValue int64, typeMistMatchValue int64) int64 {
|
||||||
|
arg := call.Argument(argIndex)
|
||||||
|
if goja.IsNumber(arg) {
|
||||||
|
return arg.ToInteger()
|
||||||
|
}
|
||||||
|
if goja.IsUndefined(arg) {
|
||||||
|
return defaultValue
|
||||||
|
}
|
||||||
|
|
||||||
|
return typeMistMatchValue
|
||||||
|
}
|
||||||
|
|
||||||
|
func OptionalIntegerArgument(r *goja.Runtime, call goja.FunctionCall, name string, argIndex int, defaultValue int64) int64 {
|
||||||
|
arg := call.Argument(argIndex)
|
||||||
|
if goja.IsNumber(arg) {
|
||||||
|
return arg.ToInteger()
|
||||||
|
}
|
||||||
|
if goja.IsUndefined(arg) {
|
||||||
|
return defaultValue
|
||||||
|
}
|
||||||
|
|
||||||
|
panic(errors.NewArgumentNotNumberTypeError(r, name))
|
||||||
|
}
|
||||||
|
|
||||||
|
func RequiredBigIntArgument(r *goja.Runtime, call goja.FunctionCall, name string, argIndex int) *big.Int {
|
||||||
|
arg := call.Argument(argIndex)
|
||||||
|
if goja.IsUndefined(arg) {
|
||||||
|
panic(errors.NewTypeError(r, errors.ErrCodeInvalidArgType, "The \"%s\" argument is required.", name))
|
||||||
|
}
|
||||||
|
if !goja.IsBigInt(arg) {
|
||||||
|
panic(errors.NewArgumentNotBigIntTypeError(r, name))
|
||||||
|
}
|
||||||
|
|
||||||
|
n, _ := arg.Export().(*big.Int)
|
||||||
|
if n == nil {
|
||||||
|
n = new(big.Int)
|
||||||
|
}
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
|
||||||
|
func RequiredStringArgument(r *goja.Runtime, call goja.FunctionCall, name string, argIndex int) string {
|
||||||
|
arg := call.Argument(argIndex)
|
||||||
|
if goja.IsString(arg) {
|
||||||
|
return arg.String()
|
||||||
|
}
|
||||||
|
if goja.IsUndefined(arg) {
|
||||||
|
panic(errors.NewTypeError(r, errors.ErrCodeInvalidArgType, "The \"%s\" argument is required.", name))
|
||||||
|
}
|
||||||
|
|
||||||
|
panic(errors.NewArgumentNotStringTypeError(r, name))
|
||||||
|
}
|
||||||
67
goja_nodejs/package-lock.json
generated
Normal file
67
goja_nodejs/package-lock.json
generated
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
{
|
||||||
|
"name": "goja_nodejs",
|
||||||
|
"lockfileVersion": 3,
|
||||||
|
"requires": true,
|
||||||
|
"packages": {
|
||||||
|
"": {
|
||||||
|
"workspaces": [
|
||||||
|
"global-types",
|
||||||
|
"url/types",
|
||||||
|
"buffer/types"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"buffer/types": {
|
||||||
|
"name": "@dop251/types-goja_nodejs-buffer",
|
||||||
|
"version": "0.0.1-rc2",
|
||||||
|
"dependencies": {
|
||||||
|
"@dop251/types-goja_nodejs-global": "0.0.1-rc2"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"typescript": "next"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"global-types": {
|
||||||
|
"name": "@dop251/types-goja_nodejs-global",
|
||||||
|
"version": "0.0.1-rc2",
|
||||||
|
"devDependencies": {
|
||||||
|
"typescript": "next"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@dop251/types-goja_nodejs-buffer": {
|
||||||
|
"resolved": "buffer/types",
|
||||||
|
"link": true
|
||||||
|
},
|
||||||
|
"node_modules/@dop251/types-goja_nodejs-global": {
|
||||||
|
"resolved": "global-types",
|
||||||
|
"link": true
|
||||||
|
},
|
||||||
|
"node_modules/@dop251/types-goja_nodejs-url": {
|
||||||
|
"resolved": "url/types",
|
||||||
|
"link": true
|
||||||
|
},
|
||||||
|
"node_modules/typescript": {
|
||||||
|
"version": "5.9.0-dev.20250314",
|
||||||
|
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.0-dev.20250314.tgz",
|
||||||
|
"integrity": "sha512-b9eLo5FjlR0BRMsYIxZYCrtTTUu97N1bh+DpQFCEm5OfRGzUg/Oc09fgct4jA4NF7R5Yg9oxWqVT90uto1TsvA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"bin": {
|
||||||
|
"tsc": "bin/tsc",
|
||||||
|
"tsserver": "bin/tsserver"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.17"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"url/types": {
|
||||||
|
"name": "@dop251/types-goja_nodejs-url",
|
||||||
|
"version": "0.0.1-rc2",
|
||||||
|
"dependencies": {
|
||||||
|
"@dop251/types-goja_nodejs-global": "0.0.1-rc2"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"typescript": "next"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
8
goja_nodejs/package.json
Normal file
8
goja_nodejs/package.json
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"private": true,
|
||||||
|
"workspaces": [
|
||||||
|
"global-types",
|
||||||
|
"url/types",
|
||||||
|
"buffer/types"
|
||||||
|
]
|
||||||
|
}
|
||||||
68
goja_nodejs/process/module_test.go
Normal file
68
goja_nodejs/process/module_test.go
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
package process
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"apigo.cc/gojs/goja"
|
||||||
|
"apigo.cc/gojs/goja_nodejs/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestProcessEnvStructure(t *testing.T) {
|
||||||
|
vm := goja.New()
|
||||||
|
|
||||||
|
new(require.Registry).Enable(vm)
|
||||||
|
Enable(vm)
|
||||||
|
|
||||||
|
if c := vm.Get("process"); c == nil {
|
||||||
|
t.Fatal("process not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
if c, err := vm.RunString("process.env"); c == nil || err != nil {
|
||||||
|
t.Fatal("error accessing process.env")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestProcessEnvValuesArtificial(t *testing.T) {
|
||||||
|
os.Setenv("GOJA_IS_AWESOME", "true")
|
||||||
|
defer os.Unsetenv("GOJA_IS_AWESOME")
|
||||||
|
|
||||||
|
vm := goja.New()
|
||||||
|
|
||||||
|
new(require.Registry).Enable(vm)
|
||||||
|
Enable(vm)
|
||||||
|
|
||||||
|
jsRes, err := vm.RunString("process.env['GOJA_IS_AWESOME']")
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Error executing: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if jsRes.String() != "true" {
|
||||||
|
t.Fatalf("Error executing: got %s but expected %s", jsRes, "true")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestProcessEnvValuesBrackets(t *testing.T) {
|
||||||
|
vm := goja.New()
|
||||||
|
|
||||||
|
new(require.Registry).Enable(vm)
|
||||||
|
Enable(vm)
|
||||||
|
|
||||||
|
for _, e := range os.Environ() {
|
||||||
|
envKeyValue := strings.SplitN(e, "=", 2)
|
||||||
|
jsExpr := fmt.Sprintf("process.env['%s']", envKeyValue[0])
|
||||||
|
|
||||||
|
jsRes, err := vm.RunString(jsExpr)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Error executing %s: %s", jsExpr, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if jsRes.String() != envKeyValue[1] {
|
||||||
|
t.Fatalf("Error executing %s: got %s but expected %s", jsExpr, jsRes, envKeyValue[1])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -23,6 +23,13 @@ type ModuleLoader func(*js.Runtime, *js.Object)
|
|||||||
// This error will be ignored by the resolver and the search will continue. Any other errors will be propagated.
|
// This error will be ignored by the resolver and the search will continue. Any other errors will be propagated.
|
||||||
type SourceLoader func(path string) ([]byte, error)
|
type SourceLoader func(path string) ([]byte, error)
|
||||||
|
|
||||||
|
// PathResolver is a function that should return a canonical path of the path parameter relative to the base. The base
|
||||||
|
// is expected to be already canonical as it would be a result of a previous call to the PathResolver for all cases
|
||||||
|
// except for the initial evaluation, but it's a responsibility of the caller to ensure that the name of the script
|
||||||
|
// is a canonical path. To match Node JS behaviour, it should resolve symlinks.
|
||||||
|
// The path parameter is the argument of the require() call. The returned value will be supplied to the SourceLoader.
|
||||||
|
type PathResolver func(base, path string) string
|
||||||
|
|
||||||
var (
|
var (
|
||||||
InvalidModuleError = errors.New("Invalid module")
|
InvalidModuleError = errors.New("Invalid module")
|
||||||
IllegalModuleNameError = errors.New("Illegal module name")
|
IllegalModuleNameError = errors.New("Illegal module name")
|
||||||
@ -39,6 +46,7 @@ type Registry struct {
|
|||||||
compiled map[string]*js.Program
|
compiled map[string]*js.Program
|
||||||
|
|
||||||
srcLoader SourceLoader
|
srcLoader SourceLoader
|
||||||
|
pathResolver PathResolver
|
||||||
globalFolders []string
|
globalFolders []string
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -75,6 +83,14 @@ func WithLoader(srcLoader SourceLoader) Option {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WithPathResolver sets a function which will be used to resolve paths (see PathResolver). If not specified, the
|
||||||
|
// DefaultPathResolver is used.
|
||||||
|
func WithPathResolver(pathResolver PathResolver) Option {
|
||||||
|
return func(r *Registry) {
|
||||||
|
r.pathResolver = pathResolver
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// WithGlobalFolders appends the given paths to the registry's list of
|
// WithGlobalFolders appends the given paths to the registry's list of
|
||||||
// global folders to search if the requested module is not found
|
// global folders to search if the requested module is not found
|
||||||
// elsewhere. By default, a registry's global folders list is empty.
|
// elsewhere. By default, a registry's global folders list is empty.
|
||||||
@ -114,8 +130,7 @@ func (r *Registry) RegisterNativeModule(name string, loader ModuleLoader) {
|
|||||||
|
|
||||||
// DefaultSourceLoader is used if none was set (see WithLoader()). It simply loads files from the host's filesystem.
|
// DefaultSourceLoader is used if none was set (see WithLoader()). It simply loads files from the host's filesystem.
|
||||||
func DefaultSourceLoader(filename string) ([]byte, error) {
|
func DefaultSourceLoader(filename string) ([]byte, error) {
|
||||||
fp := filepath.FromSlash(filename)
|
f, err := os.Open(filename)
|
||||||
f, err := os.Open(fp)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, fs.ErrNotExist) {
|
if errors.Is(err, fs.ErrNotExist) {
|
||||||
err = ModuleFileDoesNotExistError
|
err = ModuleFileDoesNotExistError
|
||||||
@ -140,6 +155,21 @@ func DefaultSourceLoader(filename string) ([]byte, error) {
|
|||||||
return io.ReadAll(f)
|
return io.ReadAll(f)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DefaultPathResolver is used if none was set (see WithPathResolver). It converts the path using filepath.FromSlash(),
|
||||||
|
// then joins it with base and resolves symlinks on the resulting path.
|
||||||
|
// Note, it does not make the path absolute, so to match nodejs behaviour, the initial script name should be set
|
||||||
|
// to an absolute path.
|
||||||
|
// The implementation is somewhat suboptimal because it runs filepath.EvalSymlinks() on the joint path, not using the
|
||||||
|
// fact that the base path is already resolved. This is because there is no way to resolve symlinks only in a portion
|
||||||
|
// of a path without re-implementing a significant part of filepath.FromSlash().
|
||||||
|
func DefaultPathResolver(base, path string) string {
|
||||||
|
p := filepath.Join(base, filepath.FromSlash(path))
|
||||||
|
if resolved, err := filepath.EvalSymlinks(p); err == nil {
|
||||||
|
p = resolved
|
||||||
|
}
|
||||||
|
return p
|
||||||
|
}
|
||||||
|
|
||||||
func (r *Registry) getSource(p string) ([]byte, error) {
|
func (r *Registry) getSource(p string) ([]byte, error) {
|
||||||
srcLoader := r.srcLoader
|
srcLoader := r.srcLoader
|
||||||
if srcLoader == nil {
|
if srcLoader == nil {
|
||||||
@ -160,11 +190,11 @@ func (r *Registry) getCompiledSource(p string) (*js.Program, error) {
|
|||||||
}
|
}
|
||||||
s := string(buf)
|
s := string(buf)
|
||||||
|
|
||||||
if path.Ext(p) == ".json" {
|
if filepath.Ext(p) == ".json" {
|
||||||
s = "module.exports = JSON.parse('" + template.JSEscapeString(s) + "')"
|
s = "module.exports = JSON.parse('" + template.JSEscapeString(s) + "')"
|
||||||
}
|
}
|
||||||
|
|
||||||
source := "(function(exports, require, module) {" + s + "\n})"
|
source := "(function(exports,require,module,__filename,__dirname){" + s + "\n})"
|
||||||
parsed, err := js.Parse(p, source, parser.WithSourceMapLoader(r.srcLoader))
|
parsed, err := js.Parse(p, source, parser.WithSourceMapLoader(r.srcLoader))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|||||||
587
goja_nodejs/require/module_test.go
Normal file
587
goja_nodejs/require/module_test.go
Normal file
@ -0,0 +1,587 @@
|
|||||||
|
package require
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
|
"syscall"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
js "apigo.cc/gojs/goja"
|
||||||
|
)
|
||||||
|
|
||||||
|
func mapFileSystemSourceLoader(files map[string]string) SourceLoader {
|
||||||
|
return func(p string) ([]byte, error) {
|
||||||
|
s, ok := files[filepath.ToSlash(p)]
|
||||||
|
if !ok {
|
||||||
|
return nil, ModuleFileDoesNotExistError
|
||||||
|
}
|
||||||
|
return []byte(s), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRequireNativeModule(t *testing.T) {
|
||||||
|
const SCRIPT = `
|
||||||
|
var m = require("test/m");
|
||||||
|
m.test();
|
||||||
|
`
|
||||||
|
|
||||||
|
vm := js.New()
|
||||||
|
|
||||||
|
registry := new(Registry)
|
||||||
|
registry.Enable(vm)
|
||||||
|
|
||||||
|
RegisterNativeModule("test/m", func(runtime *js.Runtime, module *js.Object) {
|
||||||
|
o := module.Get("exports").(*js.Object)
|
||||||
|
o.Set("test", func(call js.FunctionCall) js.Value {
|
||||||
|
return runtime.ToValue("passed")
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
v, err := vm.RunString(SCRIPT)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !v.StrictEquals(vm.ToValue("passed")) {
|
||||||
|
t.Fatalf("Unexpected result: %v", v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRegisterCoreModule(t *testing.T) {
|
||||||
|
vm := js.New()
|
||||||
|
|
||||||
|
registry := new(Registry)
|
||||||
|
registry.Enable(vm)
|
||||||
|
|
||||||
|
RegisterCoreModule("coremod", func(runtime *js.Runtime, module *js.Object) {
|
||||||
|
o := module.Get("exports").(*js.Object)
|
||||||
|
o.Set("test", func(call js.FunctionCall) js.Value {
|
||||||
|
return runtime.ToValue("passed")
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
RegisterCoreModule("coremod1", func(runtime *js.Runtime, module *js.Object) {
|
||||||
|
o := module.Get("exports").(*js.Object)
|
||||||
|
o.Set("test", func(call js.FunctionCall) js.Value {
|
||||||
|
return runtime.ToValue("passed1")
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
RegisterCoreModule("node:test1", func(runtime *js.Runtime, module *js.Object) {
|
||||||
|
o := module.Get("exports").(*js.Object)
|
||||||
|
o.Set("test", func(call js.FunctionCall) js.Value {
|
||||||
|
return runtime.ToValue("test1 passed")
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
registry.RegisterNativeModule("bob", func(runtime *js.Runtime, module *js.Object) {
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
_, err := vm.RunString(`
|
||||||
|
const m1 = require("coremod");
|
||||||
|
const m2 = require("node:coremod");
|
||||||
|
if (m1 !== m2) {
|
||||||
|
throw new Error("Modules are not equal");
|
||||||
|
}
|
||||||
|
if (m1.test() !== "passed") {
|
||||||
|
throw new Error("m1.test() has failed");
|
||||||
|
}
|
||||||
|
|
||||||
|
const m3 = require("node:coremod1");
|
||||||
|
const m4 = require("coremod1");
|
||||||
|
if (m3 !== m4) {
|
||||||
|
throw new Error("Modules are not equal (1)");
|
||||||
|
}
|
||||||
|
if (m3.test() !== "passed1") {
|
||||||
|
throw new Error("m3.test() has failed");
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
require("node:bob");
|
||||||
|
} catch (e) {
|
||||||
|
if (!e.message.includes("No such built-in module")) {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
require("bob");
|
||||||
|
|
||||||
|
try {
|
||||||
|
require("test1");
|
||||||
|
throw new Error("Expected exception");
|
||||||
|
} catch (e) {
|
||||||
|
if (!e.message.includes("Invalid module")) {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (require("node:test1").test() !== "test1 passed") {
|
||||||
|
throw new Error("test1.test() has failed");
|
||||||
|
}
|
||||||
|
`)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRequireRegistryNativeModule(t *testing.T) {
|
||||||
|
const SCRIPT = `
|
||||||
|
var log = require("test/log");
|
||||||
|
log.print('passed');
|
||||||
|
`
|
||||||
|
|
||||||
|
logWithOutput := func(w io.Writer, prefix string) ModuleLoader {
|
||||||
|
return func(vm *js.Runtime, module *js.Object) {
|
||||||
|
o := module.Get("exports").(*js.Object)
|
||||||
|
o.Set("print", func(call js.FunctionCall) js.Value {
|
||||||
|
fmt.Fprint(w, prefix, call.Argument(0).String())
|
||||||
|
return js.Undefined()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
vm1 := js.New()
|
||||||
|
buf1 := &bytes.Buffer{}
|
||||||
|
|
||||||
|
registry1 := new(Registry)
|
||||||
|
registry1.Enable(vm1)
|
||||||
|
|
||||||
|
registry1.RegisterNativeModule("test/log", logWithOutput(buf1, "vm1 "))
|
||||||
|
|
||||||
|
vm2 := js.New()
|
||||||
|
buf2 := &bytes.Buffer{}
|
||||||
|
|
||||||
|
registry2 := new(Registry)
|
||||||
|
registry2.Enable(vm2)
|
||||||
|
|
||||||
|
registry2.RegisterNativeModule("test/log", logWithOutput(buf2, "vm2 "))
|
||||||
|
|
||||||
|
_, err := vm1.RunString(SCRIPT)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
s := buf1.String()
|
||||||
|
if s != "vm1 passed" {
|
||||||
|
t.Fatalf("vm1: Unexpected result: %q", s)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = vm2.RunString(SCRIPT)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
s = buf2.String()
|
||||||
|
if s != "vm2 passed" {
|
||||||
|
t.Fatalf("vm2: Unexpected result: %q", s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRequire(t *testing.T) {
|
||||||
|
absPath, err := filepath.Abs("./testdata/m.js")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
isWindows := runtime.GOOS == "windows"
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
path string
|
||||||
|
ok bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
"./testdata/m.js",
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"../require/testdata/m.js",
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
absPath,
|
||||||
|
true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
`.\testdata\m.js`,
|
||||||
|
isWindows,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
`..\require\testdata\m.js`,
|
||||||
|
isWindows,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const SCRIPT = `
|
||||||
|
var m = require(testPath);
|
||||||
|
m.test();
|
||||||
|
`
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
t.Run(test.path, func(t *testing.T) {
|
||||||
|
vm := js.New()
|
||||||
|
vm.Set("testPath", test.path)
|
||||||
|
|
||||||
|
registry := new(Registry)
|
||||||
|
registry.Enable(vm)
|
||||||
|
|
||||||
|
v, err := vm.RunString(SCRIPT)
|
||||||
|
|
||||||
|
ok := err == nil
|
||||||
|
|
||||||
|
if ok != test.ok {
|
||||||
|
t.Fatalf("Expected ok to be %v, got %v (%v)", test.ok, ok, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !v.StrictEquals(vm.ToValue("passed")) {
|
||||||
|
t.Fatalf("Unexpected result: %v", v)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSourceLoader(t *testing.T) {
|
||||||
|
const SCRIPT = `
|
||||||
|
var m = require("m.js");
|
||||||
|
m.test();
|
||||||
|
`
|
||||||
|
|
||||||
|
const MODULE = `
|
||||||
|
function test() {
|
||||||
|
return "passed1";
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.test = test;
|
||||||
|
`
|
||||||
|
|
||||||
|
vm := js.New()
|
||||||
|
|
||||||
|
registry := NewRegistry(WithGlobalFolders("."), WithLoader(func(name string) ([]byte, error) {
|
||||||
|
if name == "m.js" {
|
||||||
|
return []byte(MODULE), nil
|
||||||
|
}
|
||||||
|
return nil, errors.New("Module does not exist")
|
||||||
|
}))
|
||||||
|
registry.Enable(vm)
|
||||||
|
|
||||||
|
v, err := vm.RunString(SCRIPT)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !v.StrictEquals(vm.ToValue("passed1")) {
|
||||||
|
t.Fatalf("Unexpected result: %v", v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStrictModule(t *testing.T) {
|
||||||
|
const SCRIPT = `
|
||||||
|
var m = require("m.js");
|
||||||
|
m.test();
|
||||||
|
`
|
||||||
|
|
||||||
|
const MODULE = `
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
function test() {
|
||||||
|
var a = "passed1";
|
||||||
|
eval("var a = 'not passed'");
|
||||||
|
return a;
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.test = test;
|
||||||
|
`
|
||||||
|
|
||||||
|
vm := js.New()
|
||||||
|
|
||||||
|
registry := NewRegistry(WithGlobalFolders("."), WithLoader(func(name string) ([]byte, error) {
|
||||||
|
if name == "m.js" {
|
||||||
|
return []byte(MODULE), nil
|
||||||
|
}
|
||||||
|
return nil, errors.New("Module does not exist")
|
||||||
|
}))
|
||||||
|
registry.Enable(vm)
|
||||||
|
|
||||||
|
v, err := vm.RunString(SCRIPT)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !v.StrictEquals(vm.ToValue("passed1")) {
|
||||||
|
t.Fatalf("Unexpected result: %v", v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestResolve(t *testing.T) {
|
||||||
|
testRequire := func(src, fpath string, globalFolders []string, fs map[string]string) (*js.Runtime, js.Value, error) {
|
||||||
|
vm := js.New()
|
||||||
|
r := NewRegistry(WithGlobalFolders(globalFolders...), WithLoader(mapFileSystemSourceLoader(fs)))
|
||||||
|
r.Enable(vm)
|
||||||
|
t.Logf("Require(%s)", fpath)
|
||||||
|
ret, err := vm.RunScript(path.Join(src, "test.js"), fmt.Sprintf("require('%s')", fpath))
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
return vm, ret, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
globalFolders := []string{
|
||||||
|
"/usr/lib/node_modules",
|
||||||
|
"/home/src/.node_modules",
|
||||||
|
}
|
||||||
|
|
||||||
|
fs := map[string]string{
|
||||||
|
"/home/src/app/app.js": `exports.name = "app"`,
|
||||||
|
"/home/src/app2/app2.json": `{"name": "app2"}`,
|
||||||
|
"/home/src/app3/index.js": `exports.name = "app3"`,
|
||||||
|
"/home/src/app4/index.json": `{"name": "app4"}`,
|
||||||
|
"/home/src/app5/package.json": `{"main": "app5.js"}`,
|
||||||
|
"/home/src/app5/app5.js": `exports.name = "app5"`,
|
||||||
|
"/home/src/app6/package.json": `{"main": "."}`,
|
||||||
|
"/home/src/app6/index.js": `exports.name = "app6"`,
|
||||||
|
"/home/src/app7/package.json": `{"main": "./a/b/c/file.js"}`,
|
||||||
|
"/home/src/app7/a/b/c/file.js": `exports.name = "app7"`,
|
||||||
|
"/usr/lib/node_modules/app8": `exports.name = "app8"`,
|
||||||
|
"/home/src/app9/app9.js": `exports.name = require('./a/file.js').name`,
|
||||||
|
"/home/src/app9/a/file.js": `exports.name = require('./b/file.js').name`,
|
||||||
|
"/home/src/app9/a/b/file.js": `exports.name = require('./c/file.js').name`,
|
||||||
|
"/home/src/app9/a/b/c/file.js": `exports.name = "app9"`,
|
||||||
|
"/home/src/.node_modules/app10": `exports.name = "app10"`,
|
||||||
|
"/home/src/app11/app11.js": `exports.name = require('d/file.js').name`,
|
||||||
|
"/home/src/app11/a/b/c/app11.js": `exports.name = require('d/file.js').name`,
|
||||||
|
"/home/src/app11/node_modules/d/file.js": `exports.name = "app11"`,
|
||||||
|
"/app12.js": `exports.name = require('a/file.js').name`,
|
||||||
|
"/node_modules/a/file.js": `exports.name = "app12"`,
|
||||||
|
"/app13/app13.js": `exports.name = require('b/file.js').name`,
|
||||||
|
"/node_modules/b/file.js": `exports.name = "app13"`,
|
||||||
|
"node_modules/app14/index.js": `exports.name = "app14"`,
|
||||||
|
"../node_modules/app15/index.js": `exports.name = "app15"`,
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, tc := range []struct {
|
||||||
|
src string
|
||||||
|
path string
|
||||||
|
ok bool
|
||||||
|
field string
|
||||||
|
value string
|
||||||
|
}{
|
||||||
|
{"/home/src", "./app/app", true, "name", "app"},
|
||||||
|
{"/home/src", "./app/app.js", true, "name", "app"},
|
||||||
|
{"/home/src", "./app/bad.js", false, "", ""},
|
||||||
|
{"/home/src", "./app2/app2", true, "name", "app2"},
|
||||||
|
{"/home/src", "./app2/app2.json", true, "name", "app2"},
|
||||||
|
{"/home/src", "./app/bad.json", false, "", ""},
|
||||||
|
{"/home/src", "./app3", true, "name", "app3"},
|
||||||
|
{"/home/src", "./appbad", false, "", ""},
|
||||||
|
{"/home/src", "./app4", true, "name", "app4"},
|
||||||
|
{"/home/src", "./appbad", false, "", ""},
|
||||||
|
{"/home/src", "./app5", true, "name", "app5"},
|
||||||
|
{"/home/src", "./app6", true, "name", "app6"},
|
||||||
|
{"/home/src", "./app7", true, "name", "app7"},
|
||||||
|
{"/home/src", "app8", true, "name", "app8"},
|
||||||
|
{"/home/src", "./app9/app9", true, "name", "app9"},
|
||||||
|
{"/home/src", "app10", true, "name", "app10"},
|
||||||
|
{"/home/src", "./app11/app11.js", true, "name", "app11"},
|
||||||
|
{"/home/src", "./app11/a/b/c/app11.js", true, "name", "app11"},
|
||||||
|
{"/", "./app12", true, "name", "app12"},
|
||||||
|
{"/", "./app13/app13", true, "name", "app13"},
|
||||||
|
{".", "app14", true, "name", "app14"},
|
||||||
|
{"..", "nonexistent", false, "", ""},
|
||||||
|
} {
|
||||||
|
vm, mod, err := testRequire(tc.src, tc.path, globalFolders, fs)
|
||||||
|
if err != nil {
|
||||||
|
if tc.ok {
|
||||||
|
t.Errorf("%d: require() failed: %v", i, err)
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
} else {
|
||||||
|
if !tc.ok {
|
||||||
|
t.Errorf("%d: expected to fail, but did not", i)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
f := mod.ToObject(vm).Get(tc.field)
|
||||||
|
if f == nil {
|
||||||
|
t.Errorf("%v: field %q not found", i, tc.field)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
value := f.String()
|
||||||
|
if value != tc.value {
|
||||||
|
t.Errorf("%v: got %q expected %q", i, value, tc.value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRequireCycle(t *testing.T) {
|
||||||
|
vm := js.New()
|
||||||
|
r := NewRegistry(WithLoader(mapFileSystemSourceLoader(map[string]string{
|
||||||
|
"a.js": `var b = require('./b.js'); exports.done = true;`,
|
||||||
|
"b.js": `var a = require('./a.js'); exports.done = true;`,
|
||||||
|
})))
|
||||||
|
r.Enable(vm)
|
||||||
|
res, err := vm.RunString(`
|
||||||
|
var a = require('./a.js');
|
||||||
|
var b = require('./b.js');
|
||||||
|
a.done && b.done;
|
||||||
|
`)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if v := res.Export(); v != true {
|
||||||
|
t.Fatalf("Unexpected result: %v", v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestErrorPropagation(t *testing.T) {
|
||||||
|
vm := js.New()
|
||||||
|
r := NewRegistry(WithLoader(mapFileSystemSourceLoader(map[string]string{
|
||||||
|
"m.js": `throw 'test passed';`,
|
||||||
|
})))
|
||||||
|
rr := r.Enable(vm)
|
||||||
|
_, err := rr.Require("./m")
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("Expected an error")
|
||||||
|
}
|
||||||
|
if ex, ok := err.(*js.Exception); ok {
|
||||||
|
if !ex.Value().StrictEquals(vm.ToValue("test passed")) {
|
||||||
|
t.Fatalf("Unexpected Exception: %v", ex)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSourceMapLoader(t *testing.T) {
|
||||||
|
vm := js.New()
|
||||||
|
r := NewRegistry(WithLoader(func(p string) ([]byte, error) {
|
||||||
|
switch filepath.ToSlash(p) {
|
||||||
|
case "dir/m.js":
|
||||||
|
return []byte(`throw 'test passed';
|
||||||
|
//# sourceMappingURL=m.js.map`), nil
|
||||||
|
case "dir/m.js.map":
|
||||||
|
return []byte(`{"version":3,"file":"m.js","sourceRoot":"","sources":["m.ts"],"names":[],"mappings":";AAAA"}
|
||||||
|
`), nil
|
||||||
|
}
|
||||||
|
return nil, ModuleFileDoesNotExistError
|
||||||
|
}))
|
||||||
|
|
||||||
|
rr := r.Enable(vm)
|
||||||
|
_, err := rr.Require("./dir/m")
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("Expected an error")
|
||||||
|
}
|
||||||
|
if ex, ok := err.(*js.Exception); ok {
|
||||||
|
if !ex.Value().StrictEquals(vm.ToValue("test passed")) {
|
||||||
|
t.Fatalf("Unexpected Exception: %v", ex)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testsetup() (string, func(), error) {
|
||||||
|
name, err := os.MkdirTemp("", "goja-nodejs-require-test")
|
||||||
|
if err != nil {
|
||||||
|
return "", nil, err
|
||||||
|
}
|
||||||
|
return name, func() {
|
||||||
|
os.RemoveAll(name)
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDefaultModuleLoader(t *testing.T) {
|
||||||
|
workdir, teardown, err := testsetup()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer teardown()
|
||||||
|
|
||||||
|
err = os.Chdir(workdir)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
err = os.Mkdir("module", 0755)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
err = os.WriteFile("module/index.js", []byte(`throw 'test passed';`), 0644)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
vm := js.New()
|
||||||
|
r := NewRegistry()
|
||||||
|
rr := r.Enable(vm)
|
||||||
|
_, err = rr.Require("./module")
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("Expected an error")
|
||||||
|
}
|
||||||
|
if ex, ok := err.(*js.Exception); ok {
|
||||||
|
if !ex.Value().StrictEquals(vm.ToValue("test passed")) {
|
||||||
|
t.Fatalf("Unexpected Exception: %v", ex)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDefaultPathResolver(t *testing.T) {
|
||||||
|
workdir, teardown, err := testsetup()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer teardown()
|
||||||
|
|
||||||
|
err = os.Chdir(workdir)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
err = os.Mkdir("node_modules", 0755)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
err = os.Mkdir("node_modules/a", 0755)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
err = os.Mkdir("node_modules/a/node_modules", 0755)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
err = os.Mkdir("node_modules/b", 0755)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
err = os.Symlink("../../b", "node_modules/a/node_modules/b")
|
||||||
|
if err != nil {
|
||||||
|
if runtime.GOOS == "windows" && errors.Is(err, syscall.Errno(1314)) { // ERROR_PRIVILEGE_NOT_HELD
|
||||||
|
t.Skip("Creating symlinks on Windows requires admin privileges")
|
||||||
|
}
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = os.WriteFile("node_modules/b/index.js", []byte(``), 0644)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
err = os.WriteFile("node_modules/a/index.js", []byte(`require('b')`), 0644)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
vm := js.New()
|
||||||
|
r := NewRegistry()
|
||||||
|
rr := r.Enable(vm)
|
||||||
|
_, err = rr.Require("a")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -4,7 +4,6 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
@ -14,24 +13,24 @@ import (
|
|||||||
|
|
||||||
const NodePrefix = "node:"
|
const NodePrefix = "node:"
|
||||||
|
|
||||||
|
func (r *RequireModule) resolvePath(base, name string) string {
|
||||||
|
if r.r.pathResolver != nil {
|
||||||
|
return r.r.pathResolver(base, name)
|
||||||
|
}
|
||||||
|
return DefaultPathResolver(base, name)
|
||||||
|
}
|
||||||
|
|
||||||
// NodeJS module search algorithm described by
|
// NodeJS module search algorithm described by
|
||||||
// https://nodejs.org/api/modules.html#modules_all_together
|
// https://nodejs.org/api/modules.html#modules_all_together
|
||||||
func (r *RequireModule) resolve(modpath string) (module *js.Object, err error) {
|
func (r *RequireModule) resolve(modpath string) (module *js.Object, err error) {
|
||||||
origPath, modpath := modpath, filepathClean(modpath)
|
|
||||||
if modpath == "" {
|
|
||||||
return nil, IllegalModuleNameError
|
|
||||||
}
|
|
||||||
|
|
||||||
var start string
|
var start string
|
||||||
err = nil
|
err = nil
|
||||||
if path.IsAbs(origPath) {
|
if !filepath.IsAbs(modpath) {
|
||||||
start = "/"
|
|
||||||
} else {
|
|
||||||
start = r.getCurrentModulePath()
|
start = r.getCurrentModulePath()
|
||||||
}
|
}
|
||||||
|
|
||||||
p := path.Join(start, modpath)
|
p := r.resolvePath(start, modpath)
|
||||||
if isFileOrDirectoryPath(origPath) {
|
if isFileOrDirectoryPath(modpath) {
|
||||||
if module = r.modules[p]; module != nil {
|
if module = r.modules[p]; module != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -40,7 +39,7 @@ func (r *RequireModule) resolve(modpath string) (module *js.Object, err error) {
|
|||||||
r.modules[p] = module
|
r.modules[p] = module
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
module, err = r.loadNative(origPath)
|
module, err = r.loadNative(modpath)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return
|
return
|
||||||
} else {
|
} else {
|
||||||
@ -117,6 +116,7 @@ func (r *RequireModule) loadAsFileOrDirectory(path string) (module *js.Object, e
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (r *RequireModule) loadAsFile(path string) (module *js.Object, err error) {
|
func (r *RequireModule) loadAsFile(path string) (module *js.Object, err error) {
|
||||||
|
// added by Star
|
||||||
if fi, err := os.Stat(path); err == nil && fi.IsDir() {
|
if fi, err := os.Stat(path); err == nil && fi.IsDir() {
|
||||||
return r.loadIndex(path)
|
return r.loadIndex(path)
|
||||||
}
|
}
|
||||||
@ -135,17 +135,17 @@ func (r *RequireModule) loadAsFile(path string) (module *js.Object, err error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (r *RequireModule) loadIndex(modpath string) (module *js.Object, err error) {
|
func (r *RequireModule) loadIndex(modpath string) (module *js.Object, err error) {
|
||||||
p := path.Join(modpath, "index.js")
|
p := r.resolvePath(modpath, "index.js")
|
||||||
if module, err = r.loadModule(p); module != nil || err != nil {
|
if module, err = r.loadModule(p); module != nil || err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
p = path.Join(modpath, "index.json")
|
p = r.resolvePath(modpath, "index.json")
|
||||||
return r.loadModule(p)
|
return r.loadModule(p)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *RequireModule) loadAsDirectory(modpath string) (module *js.Object, err error) {
|
func (r *RequireModule) loadAsDirectory(modpath string) (module *js.Object, err error) {
|
||||||
p := path.Join(modpath, "package.json")
|
p := r.resolvePath(modpath, "package.json")
|
||||||
buf, err := r.r.getSource(p)
|
buf, err := r.r.getSource(p)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return r.loadIndex(modpath)
|
return r.loadIndex(modpath)
|
||||||
@ -158,7 +158,7 @@ func (r *RequireModule) loadAsDirectory(modpath string) (module *js.Object, err
|
|||||||
return r.loadIndex(modpath)
|
return r.loadIndex(modpath)
|
||||||
}
|
}
|
||||||
|
|
||||||
m := path.Join(modpath, pkg.Main)
|
m := r.resolvePath(modpath, pkg.Main)
|
||||||
if module, err = r.loadAsFile(m); module != nil || err != nil {
|
if module, err = r.loadAsFile(m); module != nil || err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -167,7 +167,7 @@ func (r *RequireModule) loadAsDirectory(modpath string) (module *js.Object, err
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (r *RequireModule) loadNodeModule(modpath, start string) (*js.Object, error) {
|
func (r *RequireModule) loadNodeModule(modpath, start string) (*js.Object, error) {
|
||||||
return r.loadAsFileOrDirectory(path.Join(start, modpath))
|
return r.loadAsFileOrDirectory(r.resolvePath(start, modpath))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *RequireModule) loadNodeModules(modpath, start string) (module *js.Object, err error) {
|
func (r *RequireModule) loadNodeModules(modpath, start string) (module *js.Object, err error) {
|
||||||
@ -178,8 +178,8 @@ func (r *RequireModule) loadNodeModules(modpath, start string) (module *js.Objec
|
|||||||
}
|
}
|
||||||
for {
|
for {
|
||||||
var p string
|
var p string
|
||||||
if path.Base(start) != "node_modules" {
|
if filepath.Base(start) != "node_modules" {
|
||||||
p = path.Join(start, "node_modules")
|
p = filepath.Join(start, "node_modules")
|
||||||
} else {
|
} else {
|
||||||
p = start
|
p = start
|
||||||
}
|
}
|
||||||
@ -189,7 +189,7 @@ func (r *RequireModule) loadNodeModules(modpath, start string) (module *js.Objec
|
|||||||
if start == ".." { // Dir('..') is '.'
|
if start == ".." { // Dir('..') is '.'
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
parent := path.Dir(start)
|
parent := filepath.Dir(start)
|
||||||
if parent == start {
|
if parent == start {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@ -205,7 +205,7 @@ func (r *RequireModule) getCurrentModulePath() string {
|
|||||||
if len(frames) < 2 {
|
if len(frames) < 2 {
|
||||||
return "."
|
return "."
|
||||||
}
|
}
|
||||||
return path.Dir(frames[1].SrcName())
|
return filepath.Dir(frames[1].SrcName())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *RequireModule) createModuleObject() *js.Object {
|
func (r *RequireModule) createModuleObject() *js.Object {
|
||||||
@ -253,7 +253,7 @@ func (r *RequireModule) loadModuleFile(path string, jsModule *js.Object) error {
|
|||||||
// "jsExports" as the "exports" variable, "jsRequire"
|
// "jsExports" as the "exports" variable, "jsRequire"
|
||||||
// as the "require" variable and "jsModule" as the
|
// as the "require" variable and "jsModule" as the
|
||||||
// "module" variable (Nodejs capable).
|
// "module" variable (Nodejs capable).
|
||||||
_, err = call(jsExports, jsExports, jsRequire, jsModule)
|
_, err = call(jsExports, jsExports, jsRequire, jsModule, r.runtime.ToValue(path), r.runtime.ToValue(filepath.Dir(path)))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
7
goja_nodejs/require/testdata/m.js
vendored
Normal file
7
goja_nodejs/require/testdata/m.js
vendored
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
function test() {
|
||||||
|
return "passed";
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
test: test
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user