1package goja
2
3import (
4	"fmt"
5	"go/ast"
6	"reflect"
7)
8
9// JsonEncodable allows custom JSON encoding by JSON.stringify()
10// Note that if the returned value itself also implements JsonEncodable, it won't have any effect.
11type JsonEncodable interface {
12	JsonEncodable() interface{}
13}
14
15// FieldNameMapper provides custom mapping between Go and JavaScript property names.
16type FieldNameMapper interface {
17	// FieldName returns a JavaScript name for the given struct field in the given type.
18	// If this method returns "" the field becomes hidden.
19	FieldName(t reflect.Type, f reflect.StructField) string
20
21	// FieldName returns a JavaScript name for the given method in the given type.
22	// If this method returns "" the method becomes hidden.
23	MethodName(t reflect.Type, m reflect.Method) string
24}
25
26type reflectFieldInfo struct {
27	Index     []int
28	Anonymous bool
29}
30
31type reflectTypeInfo struct {
32	Fields                  map[string]reflectFieldInfo
33	Methods                 map[string]int
34	FieldNames, MethodNames []string
35}
36
37type objectGoReflect struct {
38	baseObject
39	origValue, value reflect.Value
40
41	valueTypeInfo, origValueTypeInfo *reflectTypeInfo
42
43	toJson func() interface{}
44}
45
46func (o *objectGoReflect) init() {
47	o.baseObject.init()
48	switch o.value.Kind() {
49	case reflect.Bool:
50		o.class = classBoolean
51		o.prototype = o.val.runtime.global.BooleanPrototype
52	case reflect.String:
53		o.class = classString
54		o.prototype = o.val.runtime.global.StringPrototype
55	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
56		reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64,
57		reflect.Float32, reflect.Float64:
58
59		o.class = classNumber
60		o.prototype = o.val.runtime.global.NumberPrototype
61	default:
62		o.class = classObject
63		o.prototype = o.val.runtime.global.ObjectPrototype
64	}
65
66	o.baseObject._putProp("toString", o.val.runtime.newNativeFunc(o.toStringFunc, nil, "toString", nil, 0), true, false, true)
67	o.baseObject._putProp("valueOf", o.val.runtime.newNativeFunc(o.valueOfFunc, nil, "valueOf", nil, 0), true, false, true)
68
69	o.valueTypeInfo = o.val.runtime.typeInfo(o.value.Type())
70	o.origValueTypeInfo = o.val.runtime.typeInfo(o.origValue.Type())
71
72	if j, ok := o.origValue.Interface().(JsonEncodable); ok {
73		o.toJson = j.JsonEncodable
74	}
75}
76
77func (o *objectGoReflect) toStringFunc(call FunctionCall) Value {
78	return o.toPrimitiveString()
79}
80
81func (o *objectGoReflect) valueOfFunc(call FunctionCall) Value {
82	return o.toPrimitive()
83}
84
85func (o *objectGoReflect) get(n Value) Value {
86	return o.getStr(n.String())
87}
88
89func (o *objectGoReflect) _getField(jsName string) reflect.Value {
90	if info, exists := o.valueTypeInfo.Fields[jsName]; exists {
91		v := o.value.FieldByIndex(info.Index)
92		if info.Anonymous {
93			v = v.Addr()
94		}
95		return v
96	}
97
98	return reflect.Value{}
99}
100
101func (o *objectGoReflect) _getMethod(jsName string) reflect.Value {
102	if idx, exists := o.origValueTypeInfo.Methods[jsName]; exists {
103		return o.origValue.Method(idx)
104	}
105
106	return reflect.Value{}
107}
108
109func (o *objectGoReflect) _get(name string) Value {
110	if o.value.Kind() == reflect.Struct {
111		if v := o._getField(name); v.IsValid() {
112			return o.val.runtime.ToValue(v.Interface())
113		}
114	}
115
116	if v := o._getMethod(name); v.IsValid() {
117		return o.val.runtime.ToValue(v.Interface())
118	}
119
120	return nil
121}
122
123func (o *objectGoReflect) getStr(name string) Value {
124	if v := o._get(name); v != nil {
125		return v
126	}
127	return o.baseObject._getStr(name)
128}
129
130func (o *objectGoReflect) getProp(n Value) Value {
131	name := n.String()
132	if p := o.getOwnProp(name); p != nil {
133		return p
134	}
135	return o.baseObject.getOwnProp(name)
136}
137
138func (o *objectGoReflect) getPropStr(name string) Value {
139	if v := o.getOwnProp(name); v != nil {
140		return v
141	}
142	return o.baseObject.getPropStr(name)
143}
144
145func (o *objectGoReflect) getOwnProp(name string) Value {
146	if o.value.Kind() == reflect.Struct {
147		if v := o._getField(name); v.IsValid() {
148			return &valueProperty{
149				value:      o.val.runtime.ToValue(v.Interface()),
150				writable:   true,
151				enumerable: true,
152			}
153		}
154	}
155
156	if v := o._getMethod(name); v.IsValid() {
157		return &valueProperty{
158			value:      o.val.runtime.ToValue(v.Interface()),
159			enumerable: true,
160		}
161	}
162
163	return nil
164}
165
166func (o *objectGoReflect) put(n Value, val Value, throw bool) {
167	o.putStr(n.String(), val, throw)
168}
169
170func (o *objectGoReflect) putStr(name string, val Value, throw bool) {
171	if !o._put(name, val, throw) {
172		o.val.runtime.typeErrorResult(throw, "Cannot assign to property %s of a host object", name)
173	}
174}
175
176func (o *objectGoReflect) _put(name string, val Value, throw bool) bool {
177	if o.value.Kind() == reflect.Struct {
178		if v := o._getField(name); v.IsValid() {
179			vv, err := o.val.runtime.toReflectValue(val, v.Type())
180			if err != nil {
181				o.val.runtime.typeErrorResult(throw, "Go struct conversion error: %v", err)
182				return false
183			}
184			v.Set(vv)
185			return true
186		}
187	}
188	return false
189}
190
191func (o *objectGoReflect) _putProp(name string, value Value, writable, enumerable, configurable bool) Value {
192	if o._put(name, value, false) {
193		return value
194	}
195	return o.baseObject._putProp(name, value, writable, enumerable, configurable)
196}
197
198func (r *Runtime) checkHostObjectPropertyDescr(name string, descr propertyDescr, throw bool) bool {
199	if descr.Getter != nil || descr.Setter != nil {
200		r.typeErrorResult(throw, "Host objects do not support accessor properties")
201		return false
202	}
203	if descr.Writable == FLAG_FALSE {
204		r.typeErrorResult(throw, "Host object field %s cannot be made read-only", name)
205		return false
206	}
207	if descr.Configurable == FLAG_TRUE {
208		r.typeErrorResult(throw, "Host object field %s cannot be made configurable", name)
209		return false
210	}
211	return true
212}
213
214func (o *objectGoReflect) defineOwnProperty(n Value, descr propertyDescr, throw bool) bool {
215	name := n.String()
216	if ast.IsExported(name) {
217		if o.value.Kind() == reflect.Struct {
218			if v := o._getField(name); v.IsValid() {
219				if !o.val.runtime.checkHostObjectPropertyDescr(name, descr, throw) {
220					return false
221				}
222				val := descr.Value
223				if val == nil {
224					val = _undefined
225				}
226				vv, err := o.val.runtime.toReflectValue(val, v.Type())
227				if err != nil {
228					o.val.runtime.typeErrorResult(throw, "Go struct conversion error: %v", err)
229					return false
230				}
231				v.Set(vv)
232				return true
233			}
234		}
235	}
236
237	return o.baseObject.defineOwnProperty(n, descr, throw)
238}
239
240func (o *objectGoReflect) _has(name string) bool {
241	if !ast.IsExported(name) {
242		return false
243	}
244	if o.value.Kind() == reflect.Struct {
245		if v := o._getField(name); v.IsValid() {
246			return true
247		}
248	}
249	if v := o._getMethod(name); v.IsValid() {
250		return true
251	}
252	return false
253}
254
255func (o *objectGoReflect) hasProperty(n Value) bool {
256	name := n.String()
257	if o._has(name) {
258		return true
259	}
260	return o.baseObject.hasProperty(n)
261}
262
263func (o *objectGoReflect) hasPropertyStr(name string) bool {
264	if o._has(name) {
265		return true
266	}
267	return o.baseObject.hasPropertyStr(name)
268}
269
270func (o *objectGoReflect) hasOwnProperty(n Value) bool {
271	return o._has(n.String())
272}
273
274func (o *objectGoReflect) hasOwnPropertyStr(name string) bool {
275	return o._has(name)
276}
277
278func (o *objectGoReflect) _toNumber() Value {
279	switch o.value.Kind() {
280	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
281		return intToValue(o.value.Int())
282	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
283		return intToValue(int64(o.value.Uint()))
284	case reflect.Bool:
285		if o.value.Bool() {
286			return intToValue(1)
287		} else {
288			return intToValue(0)
289		}
290	case reflect.Float32, reflect.Float64:
291		return floatToValue(o.value.Float())
292	}
293	return nil
294}
295
296func (o *objectGoReflect) _toString() Value {
297	switch o.value.Kind() {
298	case reflect.String:
299		return newStringValue(o.value.String())
300	case reflect.Bool:
301		if o.value.Interface().(bool) {
302			return stringTrue
303		} else {
304			return stringFalse
305		}
306	}
307	switch v := o.value.Interface().(type) {
308	case fmt.Stringer:
309		return newStringValue(v.String())
310	}
311	return stringObjectObject
312}
313
314func (o *objectGoReflect) toPrimitiveNumber() Value {
315	if v := o._toNumber(); v != nil {
316		return v
317	}
318	return o._toString()
319}
320
321func (o *objectGoReflect) toPrimitiveString() Value {
322	if v := o._toNumber(); v != nil {
323		return v.ToString()
324	}
325	return o._toString()
326}
327
328func (o *objectGoReflect) toPrimitive() Value {
329	if o.prototype == o.val.runtime.global.NumberPrototype {
330		return o.toPrimitiveNumber()
331	}
332	return o.toPrimitiveString()
333}
334
335func (o *objectGoReflect) deleteStr(name string, throw bool) bool {
336	if o._has(name) {
337		o.val.runtime.typeErrorResult(throw, "Cannot delete property %s from a Go type")
338		return false
339	}
340	return o.baseObject.deleteStr(name, throw)
341}
342
343func (o *objectGoReflect) delete(name Value, throw bool) bool {
344	return o.deleteStr(name.String(), throw)
345}
346
347type goreflectPropIter struct {
348	o         *objectGoReflect
349	idx       int
350	recursive bool
351}
352
353func (i *goreflectPropIter) nextField() (propIterItem, iterNextFunc) {
354	names := i.o.valueTypeInfo.FieldNames
355	if i.idx < len(names) {
356		name := names[i.idx]
357		i.idx++
358		return propIterItem{name: name, enumerable: _ENUM_TRUE}, i.nextField
359	}
360
361	i.idx = 0
362	return i.nextMethod()
363}
364
365func (i *goreflectPropIter) nextMethod() (propIterItem, iterNextFunc) {
366	names := i.o.origValueTypeInfo.MethodNames
367	if i.idx < len(names) {
368		name := names[i.idx]
369		i.idx++
370		return propIterItem{name: name, enumerable: _ENUM_TRUE}, i.nextMethod
371	}
372
373	if i.recursive {
374		return i.o.baseObject._enumerate(true)()
375	}
376
377	return propIterItem{}, nil
378}
379
380func (o *objectGoReflect) _enumerate(recursive bool) iterNextFunc {
381	r := &goreflectPropIter{
382		o:         o,
383		recursive: recursive,
384	}
385	if o.value.Kind() == reflect.Struct {
386		return r.nextField
387	}
388	return r.nextMethod
389}
390
391func (o *objectGoReflect) enumerate(all, recursive bool) iterNextFunc {
392	return (&propFilterIter{
393		wrapped: o._enumerate(recursive),
394		all:     all,
395		seen:    make(map[string]bool),
396	}).next
397}
398
399func (o *objectGoReflect) export() interface{} {
400	return o.origValue.Interface()
401}
402
403func (o *objectGoReflect) exportType() reflect.Type {
404	return o.origValue.Type()
405}
406
407func (o *objectGoReflect) equal(other objectImpl) bool {
408	if other, ok := other.(*objectGoReflect); ok {
409		return o.value.Interface() == other.value.Interface()
410	}
411	return false
412}
413
414func (r *Runtime) buildFieldInfo(t reflect.Type, index []int, info *reflectTypeInfo) {
415	n := t.NumField()
416	for i := 0; i < n; i++ {
417		field := t.Field(i)
418		name := field.Name
419		if !ast.IsExported(name) {
420			continue
421		}
422		if r.fieldNameMapper != nil {
423			name = r.fieldNameMapper.FieldName(t, field)
424			if name == "" {
425				continue
426			}
427		}
428
429		if inf, exists := info.Fields[name]; !exists {
430			info.FieldNames = append(info.FieldNames, name)
431		} else {
432			if len(inf.Index) <= len(index) {
433				continue
434			}
435		}
436
437		idx := make([]int, len(index)+1)
438		copy(idx, index)
439		idx[len(idx)-1] = i
440
441		info.Fields[name] = reflectFieldInfo{
442			Index:     idx,
443			Anonymous: field.Anonymous,
444		}
445		if field.Anonymous {
446			typ := field.Type
447			for typ.Kind() == reflect.Ptr {
448				typ = typ.Elem()
449			}
450			if typ.Kind() == reflect.Struct {
451				r.buildFieldInfo(typ, idx, info)
452			}
453		}
454	}
455}
456
457func (r *Runtime) buildTypeInfo(t reflect.Type) (info *reflectTypeInfo) {
458	info = new(reflectTypeInfo)
459	if t.Kind() == reflect.Struct {
460		info.Fields = make(map[string]reflectFieldInfo)
461		n := t.NumField()
462		info.FieldNames = make([]string, 0, n)
463		r.buildFieldInfo(t, nil, info)
464	}
465
466	info.Methods = make(map[string]int)
467	n := t.NumMethod()
468	info.MethodNames = make([]string, 0, n)
469	for i := 0; i < n; i++ {
470		method := t.Method(i)
471		name := method.Name
472		if !ast.IsExported(name) {
473			continue
474		}
475		if r.fieldNameMapper != nil {
476			name = r.fieldNameMapper.MethodName(t, method)
477			if name == "" {
478				continue
479			}
480		}
481
482		if _, exists := info.Methods[name]; !exists {
483			info.MethodNames = append(info.MethodNames, name)
484		}
485
486		info.Methods[name] = i
487	}
488	return
489}
490
491func (r *Runtime) typeInfo(t reflect.Type) (info *reflectTypeInfo) {
492	var exists bool
493	if info, exists = r.typeInfoCache[t]; !exists {
494		info = r.buildTypeInfo(t)
495		if r.typeInfoCache == nil {
496			r.typeInfoCache = make(map[reflect.Type]*reflectTypeInfo)
497		}
498		r.typeInfoCache[t] = info
499	}
500
501	return
502}
503
504// Sets a custom field name mapper for Go types. It can be called at any time, however
505// the mapping for any given value is fixed at the point of creation.
506// Setting this to nil restores the default behaviour which is all exported fields and methods are mapped to their
507// original unchanged names.
508func (r *Runtime) SetFieldNameMapper(mapper FieldNameMapper) {
509	r.fieldNameMapper = mapper
510	r.typeInfoCache = nil
511}
512