From e1afcee0b06b3ea3d30c2aa334cf9d8f0ea58c43 Mon Sep 17 00:00:00 2001 From: Archis Date: Fri, 2 Jun 2017 02:05:44 -0700 Subject: [PATCH] Other methods callable on Maps (#254) Background: When methods are attached on a map type like so: type Foo map[string]string func (f Foo) Bar() { fmt.Printf("Hello World"); } vm := otto.New(); vm.Set("foo", Foo{}); vm.Run(` foo.Bar(); `); You get: Error in Run: TypeError: 'Bar' is not a function The Fix: I looked into how/why the same works for arrays. After all array properties are tested (such as length, and any integer-based members), the code then looks for any methods attached to that type. This change literally copies that code over into maps. This is very useful when working with the http.Request object which has the http.Header type that is a map[string][]string, with a lot of useful methods attached to it. Added unit test to support/guard the change (and map had no test before) Responded to PR comments --- type_go_map.go | 8 ++++++++ type_go_map_test.go | 40 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+) create mode 100644 type_go_map_test.go diff --git a/type_go_map.go b/type_go_map.go index 3e204a0..41b9ac0 100644 --- a/type_go_map.go +++ b/type_go_map.go @@ -53,6 +53,14 @@ func goMapGetOwnProperty(self *_object, name string) *_property { return &_property{self.runtime.toValue(value.Interface()), 0111} } + // Other methods + if method := self.value.(*_goMapObject).value.MethodByName(name); (method != reflect.Value{}) { + return &_property{ + value: self.runtime.toValue(method.Interface()), + mode: 0110, + } + } + return nil } diff --git a/type_go_map_test.go b/type_go_map_test.go new file mode 100644 index 0000000..a89892b --- /dev/null +++ b/type_go_map_test.go @@ -0,0 +1,40 @@ +package otto + +import ( + "fmt" + "sort" + "testing" +) + +type GoMapTest map[string]int + +func (s GoMapTest) Join() string { + joinedStr := "" + + // Ordering the map takes some effort + // because map iterators in golang are unordered by definition. + // So we need to extract keys, sort them, and then generate K/V pairs + // All of this is meant to ensure that the test is predictable. + keys := make([]string, len(s)) + i := 0 + for key, _ := range s { + keys[i] = key + i++ + } + + sort.Strings(keys) + + for _, key := range keys { + joinedStr += key + ": " + fmt.Sprintf("%d", s[key]) + " " + } + return joinedStr +} + +func TestGoMap(t *testing.T) { + tt(t, func() { + test, vm := test() + vm.Set("TestMap", GoMapTest{"one": 1, "two": 2, "three": 3}) + is(test(`TestMap["one"]`).export(), 1) + is(test(`TestMap.Join()`).export(), "one: 1 three: 3 two: 2 ") + }) +}