Commit 7b68975a authored by Guillaume Ballet's avatar Guillaume Ballet Committed by Felix Lange

console, internal/jsre: use github.com/dop251/goja (#20470)

This replaces the JavaScript interpreter used by the console with goja,
which is actively maintained and a lot faster than otto. Clef still uses otto
and eth/tracers still uses duktape, so we are currently dependent on three
different JS interpreters. We're looking to replace the remaining uses of otto
soon though.
parent 60deeb10
...@@ -51,7 +51,9 @@ func TestConsoleWelcome(t *testing.T) { ...@@ -51,7 +51,9 @@ func TestConsoleWelcome(t *testing.T) {
geth.SetTemplateFunc("goarch", func() string { return runtime.GOARCH }) geth.SetTemplateFunc("goarch", func() string { return runtime.GOARCH })
geth.SetTemplateFunc("gover", runtime.Version) geth.SetTemplateFunc("gover", runtime.Version)
geth.SetTemplateFunc("gethver", func() string { return params.VersionWithCommit("", "") }) geth.SetTemplateFunc("gethver", func() string { return params.VersionWithCommit("", "") })
geth.SetTemplateFunc("niltime", func() string { return time.Unix(0, 0).Format(time.RFC1123) }) geth.SetTemplateFunc("niltime", func() string {
return time.Unix(0, 0).Format("Mon Jan 02 2006 15:04:05 GMT-0700 (MST)")
})
geth.SetTemplateFunc("apis", func() string { return ipcAPIs }) geth.SetTemplateFunc("apis", func() string { return ipcAPIs })
// Verify the actual welcome message to the required template // Verify the actual welcome message to the required template
...@@ -142,7 +144,9 @@ func testAttachWelcome(t *testing.T, geth *testgeth, endpoint, apis string) { ...@@ -142,7 +144,9 @@ func testAttachWelcome(t *testing.T, geth *testgeth, endpoint, apis string) {
attach.SetTemplateFunc("gover", runtime.Version) attach.SetTemplateFunc("gover", runtime.Version)
attach.SetTemplateFunc("gethver", func() string { return params.VersionWithCommit("", "") }) attach.SetTemplateFunc("gethver", func() string { return params.VersionWithCommit("", "") })
attach.SetTemplateFunc("etherbase", func() string { return geth.Etherbase }) attach.SetTemplateFunc("etherbase", func() string { return geth.Etherbase })
attach.SetTemplateFunc("niltime", func() string { return time.Unix(0, 0).Format(time.RFC1123) }) attach.SetTemplateFunc("niltime", func() string {
return time.Unix(0, 0).Format("Mon Jan 02 2006 15:04:05 GMT-0700 (MST)")
})
attach.SetTemplateFunc("ipc", func() bool { return strings.HasPrefix(endpoint, "ipc") }) attach.SetTemplateFunc("ipc", func() bool { return strings.HasPrefix(endpoint, "ipc") })
attach.SetTemplateFunc("datadir", func() string { return geth.Datadir }) attach.SetTemplateFunc("datadir", func() string { return geth.Datadir })
attach.SetTemplateFunc("apis", func() string { return apis }) attach.SetTemplateFunc("apis", func() string { return apis })
......
This diff is collapsed.
This diff is collapsed.
...@@ -289,7 +289,7 @@ func TestPrettyError(t *testing.T) { ...@@ -289,7 +289,7 @@ func TestPrettyError(t *testing.T) {
defer tester.Close(t) defer tester.Close(t)
tester.console.Evaluate("throw 'hello'") tester.console.Evaluate("throw 'hello'")
want := jsre.ErrorColor("hello") + "\n" want := jsre.ErrorColor("hello") + "\n\tat <eval>:1:7(1)\n\n"
if output := tester.output.String(); output != want { if output := tester.output.String(); output != want {
t.Fatalf("pretty error mismatch: have %s, want %s", output, want) t.Fatalf("pretty error mismatch: have %s, want %s", output, want)
} }
......
...@@ -16,13 +16,16 @@ require ( ...@@ -16,13 +16,16 @@ require (
github.com/cloudflare/cloudflare-go v0.10.2-0.20190916151808-a80f83b9add9 github.com/cloudflare/cloudflare-go v0.10.2-0.20190916151808-a80f83b9add9
github.com/davecgh/go-spew v1.1.1 github.com/davecgh/go-spew v1.1.1
github.com/deckarep/golang-set v0.0.0-20180603214616-504e848d77ea github.com/deckarep/golang-set v0.0.0-20180603214616-504e848d77ea
github.com/dlclark/regexp2 v1.2.0 // indirect
github.com/docker/docker v1.4.2-0.20180625184442-8e610b2b55bf github.com/docker/docker v1.4.2-0.20180625184442-8e610b2b55bf
github.com/dop251/goja v0.0.0-20200106141417-aaec0e7bde29
github.com/edsrzf/mmap-go v0.0.0-20160512033002-935e0e8a636c github.com/edsrzf/mmap-go v0.0.0-20160512033002-935e0e8a636c
github.com/elastic/gosigar v0.8.1-0.20180330100440-37f05ff46ffa github.com/elastic/gosigar v0.8.1-0.20180330100440-37f05ff46ffa
github.com/fatih/color v1.3.0 github.com/fatih/color v1.3.0
github.com/fjl/memsize v0.0.0-20180418122429-ca190fb6ffbc github.com/fjl/memsize v0.0.0-20180418122429-ca190fb6ffbc
github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff
github.com/go-ole/go-ole v1.2.1 // indirect github.com/go-ole/go-ole v1.2.1 // indirect
github.com/go-sourcemap/sourcemap v2.1.2+incompatible // indirect
github.com/go-stack/stack v1.8.0 github.com/go-stack/stack v1.8.0
github.com/golang/protobuf v1.3.2-0.20190517061210-b285ee9cfc6c github.com/golang/protobuf v1.3.2-0.20190517061210-b285ee9cfc6c
github.com/golang/snappy v0.0.1 github.com/golang/snappy v0.0.1
......
...@@ -57,8 +57,14 @@ github.com/deckarep/golang-set v0.0.0-20180603214616-504e848d77ea/go.mod h1:93vs ...@@ -57,8 +57,14 @@ github.com/deckarep/golang-set v0.0.0-20180603214616-504e848d77ea/go.mod h1:93vs
github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
github.com/dlclark/regexp2 v1.2.0 h1:8sAhBGEM0dRWogWqWyQeIJnxjWO6oIjl8FKqREDsGfk=
github.com/dlclark/regexp2 v1.2.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
github.com/docker/docker v1.4.2-0.20180625184442-8e610b2b55bf h1:sh8rkQZavChcmakYiSlqu2425CHyFXLZZnvm7PDpU8M= github.com/docker/docker v1.4.2-0.20180625184442-8e610b2b55bf h1:sh8rkQZavChcmakYiSlqu2425CHyFXLZZnvm7PDpU8M=
github.com/docker/docker v1.4.2-0.20180625184442-8e610b2b55bf/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker v1.4.2-0.20180625184442-8e610b2b55bf/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/dop251/goja v0.0.0-20191203121440-007eef3bc40f h1:vtCDQseO/Sbu5IZSoc2uzZ7CkSoai7OtpcwGFK5FlyE=
github.com/dop251/goja v0.0.0-20191203121440-007eef3bc40f/go.mod h1:Mw6PkjjMXWbTj+nnj4s3QPXq1jaT0s5pC0iFD4+BOAA=
github.com/dop251/goja v0.0.0-20200106141417-aaec0e7bde29 h1:Ewd9K+mC725sITA12QQHRqWj78NU4t7EhlFVVgdlzJg=
github.com/dop251/goja v0.0.0-20200106141417-aaec0e7bde29/go.mod h1:Mw6PkjjMXWbTj+nnj4s3QPXq1jaT0s5pC0iFD4+BOAA=
github.com/edsrzf/mmap-go v0.0.0-20160512033002-935e0e8a636c h1:JHHhtb9XWJrGNMcrVP6vyzO4dusgi/HnceHTgxSejUM= github.com/edsrzf/mmap-go v0.0.0-20160512033002-935e0e8a636c h1:JHHhtb9XWJrGNMcrVP6vyzO4dusgi/HnceHTgxSejUM=
github.com/edsrzf/mmap-go v0.0.0-20160512033002-935e0e8a636c/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= github.com/edsrzf/mmap-go v0.0.0-20160512033002-935e0e8a636c/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M=
github.com/elastic/gosigar v0.8.1-0.20180330100440-37f05ff46ffa h1:XKAhUk/dtp+CV0VO6mhG2V7jA9vbcGcnYF/Ay9NjZrY= github.com/elastic/gosigar v0.8.1-0.20180330100440-37f05ff46ffa h1:XKAhUk/dtp+CV0VO6mhG2V7jA9vbcGcnYF/Ay9NjZrY=
...@@ -77,6 +83,8 @@ github.com/go-logfmt/logfmt v0.3.0 h1:8HUsc87TaSWLKwrnumgC8/YconD2fJQsRJAsWaPg2i ...@@ -77,6 +83,8 @@ github.com/go-logfmt/logfmt v0.3.0 h1:8HUsc87TaSWLKwrnumgC8/YconD2fJQsRJAsWaPg2i
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-ole/go-ole v1.2.1 h1:2lOsA72HgjxAuMlKpFiCbHTvu44PIVkZ5hqm3RSdI/E= github.com/go-ole/go-ole v1.2.1 h1:2lOsA72HgjxAuMlKpFiCbHTvu44PIVkZ5hqm3RSdI/E=
github.com/go-ole/go-ole v1.2.1/go.mod h1:7FAglXiTm7HKlQRDeOQ6ZNUHidzCWXuZWq/1dTyBNF8= github.com/go-ole/go-ole v1.2.1/go.mod h1:7FAglXiTm7HKlQRDeOQ6ZNUHidzCWXuZWq/1dTyBNF8=
github.com/go-sourcemap/sourcemap v2.1.2+incompatible h1:0b/xya7BKGhXuqFESKM4oIiRo9WOt2ebz7KxfreD6ug=
github.com/go-sourcemap/sourcemap v2.1.2+incompatible/go.mod h1:F8jJfvm2KbVjc5NqelyYJmf/v5J0dwNLS2mL4sNA1Jg=
github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk= github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
......
...@@ -20,35 +20,43 @@ import ( ...@@ -20,35 +20,43 @@ import (
"sort" "sort"
"strings" "strings"
"github.com/robertkrimen/otto" "github.com/dop251/goja"
) )
// CompleteKeywords returns potential continuations for the given line. Since line is // CompleteKeywords returns potential continuations for the given line. Since line is
// evaluated, callers need to make sure that evaluating line does not have side effects. // evaluated, callers need to make sure that evaluating line does not have side effects.
func (jsre *JSRE) CompleteKeywords(line string) []string { func (jsre *JSRE) CompleteKeywords(line string) []string {
var results []string var results []string
jsre.Do(func(vm *otto.Otto) { jsre.Do(func(vm *goja.Runtime) {
results = getCompletions(vm, line) results = getCompletions(vm, line)
}) })
return results return results
} }
func getCompletions(vm *otto.Otto, line string) (results []string) { func getCompletions(vm *goja.Runtime, line string) (results []string) {
parts := strings.Split(line, ".") parts := strings.Split(line, ".")
objRef := "this" if len(parts) == 0 {
prefix := line return nil
if len(parts) > 1 {
objRef = strings.Join(parts[0:len(parts)-1], ".")
prefix = parts[len(parts)-1]
} }
obj, _ := vm.Object(objRef) // Find the right-most fully named object in the line. e.g. if line = "x.y.z"
if obj == nil { // and "x.y" is an object, obj will reference "x.y".
return nil obj := vm.GlobalObject()
for i := 0; i < len(parts)-1; i++ {
v := obj.Get(parts[i])
if v == nil {
return nil // No object was found
}
obj = v.ToObject(vm)
} }
// Go over the keys of the object and retain the keys matching prefix.
// Example: if line = "x.y.z" and "x.y" exists and has keys "zebu", "zebra"
// and "platypus", then "x.y.zebu" and "x.y.zebra" will be added to results.
prefix := parts[len(parts)-1]
iterOwnAndConstructorKeys(vm, obj, func(k string) { iterOwnAndConstructorKeys(vm, obj, func(k string) {
if strings.HasPrefix(k, prefix) { if strings.HasPrefix(k, prefix) {
if objRef == "this" { if len(parts) == 1 {
results = append(results, k) results = append(results, k)
} else { } else {
results = append(results, strings.Join(parts[:len(parts)-1], ".")+"."+k) results = append(results, strings.Join(parts[:len(parts)-1], ".")+"."+k)
...@@ -59,9 +67,9 @@ func getCompletions(vm *otto.Otto, line string) (results []string) { ...@@ -59,9 +67,9 @@ func getCompletions(vm *otto.Otto, line string) (results []string) {
// Append opening parenthesis (for functions) or dot (for objects) // Append opening parenthesis (for functions) or dot (for objects)
// if the line itself is the only completion. // if the line itself is the only completion.
if len(results) == 1 && results[0] == line { if len(results) == 1 && results[0] == line {
obj, _ := vm.Object(line) obj := obj.Get(parts[len(parts)-1])
if obj != nil { if obj != nil {
if obj.Class() == "Function" { if _, isfunc := goja.AssertFunction(obj); isfunc {
results[0] += "(" results[0] += "("
} else { } else {
results[0] += "." results[0] += "."
......
...@@ -39,6 +39,10 @@ func TestCompleteKeywords(t *testing.T) { ...@@ -39,6 +39,10 @@ func TestCompleteKeywords(t *testing.T) {
input string input string
want []string want []string
}{ }{
{
input: "St",
want: []string{"String"},
},
{ {
input: "x", input: "x",
want: []string{"x."}, want: []string{"x."},
......
This diff is collapsed.
...@@ -20,25 +20,24 @@ import ( ...@@ -20,25 +20,24 @@ import (
"io/ioutil" "io/ioutil"
"os" "os"
"path" "path"
"reflect"
"testing" "testing"
"time" "time"
"github.com/robertkrimen/otto" "github.com/dop251/goja"
) )
type testNativeObjectBinding struct{} type testNativeObjectBinding struct {
vm *goja.Runtime
}
type msg struct { type msg struct {
Msg string Msg string
} }
func (no *testNativeObjectBinding) TestMethod(call otto.FunctionCall) otto.Value { func (no *testNativeObjectBinding) TestMethod(call goja.FunctionCall) goja.Value {
m, err := call.Argument(0).ToString() m := call.Argument(0).ToString().String()
if err != nil { return no.vm.ToValue(&msg{m})
return otto.UndefinedValue()
}
v, _ := call.Otto.ToValue(&msg{m})
return v
} }
func newWithTestJS(t *testing.T, testjs string) (*JSRE, string) { func newWithTestJS(t *testing.T, testjs string) (*JSRE, string) {
...@@ -51,7 +50,8 @@ func newWithTestJS(t *testing.T, testjs string) (*JSRE, string) { ...@@ -51,7 +50,8 @@ func newWithTestJS(t *testing.T, testjs string) (*JSRE, string) {
t.Fatal("cannot create test.js:", err) t.Fatal("cannot create test.js:", err)
} }
} }
return New(dir, os.Stdout), dir jsre := New(dir, os.Stdout)
return jsre, dir
} }
func TestExec(t *testing.T) { func TestExec(t *testing.T) {
...@@ -66,11 +66,11 @@ func TestExec(t *testing.T) { ...@@ -66,11 +66,11 @@ func TestExec(t *testing.T) {
if err != nil { if err != nil {
t.Errorf("expected no error, got %v", err) t.Errorf("expected no error, got %v", err)
} }
if !val.IsString() { if val.ExportType().Kind() != reflect.String {
t.Errorf("expected string value, got %v", val) t.Errorf("expected string value, got %v", val)
} }
exp := "testMsg" exp := "testMsg"
got, _ := val.ToString() got := val.ToString().String()
if exp != got { if exp != got {
t.Errorf("expected '%v', got '%v'", exp, got) t.Errorf("expected '%v', got '%v'", exp, got)
} }
...@@ -90,11 +90,11 @@ func TestNatto(t *testing.T) { ...@@ -90,11 +90,11 @@ func TestNatto(t *testing.T) {
if err != nil { if err != nil {
t.Errorf("expected no error, got %v", err) t.Errorf("expected no error, got %v", err)
} }
if !val.IsString() { if val.ExportType().Kind() != reflect.String {
t.Errorf("expected string value, got %v", val) t.Errorf("expected string value, got %v", val)
} }
exp := "testMsg" exp := "testMsg"
got, _ := val.ToString() got := val.ToString().String()
if exp != got { if exp != got {
t.Errorf("expected '%v', got '%v'", exp, got) t.Errorf("expected '%v', got '%v'", exp, got)
} }
...@@ -105,7 +105,7 @@ func TestBind(t *testing.T) { ...@@ -105,7 +105,7 @@ func TestBind(t *testing.T) {
jsre := New("", os.Stdout) jsre := New("", os.Stdout)
defer jsre.Stop(false) defer jsre.Stop(false)
jsre.Bind("no", &testNativeObjectBinding{}) jsre.Set("no", &testNativeObjectBinding{vm: jsre.vm})
_, err := jsre.Run(`no.TestMethod("testMsg")`) _, err := jsre.Run(`no.TestMethod("testMsg")`)
if err != nil { if err != nil {
...@@ -125,11 +125,11 @@ func TestLoadScript(t *testing.T) { ...@@ -125,11 +125,11 @@ func TestLoadScript(t *testing.T) {
if err != nil { if err != nil {
t.Errorf("expected no error, got %v", err) t.Errorf("expected no error, got %v", err)
} }
if !val.IsString() { if val.ExportType().Kind() != reflect.String {
t.Errorf("expected string value, got %v", val) t.Errorf("expected string value, got %v", val)
} }
exp := "testMsg" exp := "testMsg"
got, _ := val.ToString() got := val.ToString().String()
if exp != got { if exp != got {
t.Errorf("expected '%v', got '%v'", exp, got) t.Errorf("expected '%v', got '%v'", exp, got)
} }
......
...@@ -19,12 +19,13 @@ package jsre ...@@ -19,12 +19,13 @@ package jsre
import ( import (
"fmt" "fmt"
"io" "io"
"reflect"
"sort" "sort"
"strconv" "strconv"
"strings" "strings"
"github.com/dop251/goja"
"github.com/fatih/color" "github.com/fatih/color"
"github.com/robertkrimen/otto"
) )
const ( const (
...@@ -52,29 +53,29 @@ var boringKeys = map[string]bool{ ...@@ -52,29 +53,29 @@ var boringKeys = map[string]bool{
} }
// prettyPrint writes value to standard output. // prettyPrint writes value to standard output.
func prettyPrint(vm *otto.Otto, value otto.Value, w io.Writer) { func prettyPrint(vm *goja.Runtime, value goja.Value, w io.Writer) {
ppctx{vm: vm, w: w}.printValue(value, 0, false) ppctx{vm: vm, w: w}.printValue(value, 0, false)
} }
// prettyError writes err to standard output. // prettyError writes err to standard output.
func prettyError(vm *otto.Otto, err error, w io.Writer) { func prettyError(vm *goja.Runtime, err error, w io.Writer) {
failure := err.Error() failure := err.Error()
if ottoErr, ok := err.(*otto.Error); ok { if gojaErr, ok := err.(*goja.Exception); ok {
failure = ottoErr.String() failure = gojaErr.String()
} }
fmt.Fprint(w, ErrorColor("%s", failure)) fmt.Fprint(w, ErrorColor("%s", failure))
} }
func (re *JSRE) prettyPrintJS(call otto.FunctionCall) otto.Value { func (re *JSRE) prettyPrintJS(call goja.FunctionCall) goja.Value {
for _, v := range call.ArgumentList { for _, v := range call.Arguments {
prettyPrint(call.Otto, v, re.output) prettyPrint(re.vm, v, re.output)
fmt.Fprintln(re.output) fmt.Fprintln(re.output)
} }
return otto.UndefinedValue() return goja.Undefined()
} }
type ppctx struct { type ppctx struct {
vm *otto.Otto vm *goja.Runtime
w io.Writer w io.Writer
} }
...@@ -82,35 +83,47 @@ func (ctx ppctx) indent(level int) string { ...@@ -82,35 +83,47 @@ func (ctx ppctx) indent(level int) string {
return strings.Repeat(indentString, level) return strings.Repeat(indentString, level)
} }
func (ctx ppctx) printValue(v otto.Value, level int, inArray bool) { func (ctx ppctx) printValue(v goja.Value, level int, inArray bool) {
if goja.IsNull(v) || goja.IsUndefined(v) {
fmt.Fprint(ctx.w, SpecialColor(v.String()))
return
}
kind := v.ExportType().Kind()
switch { switch {
case v.IsObject(): case kind == reflect.Bool:
ctx.printObject(v.Object(), level, inArray) fmt.Fprint(ctx.w, SpecialColor("%t", v.ToBoolean()))
case v.IsNull(): case kind == reflect.String:
fmt.Fprint(ctx.w, SpecialColor("null")) fmt.Fprint(ctx.w, StringColor("%q", v.String()))
case v.IsUndefined(): case kind >= reflect.Int && kind <= reflect.Complex128:
fmt.Fprint(ctx.w, SpecialColor("undefined")) fmt.Fprint(ctx.w, NumberColor("%s", v.String()))
case v.IsString():
s, _ := v.ToString()
fmt.Fprint(ctx.w, StringColor("%q", s))
case v.IsBoolean():
b, _ := v.ToBoolean()
fmt.Fprint(ctx.w, SpecialColor("%t", b))
case v.IsNaN():
fmt.Fprint(ctx.w, NumberColor("NaN"))
case v.IsNumber():
s, _ := v.ToString()
fmt.Fprint(ctx.w, NumberColor("%s", s))
default: default:
fmt.Fprint(ctx.w, "<unprintable>") if obj, ok := v.(*goja.Object); ok {
ctx.printObject(obj, level, inArray)
} else {
fmt.Fprintf(ctx.w, "<unprintable %T>", v)
}
} }
} }
func (ctx ppctx) printObject(obj *otto.Object, level int, inArray bool) { // SafeGet attempt to get the value associated to `key`, and
switch obj.Class() { // catches the panic that goja creates if an error occurs in
// key.
func SafeGet(obj *goja.Object, key string) (ret goja.Value) {
defer func() {
if r := recover(); r != nil {
ret = goja.Undefined()
}
}()
ret = obj.Get(key)
return ret
}
func (ctx ppctx) printObject(obj *goja.Object, level int, inArray bool) {
switch obj.ClassName() {
case "Array", "GoArray": case "Array", "GoArray":
lv, _ := obj.Get("length") lv := obj.Get("length")
len, _ := lv.ToInteger() len := lv.ToInteger()
if len == 0 { if len == 0 {
fmt.Fprintf(ctx.w, "[]") fmt.Fprintf(ctx.w, "[]")
return return
...@@ -121,8 +134,8 @@ func (ctx ppctx) printObject(obj *otto.Object, level int, inArray bool) { ...@@ -121,8 +134,8 @@ func (ctx ppctx) printObject(obj *otto.Object, level int, inArray bool) {
} }
fmt.Fprint(ctx.w, "[") fmt.Fprint(ctx.w, "[")
for i := int64(0); i < len; i++ { for i := int64(0); i < len; i++ {
el, err := obj.Get(strconv.FormatInt(i, 10)) el := obj.Get(strconv.FormatInt(i, 10))
if err == nil { if el != nil {
ctx.printValue(el, level+1, true) ctx.printValue(el, level+1, true)
} }
if i < len-1 { if i < len-1 {
...@@ -149,7 +162,7 @@ func (ctx ppctx) printObject(obj *otto.Object, level int, inArray bool) { ...@@ -149,7 +162,7 @@ func (ctx ppctx) printObject(obj *otto.Object, level int, inArray bool) {
} }
fmt.Fprintln(ctx.w, "{") fmt.Fprintln(ctx.w, "{")
for i, k := range keys { for i, k := range keys {
v, _ := obj.Get(k) v := SafeGet(obj, k)
fmt.Fprintf(ctx.w, "%s%s: ", ctx.indent(level+1), k) fmt.Fprintf(ctx.w, "%s%s: ", ctx.indent(level+1), k)
ctx.printValue(v, level+1, false) ctx.printValue(v, level+1, false)
if i < len(keys)-1 { if i < len(keys)-1 {
...@@ -163,29 +176,25 @@ func (ctx ppctx) printObject(obj *otto.Object, level int, inArray bool) { ...@@ -163,29 +176,25 @@ func (ctx ppctx) printObject(obj *otto.Object, level int, inArray bool) {
fmt.Fprintf(ctx.w, "%s}", ctx.indent(level)) fmt.Fprintf(ctx.w, "%s}", ctx.indent(level))
case "Function": case "Function":
// Use toString() to display the argument list if possible. robj := obj.ToString()
if robj, err := obj.Call("toString"); err != nil { desc := strings.Trim(strings.Split(robj.String(), "{")[0], " \t\n")
fmt.Fprint(ctx.w, FunctionColor("function()")) desc = strings.Replace(desc, " (", "(", 1)
} else { fmt.Fprint(ctx.w, FunctionColor("%s", desc))
desc := strings.Trim(strings.Split(robj.String(), "{")[0], " \t\n")
desc = strings.Replace(desc, " (", "(", 1)
fmt.Fprint(ctx.w, FunctionColor("%s", desc))
}
case "RegExp": case "RegExp":
fmt.Fprint(ctx.w, StringColor("%s", toString(obj))) fmt.Fprint(ctx.w, StringColor("%s", toString(obj)))
default: default:
if v, _ := obj.Get("toString"); v.IsFunction() && level <= maxPrettyPrintLevel { if level <= maxPrettyPrintLevel {
s, _ := obj.Call("toString") s := obj.ToString().String()
fmt.Fprintf(ctx.w, "<%s %s>", obj.Class(), s.String()) fmt.Fprintf(ctx.w, "<%s %s>", obj.ClassName(), s)
} else { } else {
fmt.Fprintf(ctx.w, "<%s>", obj.Class()) fmt.Fprintf(ctx.w, "<%s>", obj.ClassName())
} }
} }
} }
func (ctx ppctx) fields(obj *otto.Object) []string { func (ctx ppctx) fields(obj *goja.Object) []string {
var ( var (
vals, methods []string vals, methods []string
seen = make(map[string]bool) seen = make(map[string]bool)
...@@ -195,11 +204,22 @@ func (ctx ppctx) fields(obj *otto.Object) []string { ...@@ -195,11 +204,22 @@ func (ctx ppctx) fields(obj *otto.Object) []string {
return return
} }
seen[k] = true seen[k] = true
if v, _ := obj.Get(k); v.IsFunction() {
methods = append(methods, k) key := SafeGet(obj, k)
} else { if key == nil {
// The value corresponding to that key could not be found
// (typically because it is backed by an RPC call that is
// not supported by this instance. Add it to the list of
// values so that it appears as `undefined` to the user.
vals = append(vals, k) vals = append(vals, k)
} else {
if _, callable := goja.AssertFunction(key); callable {
methods = append(methods, k)
} else {
vals = append(vals, k)
}
} }
} }
iterOwnAndConstructorKeys(ctx.vm, obj, add) iterOwnAndConstructorKeys(ctx.vm, obj, add)
sort.Strings(vals) sort.Strings(vals)
...@@ -207,13 +227,13 @@ func (ctx ppctx) fields(obj *otto.Object) []string { ...@@ -207,13 +227,13 @@ func (ctx ppctx) fields(obj *otto.Object) []string {
return append(vals, methods...) return append(vals, methods...)
} }
func iterOwnAndConstructorKeys(vm *otto.Otto, obj *otto.Object, f func(string)) { func iterOwnAndConstructorKeys(vm *goja.Runtime, obj *goja.Object, f func(string)) {
seen := make(map[string]bool) seen := make(map[string]bool)
iterOwnKeys(vm, obj, func(prop string) { iterOwnKeys(vm, obj, func(prop string) {
seen[prop] = true seen[prop] = true
f(prop) f(prop)
}) })
if cp := constructorPrototype(obj); cp != nil { if cp := constructorPrototype(vm, obj); cp != nil {
iterOwnKeys(vm, cp, func(prop string) { iterOwnKeys(vm, cp, func(prop string) {
if !seen[prop] { if !seen[prop] {
f(prop) f(prop)
...@@ -222,10 +242,17 @@ func iterOwnAndConstructorKeys(vm *otto.Otto, obj *otto.Object, f func(string)) ...@@ -222,10 +242,17 @@ func iterOwnAndConstructorKeys(vm *otto.Otto, obj *otto.Object, f func(string))
} }
} }
func iterOwnKeys(vm *otto.Otto, obj *otto.Object, f func(string)) { func iterOwnKeys(vm *goja.Runtime, obj *goja.Object, f func(string)) {
Object, _ := vm.Object("Object") Object := vm.Get("Object").ToObject(vm)
rv, _ := Object.Call("getOwnPropertyNames", obj.Value()) getOwnPropertyNames, isFunc := goja.AssertFunction(Object.Get("getOwnPropertyNames"))
gv, _ := rv.Export() if !isFunc {
panic(vm.ToValue("Object.getOwnPropertyNames isn't a function"))
}
rv, err := getOwnPropertyNames(goja.Null(), obj)
if err != nil {
panic(vm.ToValue(fmt.Sprintf("Error getting object properties: %v", err)))
}
gv := rv.Export()
switch gv := gv.(type) { switch gv := gv.(type) {
case []interface{}: case []interface{}:
for _, v := range gv { for _, v := range gv {
...@@ -240,32 +267,35 @@ func iterOwnKeys(vm *otto.Otto, obj *otto.Object, f func(string)) { ...@@ -240,32 +267,35 @@ func iterOwnKeys(vm *otto.Otto, obj *otto.Object, f func(string)) {
} }
} }
func (ctx ppctx) isBigNumber(v *otto.Object) bool { func (ctx ppctx) isBigNumber(v *goja.Object) bool {
// Handle numbers with custom constructor. // Handle numbers with custom constructor.
if v, _ := v.Get("constructor"); v.Object() != nil { if obj := v.Get("constructor").ToObject(ctx.vm); obj != nil {
if strings.HasPrefix(toString(v.Object()), "function BigNumber") { if strings.HasPrefix(toString(obj), "function BigNumber") {
return true return true
} }
} }
// Handle default constructor. // Handle default constructor.
BigNumber, _ := ctx.vm.Object("BigNumber.prototype") BigNumber := ctx.vm.Get("BigNumber").ToObject(ctx.vm)
if BigNumber == nil { if BigNumber == nil {
return false return false
} }
bv, _ := BigNumber.Call("isPrototypeOf", v) prototype := BigNumber.Get("prototype").ToObject(ctx.vm)
b, _ := bv.ToBoolean() isPrototypeOf, callable := goja.AssertFunction(prototype.Get("isPrototypeOf"))
return b if !callable {
return false
}
bv, _ := isPrototypeOf(prototype, v)
return bv.ToBoolean()
} }
func toString(obj *otto.Object) string { func toString(obj *goja.Object) string {
s, _ := obj.Call("toString") return obj.ToString().String()
return s.String()
} }
func constructorPrototype(obj *otto.Object) *otto.Object { func constructorPrototype(vm *goja.Runtime, obj *goja.Object) *goja.Object {
if v, _ := obj.Get("constructor"); v.Object() != nil { if v := obj.Get("constructor"); v != nil {
if v, _ = v.Object().Get("prototype"); v.Object() != nil { if v := v.ToObject(vm).Get("prototype"); v != nil {
return v.Object() return v.ToObject(vm)
} }
} }
return nil return nil
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment