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