// HELPER FUNCTIONS //_________________________________________________________________________________________ /// <summary> /// Checks whether the given descriptor is compatible with the current descriptor. /// </summary> /// <param name="descriptor"> The new descriptor. </param> /// <param name="current"> The descriptor corresponding to the currently existing property. </param> /// <returns> <c>true</c> if the new descriptor is compatible with the old one; <c>false</c> otherwise. </returns> internal static bool IsCompatible(PropertyDescriptor descriptor, PropertyDescriptor current) { // If the current property is configurable, then allow the modification. if (current.IsConfigurable) { return(true); } // If both properties are accessors, then they must match exactly. if (descriptor.value is PropertyAccessorValue descriptorAccessor) { if (current.value is PropertyAccessorValue currentAccessor) { return(TypeComparer.SameValue(descriptorAccessor.Getter, currentAccessor.Getter) && TypeComparer.SameValue(descriptorAccessor.Setter, currentAccessor.Setter) && descriptor.Attributes == current.Attributes); } return(false); } // If this is a data property, and writable is false, then the properties must match exactly. if (current.IsWritable == false) { return(TypeComparer.SameValue(descriptor.Value, current.Value) && descriptor.Attributes == current.Attributes); } // Otherwise, if the data property is writable, then IsEnumerable and IsConfigurable must match. // Value changes are allowed, and making the property non-writable is also allowed. return(!descriptor.IsConfigurable && descriptor.IsEnumerable == current.IsEnumerable); }
/// <summary> /// Gets the value of the property with the given name. /// </summary> /// <param name="key"> The property key (either a string or a Symbol). </param> /// <param name="thisValue"> The value of the "this" keyword inside a getter. </param> /// <returns> The value of the property, or <c>null</c> if the property doesn't exist. </returns> /// <remarks> The prototype chain is searched if the property does not exist directly on /// this object. </remarks> public override object GetPropertyValue(object key, object thisValue) { // Check for revocation. if (target == null || handler == null) { throw new JavaScriptException(ErrorType.TypeError, "Cannot call 'get' on a proxy that has been revoked."); } // Call the handler, if one exists. var trap = handler.GetMethod("get"); if (trap == null) { return(target.GetPropertyValue(key, thisValue)); } var result = trap.CallLateBound(handler, target, key, thisValue); // Validate. var targetDescriptor = target.GetOwnPropertyDescriptor(key); if (targetDescriptor.Exists && !targetDescriptor.IsConfigurable) { if (!targetDescriptor.IsAccessor && !targetDescriptor.IsWritable && !TypeComparer.SameValue(result, targetDescriptor.Value)) { throw new JavaScriptException(ErrorType.TypeError, $"'get' on proxy: property '{TypeConverter.ToString(key)}' is a read-only and non-configurable data property on the proxy target but the proxy did not return its actual value (expected '{TypeConverter.ToString(targetDescriptor.Value)}' but got '{TypeConverter.ToString(result)}')."); } if (targetDescriptor.IsAccessor && targetDescriptor.Getter == null && result != null && result != Undefined.Value) { throw new JavaScriptException(ErrorType.TypeError, $"'get' on proxy: property '{TypeConverter.ToString(key)}' is a non-configurable accessor property on the proxy target and does not have a getter function, but the trap did not return 'undefined' (got '{TypeConverter.ToString(result)}')."); } } return(result); }
public void SameValue() { // undefined Assert.AreEqual(true, TypeComparer.SameValue(Undefined.Value, Undefined.Value)); Assert.AreEqual(false, TypeComparer.SameValue(Undefined.Value, Null.Value)); Assert.AreEqual(false, TypeComparer.SameValue(Undefined.Value, 0)); Assert.AreEqual(true, TypeComparer.SameValue(null, null)); Assert.AreEqual(true, TypeComparer.SameValue(null, Undefined.Value)); Assert.AreEqual(false, TypeComparer.SameValue(null, Null.Value)); Assert.AreEqual(false, TypeComparer.SameValue(null, 0)); // null Assert.AreEqual(true, TypeComparer.SameValue(Null.Value, Null.Value)); Assert.AreEqual(false, TypeComparer.SameValue(Null.Value, Undefined.Value)); Assert.AreEqual(false, TypeComparer.SameValue(Null.Value, 0)); // number Assert.AreEqual(true, TypeComparer.SameValue(+0.0, +0.0)); Assert.AreEqual(true, TypeComparer.SameValue(-0.0, -0.0)); Assert.AreEqual(false, TypeComparer.SameValue(+0.0, -0.0)); Assert.AreEqual(false, TypeComparer.SameValue(-0.0, +0.0)); Assert.AreEqual(true, TypeComparer.SameValue(1, 1)); Assert.AreEqual(false, TypeComparer.SameValue(0, 1)); Assert.AreEqual(true, TypeComparer.SameValue(5, 5.0)); Assert.AreEqual(true, TypeComparer.SameValue(5.0, 5)); Assert.AreEqual(true, TypeComparer.SameValue(5.0, 5.0)); Assert.AreEqual(false, TypeComparer.SameValue(5.0, 6.0)); Assert.AreEqual(true, TypeComparer.SameValue(double.NaN, double.NaN)); Assert.AreEqual(false, TypeComparer.SameValue(double.NaN, 5)); Assert.AreEqual(false, TypeComparer.SameValue(double.NaN, 5.0)); Assert.AreEqual(false, TypeComparer.SameValue(0, "0")); // string Assert.AreEqual(true, TypeComparer.SameValue("", "")); Assert.AreEqual(true, TypeComparer.SameValue("a", "a")); Assert.AreEqual(false, TypeComparer.SameValue("a", "b")); Assert.AreEqual(false, TypeComparer.SameValue("0", 0)); // bool Assert.AreEqual(true, TypeComparer.SameValue(false, false)); Assert.AreEqual(true, TypeComparer.SameValue(true, true)); Assert.AreEqual(false, TypeComparer.SameValue(true, false)); Assert.AreEqual(false, TypeComparer.SameValue(false, 0)); // object var engine = new ScriptEngine(); var temp1 = engine.Object.Construct(); var temp2 = engine.Object.Construct(); var number1 = engine.Number.Construct(5.0); Assert.AreEqual(true, TypeComparer.SameValue(temp1, temp1)); Assert.AreEqual(false, TypeComparer.SameValue(temp1, temp2)); Assert.AreEqual(true, TypeComparer.SameValue(number1, number1)); Assert.AreEqual(false, TypeComparer.SameValue(number1, 5.0)); }
/// <summary> /// Sets the value of the property with the given name. If a property with the given name /// does not exist, or exists in the prototype chain (and is not a setter) then a new /// property is created. /// </summary> /// <param name="key"> The property key of the property to set. </param> /// <param name="value"> The value to set the property to. This must be a javascript /// primitive (double, string, etc) or a class derived from <see cref="ObjectInstance"/>. </param> /// <param name="thisValue"> The value of the "this" keyword inside a setter. </param> /// <param name="throwOnError"> <c>true</c> to throw an exception if the property could not /// be set (i.e. if the property is read-only or if the object is not extensible and a new /// property needs to be created). </param> /// <returns> <c>false</c> if <paramref name="throwOnError"/> is false and an error /// occurred; <c>true</c> otherwise. </returns> public override bool SetPropertyValue(object key, object value, object thisValue, bool throwOnError) { // Check for revocation. if (target == null || handler == null) { throw new JavaScriptException(ErrorType.TypeError, "Cannot call 'set' on a proxy that has been revoked."); } // Call the handler, if one exists. var trap = handler.GetMethod("set"); if (trap == null) { return(target.SetPropertyValue(key, value, thisValue, throwOnError)); } var result = TypeConverter.ToBoolean(trap.CallLateBound(handler, target, key, value, thisValue)); if (!result) { return(false); } // Validate. var targetDescriptor = target.GetOwnPropertyDescriptor(key); if (targetDescriptor.Exists && !targetDescriptor.IsConfigurable) { if (!targetDescriptor.IsAccessor && !targetDescriptor.IsWritable && !TypeComparer.SameValue(value, targetDescriptor.Value)) { throw new JavaScriptException(ErrorType.TypeError, $"'set' on proxy: trap returned truish for property '{TypeConverter.ToString(key)}' which exists in the proxy target as a non-configurable and non-writable data property with a different value."); } if (targetDescriptor.IsAccessor && targetDescriptor.Setter == null) { throw new JavaScriptException(ErrorType.TypeError, $"'set' on proxy: trap returned truish for property '{TypeConverter.ToString(key)}' which exists in the proxy target as a non-configurable and non-writable accessor property without a setter."); } } return(true); }
public static bool Is(object value1, object value2) { return(TypeComparer.SameValue(value1, value2)); }