Skip to content

Commit

Permalink
ExpandoObject support
Browse files Browse the repository at this point in the history
  • Loading branch information
nilproject committed Nov 7, 2016
1 parent 9249a6a commit a4f79da
Show file tree
Hide file tree
Showing 10 changed files with 197 additions and 74 deletions.
64 changes: 64 additions & 0 deletions FunctionalTests/ExpandoObjectTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
using Microsoft.VisualStudio.TestTools.UnitTesting;
using NiL.JS.Core;
using System.Collections.Generic;
using System.Dynamic;

namespace FunctionalTests
{
[TestClass]
public class ExpandoObjectTests
{
[TestMethod]
public void ReadPropertiesOfDynamicobject()
{
dynamic obj = new ExpandoObject();
obj.field = "value";
var context = new Context();
context.DefineVariable("obj").Assign(JSValue.Marshal(obj));

var value = context.Eval("obj.field");

Assert.AreEqual(JSValueType.String, value.ValueType);
Assert.AreEqual("value", value.Value);
}

[TestMethod]
public void WritePropertiesOfDynamicobject()
{
dynamic obj = new ExpandoObject();
var context = new Context();
context.DefineVariable("obj").Assign(JSValue.Marshal(obj));

var value = context.Eval("obj.field = 'value'");

Assert.IsInstanceOfType(obj.field, typeof(string));
Assert.AreEqual("value", obj.field);
}

[TestMethod]
public void WritePropertiesOfDynamicobjectOverWith()
{
dynamic obj = new ExpandoObject();
obj.field = null;
var context = new Context();
context.DefineVariable("obj").Assign(JSValue.Marshal(obj));

var value = context.Eval("with(obj) field = 'value'");

Assert.IsInstanceOfType(obj.field, typeof(string));
Assert.AreEqual("value", obj.field);
}

[TestMethod]
public void WriteInsideWithoShouldNotCreateNewField()
{
dynamic obj = new ExpandoObject();
var context = new Context();
context.DefineVariable("obj").Assign(JSValue.Marshal(obj));

var value = context.Eval("with(obj) field = 'value'");

Assert.IsFalse((obj as IDictionary<string, object>).ContainsKey("field"));
}
}
}
2 changes: 2 additions & 0 deletions FunctionalTests/FunctionalTests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@
<CodeAnalysisRuleSet>MinimumRecommendedRules.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
<ItemGroup>
<Reference Include="Microsoft.CSharp" />
<Reference Include="System" />
</ItemGroup>
<Choose>
Expand All @@ -69,6 +70,7 @@
</Otherwise>
</Choose>
<ItemGroup>
<Compile Include="ExpandoObjectTests.cs" />
<Compile Include="FunctionsToDelegateWrapping.cs" />
<Compile Include="Generated\Embedded.cs">
<DependentUpon>Embedded.tt</DependentUpon>
Expand Down
5 changes: 3 additions & 2 deletions FunctionalTests/Generated/FileTestBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,13 @@ protected void RunFile(string fileName)
using (var f = new FileStream(fileName, FileMode.Open, FileAccess.Read))
using (var sr = new StreamReader(f))
code = sr.ReadToEnd();

var globalContext = new GlobalContext();
globalContext.ActivateInCurrentThread();

