Browse Source

Allow adding symbol properties to CLR objects (#1131)

Marko Lahma 3 years ago
parent
commit
6b24844b7d
2 changed files with 56 additions and 6 deletions
  1. 46 0
      Jint.Tests/Runtime/InteropTests.cs
  2. 10 6
      Jint/Runtime/Interop/ObjectWrapper.cs

+ 46 - 0
Jint.Tests/Runtime/InteropTests.cs

@@ -6,6 +6,7 @@ using System.Linq;
 using System.Reflection;
 using Jint.Native;
 using Jint.Native.Object;
+using Jint.Native.Symbol;
 using Jint.Runtime;
 using Jint.Runtime.Interop;
 using Jint.Tests.Runtime.Converters;
@@ -918,6 +919,51 @@ namespace Jint.Tests.Runtime
             var name = e.Evaluate("o.values.filter(x => x.age == 12)[0].name").ToString();
             Assert.Equal("John", name);
         }
+        
+                
+        [Fact]
+        public void CanSetIsConcatSpreadableForArrays()
+        {
+            var engine = new Engine(opt =>
+            {
+                opt.SetWrapObjectHandler((eng, obj) =>
+                {
+                    var wrapper = new ObjectWrapper(eng, obj);
+                    if (wrapper.IsArrayLike)
+                    {
+                        wrapper.SetPrototypeOf(eng.Realm.Intrinsics.Array.PrototypeObject);
+                        wrapper.Set(GlobalSymbolRegistry.IsConcatSpreadable, true);
+                    }
+                    return wrapper;
+                });
+            });
+
+            engine
+                .SetValue("list1", new List<string> { "A", "B", "C" })
+                .SetValue("list2", new List<string> { "D", "E", "F" })
+                .Execute("var array1 = ['A', 'B', 'C'];")
+                .Execute("var array2 = ['D', 'E', 'F'];");
+
+            Assert.True(engine.Evaluate("list1[Symbol.isConcatSpreadable] = true; list1[Symbol.isConcatSpreadable];").AsBoolean());
+            Assert.True(engine.Evaluate("list2[Symbol.isConcatSpreadable] = true; list2[Symbol.isConcatSpreadable];").AsBoolean());
+            
+            Assert.Equal("[\"A\",\"B\",\"C\"]", engine.Evaluate("JSON.stringify(array1);"));
+            Assert.Equal("[\"D\",\"E\",\"F\"]", engine.Evaluate("JSON.stringify(array2);"));
+            Assert.Equal("[\"A\",\"B\",\"C\"]", engine.Evaluate("JSON.stringify(list1);"));
+            Assert.Equal("[\"D\",\"E\",\"F\"]", engine.Evaluate("JSON.stringify(list2);"));
+
+            const string Concatenated = "[\"A\",\"B\",\"C\",\"D\",\"E\",\"F\"]";
+            Assert.Equal(Concatenated, engine.Evaluate("JSON.stringify(array1.concat(array2));"));
+            Assert.Equal(Concatenated, engine.Evaluate("JSON.stringify(array1.concat(list2));"));
+            Assert.Equal(Concatenated, engine.Evaluate("JSON.stringify(list1.concat(array2));"));
+            Assert.Equal(Concatenated, engine.Evaluate("JSON.stringify(list1.concat(list2));"));
+            
+            Assert.False(engine.Evaluate("list1[Symbol.isConcatSpreadable] = false; list1[Symbol.isConcatSpreadable];").AsBoolean());
+            Assert.False(engine.Evaluate("list2[Symbol.isConcatSpreadable] = false; list2[Symbol.isConcatSpreadable];").AsBoolean());
+            
+            Assert.Equal("[[\"A\",\"B\",\"C\"]]", engine.Evaluate("JSON.stringify([].concat(list1));"));
+            Assert.Equal("[[\"A\",\"B\",\"C\"],[\"D\",\"E\",\"F\"]]", engine.Evaluate("JSON.stringify(list1.concat(list2));"));
+        }
 
         [Fact]
         public void ShouldConvertArrayToArrayInstance()

+ 10 - 6
Jint/Runtime/Interop/ObjectWrapper.cs

@@ -70,6 +70,16 @@ namespace Jint.Runtime.Interop
                     return true;
                 }
             }
+            else if (property is JsSymbol jsSymbol)
+            {
+                // symbol addition will never hit any known CLR object properties, so if write is allowed, allow writing symbols too 
+                if (_engine.Options.Interop.AllowWrite)
+                {
+                    return base.Set(jsSymbol, value, receiver);
+                }
+
+                return false;
+            }
 
             return SetSlow(property, value);
         }
@@ -105,12 +115,6 @@ namespace Jint.Runtime.Interop
                 return (uint) index < list.Count ? FromObject(_engine, list[index]) : Undefined;
             }
 
-            if (property.IsSymbol() && property != GlobalSymbolRegistry.Iterator)
-            {
-                // wrapped objects cannot have symbol properties
-                return Undefined;
-            }
-
             return base.Get(property, receiver);
         }