123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677 |
- package goja
- import (
- "fmt"
- "go/ast"
- "reflect"
- "strings"
- "github.com/dop251/goja/parser"
- "github.com/dop251/goja/unistring"
- )
- // JsonEncodable allows custom JSON encoding by JSON.stringify()
- // Note that if the returned value itself also implements JsonEncodable, it won't have any effect.
- type JsonEncodable interface {
- JsonEncodable() interface{}
- }
- // FieldNameMapper provides custom mapping between Go and JavaScript property names.
- type FieldNameMapper interface {
- // FieldName returns a JavaScript name for the given struct field in the given type.
- // If this method returns "" the field becomes hidden.
- FieldName(t reflect.Type, f reflect.StructField) string
- // MethodName returns a JavaScript name for the given method in the given type.
- // If this method returns "" the method becomes hidden.
- MethodName(t reflect.Type, m reflect.Method) string
- }
- type tagFieldNameMapper struct {
- tagName string
- uncapMethods bool
- }
- func (tfm tagFieldNameMapper) FieldName(_ reflect.Type, f reflect.StructField) string {
- tag := f.Tag.Get(tfm.tagName)
- if idx := strings.IndexByte(tag, ','); idx != -1 {
- tag = tag[:idx]
- }
- if parser.IsIdentifier(tag) {
- return tag
- }
- return ""
- }
- func uncapitalize(s string) string {
- return strings.ToLower(s[0:1]) + s[1:]
- }
- func (tfm tagFieldNameMapper) MethodName(_ reflect.Type, m reflect.Method) string {
- if tfm.uncapMethods {
- return uncapitalize(m.Name)
- }
- return m.Name
- }
- type uncapFieldNameMapper struct {
- }
- func (u uncapFieldNameMapper) FieldName(_ reflect.Type, f reflect.StructField) string {
- return uncapitalize(f.Name)
- }
- func (u uncapFieldNameMapper) MethodName(_ reflect.Type, m reflect.Method) string {
- return uncapitalize(m.Name)
- }
- type reflectFieldInfo struct {
- Index []int
- Anonymous bool
- }
- type reflectFieldsInfo struct {
- Fields map[string]reflectFieldInfo
- Names []string
- }
- type reflectMethodsInfo struct {
- Methods map[string]int
- Names []string
- }
- type reflectValueWrapper interface {
- esValue() Value
- reflectValue() reflect.Value
- setReflectValue(reflect.Value)
- }
- func isContainer(k reflect.Kind) bool {
- switch k {
- case reflect.Struct, reflect.Slice, reflect.Array:
- return true
- }
- return false
- }
- func copyReflectValueWrapper(w reflectValueWrapper) {
- v := w.reflectValue()
- c := reflect.New(v.Type()).Elem()
- c.Set(v)
- w.setReflectValue(c)
- }
- type objectGoReflect struct {
- baseObject
- origValue, fieldsValue reflect.Value
- fieldsInfo *reflectFieldsInfo
- methodsInfo *reflectMethodsInfo
- methodsValue reflect.Value
- valueCache map[string]reflectValueWrapper
- toString, valueOf func() Value
- toJson func() interface{}
- }
- func (o *objectGoReflect) init() {
- o.baseObject.init()
- switch o.fieldsValue.Kind() {
- case reflect.Bool:
- o.class = classBoolean
- o.prototype = o.val.runtime.getBooleanPrototype()
- o.toString = o._toStringBool
- o.valueOf = o._valueOfBool
- case reflect.String:
- o.class = classString
- o.prototype = o.val.runtime.getStringPrototype()
- o.toString = o._toStringString
- case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
- o.class = classNumber
- o.prototype = o.val.runtime.getNumberPrototype()
- o.valueOf = o._valueOfInt
- case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
- o.class = classNumber
- o.prototype = o.val.runtime.getNumberPrototype()
- o.valueOf = o._valueOfUint
- case reflect.Float32, reflect.Float64:
- o.class = classNumber
- o.prototype = o.val.runtime.getNumberPrototype()
- o.valueOf = o._valueOfFloat
- default:
- o.class = classObject
- o.prototype = o.val.runtime.global.ObjectPrototype
- }
- if o.fieldsValue.Kind() == reflect.Struct {
- o.fieldsInfo = o.val.runtime.fieldsInfo(o.fieldsValue.Type())
- }
- var methodsType reflect.Type
- // Always use pointer type for non-interface values to be able to access both methods defined on
- // the literal type and on the pointer.
- if o.fieldsValue.Kind() != reflect.Interface {
- methodsType = reflect.PtrTo(o.fieldsValue.Type())
- } else {
- methodsType = o.fieldsValue.Type()
- }
- o.methodsInfo = o.val.runtime.methodsInfo(methodsType)
- // Container values and values that have at least one method defined on the pointer type
- // need to be addressable.
- if !o.origValue.CanAddr() && (isContainer(o.origValue.Kind()) || len(o.methodsInfo.Names) > 0) {
- value := reflect.New(o.origValue.Type()).Elem()
- value.Set(o.origValue)
- o.origValue = value
- if value.Kind() != reflect.Ptr {
- o.fieldsValue = value
- }
- }
- o.extensible = true
- switch o.origValue.Interface().(type) {
- case fmt.Stringer:
- o.toString = o._toStringStringer
- case error:
- o.toString = o._toStringError
- }
- if len(o.methodsInfo.Names) > 0 && o.fieldsValue.Kind() != reflect.Interface {
- o.methodsValue = o.fieldsValue.Addr()
- } else {
- o.methodsValue = o.fieldsValue
- }
- if j, ok := o.origValue.Interface().(JsonEncodable); ok {
- o.toJson = j.JsonEncodable
- }
- }
- func (o *objectGoReflect) getStr(name unistring.String, receiver Value) Value {
- if v := o._get(name.String()); v != nil {
- return v
- }
- return o.baseObject.getStr(name, receiver)
- }
- func (o *objectGoReflect) _getField(jsName string) reflect.Value {
- if o.fieldsInfo != nil {
- if info, exists := o.fieldsInfo.Fields[jsName]; exists {
- return o.fieldsValue.FieldByIndex(info.Index)
- }
- }
- return reflect.Value{}
- }
- func (o *objectGoReflect) _getMethod(jsName string) reflect.Value {
- if o.methodsInfo != nil {
- if idx, exists := o.methodsInfo.Methods[jsName]; exists {
- return o.methodsValue.Method(idx)
- }
- }
- return reflect.Value{}
- }
- func (o *objectGoReflect) elemToValue(ev reflect.Value) (Value, reflectValueWrapper) {
- if isContainer(ev.Kind()) {
- if ev.CanAddr() {
- ev = ev.Addr()
- }
- ret := o.val.runtime.toValue(ev.Interface(), ev)
- if obj, ok := ret.(*Object); ok {
- if w, ok := obj.self.(reflectValueWrapper); ok {
- return ret, w
- }
- }
- return ret, nil
- }
- if ev.Kind() == reflect.Interface {
- ev = ev.Elem()
- }
- if ev.Kind() == reflect.Invalid {
- return _null, nil
- }
- return o.val.runtime.toValue(ev.Interface(), ev), nil
- }
- func (o *objectGoReflect) _getFieldValue(name string) Value {
- if v := o.valueCache[name]; v != nil {
- return v.esValue()
- }
- if v := o._getField(name); v.IsValid() {
- res, w := o.elemToValue(v)
- if w != nil {
- if o.valueCache == nil {
- o.valueCache = make(map[string]reflectValueWrapper)
- }
- o.valueCache[name] = w
- }
- return res
- }
- return nil
- }
- func (o *objectGoReflect) _get(name string) Value {
- if o.fieldsValue.Kind() == reflect.Struct {
- if ret := o._getFieldValue(name); ret != nil {
- return ret
- }
- }
- if v := o._getMethod(name); v.IsValid() {
- return o.val.runtime.toValue(v.Interface(), v)
- }
- return nil
- }
- func (o *objectGoReflect) getOwnPropStr(name unistring.String) Value {
- n := name.String()
- if o.fieldsValue.Kind() == reflect.Struct {
- if v := o._getFieldValue(n); v != nil {
- return &valueProperty{
- value: v,
- writable: true,
- enumerable: true,
- }
- }
- }
- if v := o._getMethod(n); v.IsValid() {
- return &valueProperty{
- value: o.val.runtime.toValue(v.Interface(), v),
- enumerable: true,
- }
- }
- return o.baseObject.getOwnPropStr(name)
- }
- func (o *objectGoReflect) setOwnStr(name unistring.String, val Value, throw bool) bool {
- has, ok := o._put(name.String(), val, throw)
- if !has {
- if res, ok := o._setForeignStr(name, nil, val, o.val, throw); !ok {
- o.val.runtime.typeErrorResult(throw, "Cannot assign to property %s of a host object", name)
- return false
- } else {
- return res
- }
- }
- return ok
- }
- func (o *objectGoReflect) setForeignStr(name unistring.String, val, receiver Value, throw bool) (bool, bool) {
- return o._setForeignStr(name, trueValIfPresent(o._has(name.String())), val, receiver, throw)
- }
- func (o *objectGoReflect) setForeignIdx(idx valueInt, val, receiver Value, throw bool) (bool, bool) {
- return o._setForeignIdx(idx, nil, val, receiver, throw)
- }
- func (o *objectGoReflect) _put(name string, val Value, throw bool) (has, ok bool) {
- if o.fieldsValue.Kind() == reflect.Struct {
- if v := o._getField(name); v.IsValid() {
- cached := o.valueCache[name]
- if cached != nil {
- copyReflectValueWrapper(cached)
- }
- err := o.val.runtime.toReflectValue(val, v, &objectExportCtx{})
- if err != nil {
- if cached != nil {
- cached.setReflectValue(v)
- }
- o.val.runtime.typeErrorResult(throw, "Go struct conversion error: %v", err)
- return true, false
- }
- if cached != nil {
- delete(o.valueCache, name)
- }
- return true, true
- }
- }
- return false, false
- }
- func (o *objectGoReflect) _putProp(name unistring.String, value Value, writable, enumerable, configurable bool) Value {
- if _, ok := o._put(name.String(), value, false); ok {
- return value
- }
- return o.baseObject._putProp(name, value, writable, enumerable, configurable)
- }
- func (r *Runtime) checkHostObjectPropertyDescr(name unistring.String, descr PropertyDescriptor, throw bool) bool {
- if descr.Getter != nil || descr.Setter != nil {
- r.typeErrorResult(throw, "Host objects do not support accessor properties")
- return false
- }
- if descr.Writable == FLAG_FALSE {
- r.typeErrorResult(throw, "Host object field %s cannot be made read-only", name)
- return false
- }
- if descr.Configurable == FLAG_TRUE {
- r.typeErrorResult(throw, "Host object field %s cannot be made configurable", name)
- return false
- }
- return true
- }
- func (o *objectGoReflect) defineOwnPropertyStr(name unistring.String, descr PropertyDescriptor, throw bool) bool {
- if o.val.runtime.checkHostObjectPropertyDescr(name, descr, throw) {
- n := name.String()
- if has, ok := o._put(n, descr.Value, throw); !has {
- o.val.runtime.typeErrorResult(throw, "Cannot define property '%s' on a host object", n)
- return false
- } else {
- return ok
- }
- }
- return false
- }
- func (o *objectGoReflect) _has(name string) bool {
- if o.fieldsValue.Kind() == reflect.Struct {
- if v := o._getField(name); v.IsValid() {
- return true
- }
- }
- if v := o._getMethod(name); v.IsValid() {
- return true
- }
- return false
- }
- func (o *objectGoReflect) hasOwnPropertyStr(name unistring.String) bool {
- return o._has(name.String()) || o.baseObject.hasOwnPropertyStr(name)
- }
- func (o *objectGoReflect) _valueOfInt() Value {
- return intToValue(o.fieldsValue.Int())
- }
- func (o *objectGoReflect) _valueOfUint() Value {
- return intToValue(int64(o.fieldsValue.Uint()))
- }
- func (o *objectGoReflect) _valueOfBool() Value {
- if o.fieldsValue.Bool() {
- return valueTrue
- } else {
- return valueFalse
- }
- }
- func (o *objectGoReflect) _valueOfFloat() Value {
- return floatToValue(o.fieldsValue.Float())
- }
- func (o *objectGoReflect) _toStringStringer() Value {
- return newStringValue(o.origValue.Interface().(fmt.Stringer).String())
- }
- func (o *objectGoReflect) _toStringString() Value {
- return newStringValue(o.fieldsValue.String())
- }
- func (o *objectGoReflect) _toStringBool() Value {
- if o.fieldsValue.Bool() {
- return stringTrue
- } else {
- return stringFalse
- }
- }
- func (o *objectGoReflect) _toStringError() Value {
- return newStringValue(o.origValue.Interface().(error).Error())
- }
- func (o *objectGoReflect) deleteStr(name unistring.String, throw bool) bool {
- n := name.String()
- if o._has(n) {
- o.val.runtime.typeErrorResult(throw, "Cannot delete property %s from a Go type", n)
- return false
- }
- return o.baseObject.deleteStr(name, throw)
- }
- type goreflectPropIter struct {
- o *objectGoReflect
- idx int
- }
- func (i *goreflectPropIter) nextField() (propIterItem, iterNextFunc) {
- names := i.o.fieldsInfo.Names
- if i.idx < len(names) {
- name := names[i.idx]
- i.idx++
- return propIterItem{name: newStringValue(name), enumerable: _ENUM_TRUE}, i.nextField
- }
- i.idx = 0
- return i.nextMethod()
- }
- func (i *goreflectPropIter) nextMethod() (propIterItem, iterNextFunc) {
- names := i.o.methodsInfo.Names
- if i.idx < len(names) {
- name := names[i.idx]
- i.idx++
- return propIterItem{name: newStringValue(name), enumerable: _ENUM_TRUE}, i.nextMethod
- }
- return propIterItem{}, nil
- }
- func (o *objectGoReflect) iterateStringKeys() iterNextFunc {
- r := &goreflectPropIter{
- o: o,
- }
- if o.fieldsInfo != nil {
- return r.nextField
- }
- return r.nextMethod
- }
- func (o *objectGoReflect) stringKeys(_ bool, accum []Value) []Value {
- // all own keys are enumerable
- if o.fieldsInfo != nil {
- for _, name := range o.fieldsInfo.Names {
- accum = append(accum, newStringValue(name))
- }
- }
- for _, name := range o.methodsInfo.Names {
- accum = append(accum, newStringValue(name))
- }
- return accum
- }
- func (o *objectGoReflect) export(*objectExportCtx) interface{} {
- return o.origValue.Interface()
- }
- func (o *objectGoReflect) exportType() reflect.Type {
- return o.origValue.Type()
- }
- func (o *objectGoReflect) equal(other objectImpl) bool {
- if other, ok := other.(*objectGoReflect); ok {
- k1, k2 := o.fieldsValue.Kind(), other.fieldsValue.Kind()
- if k1 == k2 {
- if isContainer(k1) {
- return o.fieldsValue == other.fieldsValue
- }
- return o.fieldsValue.Interface() == other.fieldsValue.Interface()
- }
- }
- return false
- }
- func (o *objectGoReflect) reflectValue() reflect.Value {
- return o.fieldsValue
- }
- func (o *objectGoReflect) setReflectValue(v reflect.Value) {
- o.fieldsValue = v
- o.origValue = v
- o.methodsValue = v.Addr()
- }
- func (o *objectGoReflect) esValue() Value {
- return o.val
- }
- func (r *Runtime) buildFieldInfo(t reflect.Type, index []int, info *reflectFieldsInfo) {
- n := t.NumField()
- for i := 0; i < n; i++ {
- field := t.Field(i)
- name := field.Name
- isExported := ast.IsExported(name)
- if !isExported && !field.Anonymous {
- continue
- }
- if r.fieldNameMapper != nil {
- name = r.fieldNameMapper.FieldName(t, field)
- }
- if name != "" && isExported {
- if inf, exists := info.Fields[name]; !exists {
- info.Names = append(info.Names, name)
- } else {
- if len(inf.Index) <= len(index) {
- continue
- }
- }
- }
- if name != "" || field.Anonymous {
- idx := make([]int, len(index)+1)
- copy(idx, index)
- idx[len(idx)-1] = i
- if name != "" && isExported {
- info.Fields[name] = reflectFieldInfo{
- Index: idx,
- Anonymous: field.Anonymous,
- }
- }
- if field.Anonymous {
- typ := field.Type
- for typ.Kind() == reflect.Ptr {
- typ = typ.Elem()
- }
- if typ.Kind() == reflect.Struct {
- r.buildFieldInfo(typ, idx, info)
- }
- }
- }
- }
- }
- var emptyMethodsInfo = reflectMethodsInfo{}
- func (r *Runtime) buildMethodsInfo(t reflect.Type) (info *reflectMethodsInfo) {
- n := t.NumMethod()
- if n == 0 {
- return &emptyMethodsInfo
- }
- info = new(reflectMethodsInfo)
- info.Methods = make(map[string]int, n)
- info.Names = make([]string, 0, n)
- for i := 0; i < n; i++ {
- method := t.Method(i)
- name := method.Name
- if !ast.IsExported(name) {
- continue
- }
- if r.fieldNameMapper != nil {
- name = r.fieldNameMapper.MethodName(t, method)
- if name == "" {
- continue
- }
- }
- if _, exists := info.Methods[name]; !exists {
- info.Names = append(info.Names, name)
- }
- info.Methods[name] = i
- }
- return
- }
- func (r *Runtime) buildFieldsInfo(t reflect.Type) (info *reflectFieldsInfo) {
- info = new(reflectFieldsInfo)
- n := t.NumField()
- info.Fields = make(map[string]reflectFieldInfo, n)
- info.Names = make([]string, 0, n)
- r.buildFieldInfo(t, nil, info)
- return
- }
- func (r *Runtime) fieldsInfo(t reflect.Type) (info *reflectFieldsInfo) {
- var exists bool
- if info, exists = r.fieldsInfoCache[t]; !exists {
- info = r.buildFieldsInfo(t)
- if r.fieldsInfoCache == nil {
- r.fieldsInfoCache = make(map[reflect.Type]*reflectFieldsInfo)
- }
- r.fieldsInfoCache[t] = info
- }
- return
- }
- func (r *Runtime) methodsInfo(t reflect.Type) (info *reflectMethodsInfo) {
- var exists bool
- if info, exists = r.methodsInfoCache[t]; !exists {
- info = r.buildMethodsInfo(t)
- if r.methodsInfoCache == nil {
- r.methodsInfoCache = make(map[reflect.Type]*reflectMethodsInfo)
- }
- r.methodsInfoCache[t] = info
- }
- return
- }
- // SetFieldNameMapper sets a custom field name mapper for Go types. It can be called at any time, however
- // the mapping for any given value is fixed at the point of creation.
- // Setting this to nil restores the default behaviour which is all exported fields and methods are mapped to their
- // original unchanged names.
- func (r *Runtime) SetFieldNameMapper(mapper FieldNameMapper) {
- r.fieldNameMapper = mapper
- r.fieldsInfoCache = nil
- r.methodsInfoCache = nil
- }
- // TagFieldNameMapper returns a FieldNameMapper that uses the given tagName for struct fields and optionally
- // uncapitalises (making the first letter lower case) method names.
- // The common tag value syntax is supported (name[,options]), however options are ignored.
- // Setting name to anything other than a valid ECMAScript identifier makes the field hidden.
- func TagFieldNameMapper(tagName string, uncapMethods bool) FieldNameMapper {
- return tagFieldNameMapper{
- tagName: tagName,
- uncapMethods: uncapMethods,
- }
- }
- // UncapFieldNameMapper returns a FieldNameMapper that uncapitalises struct field and method names
- // making the first letter lower case.
- func UncapFieldNameMapper() FieldNameMapper {
- return uncapFieldNameMapper{}
- }
|