try
{
globalContext.ActivateInCurrentThread();

var negative = false;
var output = new StringBuilder();
var oldOutput = Console.Out;
Expand Down
24 changes: 17 additions & 7 deletions FunctionalTests/JsFunfuzz.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,25 @@ public class JsFunfuzz
private StringBuilder _output;
private TextWriter _oldOutput;
private TextWriter _oldErrOutput;
private GlobalContext _context;

[TestInitialize]
public void Initialize()
{
new GlobalContext().ActivateInCurrentThread();

using (var file = new FileStream(JsFunfuzzScriptPath, FileMode.Open))
using (var fileReader = new StreamReader(file))
_module = new Module(fileReader.ReadToEnd());
_context = new GlobalContext();
_context.ActivateInCurrentThread();

try
{
using (var file = new FileStream(JsFunfuzzScriptPath, FileMode.Open))
using (var fileReader = new StreamReader(file))
_module = new Module(fileReader.ReadToEnd());
}
catch
{
_context.Deactivate();
throw;
}

_output = new StringBuilder();
_oldErrOutput = Console.Error;
Expand All @@ -36,10 +46,10 @@ public void Initialize()
[TestCleanup]
public void Cleanup()
{
_context.Deactivate();

Console.SetOut(_oldOutput);
Console.SetError(_oldErrOutput);

Context.CurrentContext.GlobalContext.Deactivate();
}

[TestMethod]
Expand Down
6 changes: 4 additions & 2 deletions FunctionalTests/UglifyJs.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,13 @@ public class UglifyJs
private static readonly string UglifyJsScriptPath = Environment.CurrentDirectory + "/../../../Tests/uglifyjs.js";

private Module _module;
private GlobalContext _context;

[TestInitialize]
public void Initialize()
{
new GlobalContext().ActivateInCurrentThread();
_context = new GlobalContext();
_context.ActivateInCurrentThread();

using (var file = new FileStream(UglifyJsScriptPath, FileMode.Open))
using (var fileReader = new StreamReader(file))
Expand All @@ -32,7 +34,7 @@ public void Initialize()
[TestCleanup]
public void Cleanup()
{
_module.Context.GlobalContext.Deactivate();
_context.Deactivate();
}

[TestMethod]
Expand Down
17 changes: 7 additions & 10 deletions NiL.JS/Core/GlobalContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
using NiL.JS.Core.Functions;
using NiL.JS.Core.Interop;
using NiL.JS.Extensions;
using System.Dynamic;

#if NET40 || NETCORE
using NiL.JS.Backward;
Expand Down Expand Up @@ -434,19 +435,15 @@ public JSValue ProxyValue(object value)
{
if (value is Delegate)
{
return new JSValue
{
_oValue = new MethodProxy(this, ((Delegate)value).GetMethodInfo(), ((Delegate)value).Target),
_valueType = JSValueType.Function
};
return new MethodProxy(this, ((Delegate)value).GetMethodInfo(), ((Delegate)value).Target);
}
else if (value is IList)
{
return new JSValue
{
_oValue = new NativeList(value as IList),
_valueType = JSValueType.Object
};
return new NativeList(value as IList);
}
else if (value is ExpandoObject)
{
return new ExpandoObjectWrapper(value as ExpandoObject);
}
else
{
Expand Down
91 changes: 91 additions & 0 deletions NiL.JS/Core/Interop/ExpandoObjectWrapper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
using System;
using System.Collections.Generic;
using System.Dynamic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace NiL.JS.Core.Interop
{
internal sealed class ExpandoObjectWrapper : JSObject
{
private readonly ExpandoObject _target;

private sealed class ValueWrapper : JSValue
{
private readonly string _key;
private readonly ExpandoObjectWrapper _owner;

public ValueWrapper(ExpandoObjectWrapper owner, string key)
{
_owner = owner;
_key = key;
_attributes |= JSValueAttributesInternal.Reassign;

object value = null;
if ((owner._target as IDictionary<string, object>).TryGetValue(key, out value))
base.Assign(Marshal(value));
}

public override void Assign(JSValue value)
{
(_owner._target as IDictionary<string, object>)[_key] = value.Value;

base.Assign(value);
}
}

public ExpandoObjectWrapper(ExpandoObject target)
{
_valueType = JSValueType.Object;
_oValue = this;
_target = target;
}

protected internal override JSValue GetProperty(JSValue key, bool forWrite, PropertyScope propertyScope)
{
if (key.ValueType == JSValueType.Symbol || propertyScope >= PropertyScope.Super)
return base.GetProperty(key, forWrite, propertyScope);

var keyString = key.ToString();
var targetDictionary = _target as IDictionary<string, object>;

if (!forWrite)
{
if (!targetDictionary.ContainsKey(keyString))
return undefined;

return Marshal(targetDictionary[keyString]);
}

return new ValueWrapper(this, keyString);
}

protected internal override void SetProperty(JSValue key, JSValue value, PropertyScope propertyScope, bool throwOnError)
{
if (key.ValueType == JSValueType.Symbol || propertyScope >= PropertyScope.Super)
base.SetProperty(key, value, propertyScope, throwOnError);

(_target as IDictionary<string, object>)[key.ToString()] = value.Value;
}

protected internal override bool DeleteProperty(JSValue key)
{
if (key.ValueType == JSValueType.Symbol)
return base.DeleteProperty(key);

return (_target as IDictionary<string, object>).Remove(key.ToString());
}

protected internal override IEnumerator<KeyValuePair<string, JSValue>> GetEnumerator(bool hideNonEnum, EnumerationMode enumeratorMode)
{
if (enumeratorMode == EnumerationMode.KeysOnly)
return (_target as IDictionary<string, object>).Keys.Select(x => new KeyValuePair<string, JSValue>(x, null)).GetEnumerator();

if (enumeratorMode == EnumerationMode.RequireValues)
return (_target as IDictionary<string, object>).Select(x => new KeyValuePair<string, JSValue>(x.Key, Marshal(x.Value))).GetEnumerator();

return (_target as IDictionary<string, object>).Select(x => new KeyValuePair<string, JSValue>(x.Key, new ValueWrapper(this, x.Key))).GetEnumerator();
}
}
}
16 changes: 8 additions & 8 deletions NiL.JS/Core/JSObject.cs
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,7 @@ private JSValue getSymbol(JSValue key, bool forWrite, PropertyScope memberScope)
return res;
}

protected internal override void SetProperty(JSValue key, JSValue value, PropertyScope memberScope, bool throwOnError)
protected internal override void SetProperty(JSValue key, JSValue value, PropertyScope propertyScope, bool throwOnError)
{
JSValue field;
if (_valueType >= JSValueType.Object && _oValue != this)
Expand All @@ -235,7 +235,7 @@ protected internal override void SetProperty(JSValue key, JSValue value, Propert
field = _oValue as JSObject;
if (field != null)
{
field.SetProperty(key, value, memberScope, throwOnError);
field.SetProperty(key, value, propertyScope, throwOnError);
return;
}
}
Expand All @@ -261,22 +261,22 @@ protected internal override void SetProperty(JSValue key, JSValue value, Propert
}
}

protected internal override bool DeleteProperty(JSValue name)
protected internal override bool DeleteProperty(JSValue key)
{
JSValue field;
if (_valueType >= JSValueType.Object && _oValue != this)
{
if (_oValue == null)
ExceptionHelper.Throw(new TypeError("Can't get property \"" + name + "\" of \"null\""));
ExceptionHelper.Throw(new TypeError("Can't get property \"" + key + "\" of \"null\""));

field = _oValue as JSObject;
if (field != null)
return field.DeleteProperty(name);
return field.DeleteProperty(key);
}

string tname = null;
if (_fields != null
&& _fields.TryGetValue(tname = name.ToString(), out field)
&& _fields.TryGetValue(tname = key.ToString(), out field)
&& (!field.Exists || (field._attributes & JSValueAttributesInternal.DoNotDelete) == 0))
{
if ((field._attributes & JSValueAttributesInternal.SystemObject) == 0)
Expand All @@ -285,12 +285,12 @@ protected internal override bool DeleteProperty(JSValue name)
return _fields.Remove(tname);
}

field = GetProperty(name, true, PropertyScope.Own);
field = GetProperty(key, true, PropertyScope.Own);
if (!field.Exists)
return true;

if ((field._attributes & JSValueAttributesInternal.SystemObject) != 0)
field = GetProperty(name, true, PropertyScope.Own);
field = GetProperty(key, true, PropertyScope.Own);

if ((field._attributes & JSValueAttributesInternal.DoNotDelete) == 0)
{
Expand Down
44 changes: 0 additions & 44 deletions NiL.JS/Expressions/AssignmentOverReplace.cs

This file was deleted.

Loading

0 comments on commit a4f79da

Please sign in to comment.