using System.Diagnostics.CodeAnalysis;
using Jint.Native;
using Jint.Native.Object;
using Jint.Native.Symbol;
using Jint.Runtime.Descriptors;
namespace Jint.Runtime.Environments;
///
/// Represents an object environment record
/// https://tc39.es/ecma262/#sec-object-environment-records
///
internal sealed class ObjectEnvironment : Environment
{
internal readonly ObjectInstance _bindingObject;
private readonly bool _provideThis;
private readonly bool _withEnvironment;
public ObjectEnvironment(
Engine engine,
ObjectInstance bindingObject,
bool provideThis,
bool withEnvironment) : base(engine)
{
_bindingObject = bindingObject;
_provideThis = provideThis;
_withEnvironment = withEnvironment;
}
internal override bool HasBinding(Key name)
{
var property = new JsString(name.Name);
var foundBinding = HasProperty(property);
if (!foundBinding)
{
return false;
}
if (!_withEnvironment)
{
return true;
}
return !IsBlocked(property);
}
internal override bool HasBinding(BindingName name)
{
var foundBinding = HasProperty(name.Value);
if (!foundBinding)
{
return false;
}
if (!_withEnvironment)
{
return true;
}
return !IsBlocked(name.Value);
}
private bool HasProperty(JsValue property)
{
return _bindingObject.HasProperty(property);
}
internal override bool TryGetBinding(BindingName name, [NotNullWhen(true)] out JsValue? value)
{
// we unwrap by name
if (!HasProperty(name.Value))
{
value = default;
return false;
}
if (_withEnvironment && IsBlocked(name.Value))
{
value = default;
return false;
}
value = _bindingObject.Get(name.Value);
return true;
}
private bool IsBlocked(JsValue property)
{
var unscopables = _bindingObject.Get(GlobalSymbolRegistry.Unscopables);
if (unscopables is ObjectInstance oi)
{
var blocked = TypeConverter.ToBoolean(oi.Get(property));
if (blocked)
{
return true;
}
}
return false;
}
///
/// http://www.ecma-international.org/ecma-262/6.0/#sec-object-environment-records-createmutablebinding-n-d
///
internal override void CreateMutableBinding(Key name, bool canBeDeleted = false)
{
_bindingObject.DefinePropertyOrThrow(name.Name, new PropertyDescriptor(Undefined, canBeDeleted
? PropertyFlag.ConfigurableEnumerableWritable | PropertyFlag.MutableBinding
: PropertyFlag.NonConfigurable | PropertyFlag.MutableBinding));
}
///
/// https://tc39.es/ecma262/#sec-object-environment-records-createimmutablebinding-n-s
///
internal override void CreateImmutableBinding(Key name, bool strict = true)
{
ExceptionHelper.ThrowInvalidOperationException("The concrete Environment Record method CreateImmutableBinding is never used within this specification in association with Object Environment Records.");
}
///
/// https://tc39.es/ecma262/#sec-object-environment-records-initializebinding-n-v
///
internal override void InitializeBinding(Key name, JsValue value) => SetMutableBinding(name, value, strict: false);
internal override void SetMutableBinding(Key name, JsValue value, bool strict)
{
var jsString = new JsString(name);
if (strict && !_bindingObject.HasProperty(jsString))
{
ExceptionHelper.ThrowReferenceNameError(_engine.Realm, name);
}
_bindingObject.Set(jsString, value);
}
internal override void SetMutableBinding(BindingName name, JsValue value, bool strict)
{
if (strict && !_bindingObject.HasProperty(name.Value))
{
ExceptionHelper.ThrowReferenceNameError(_engine.Realm, name.Key);
}
_bindingObject.Set(name.Value, value);
}
internal override JsValue GetBindingValue(Key name, bool strict)
{
if (!_bindingObject.TryGetValue(name.Name, out var value) && strict)
{
ExceptionHelper.ThrowReferenceNameError(_engine.Realm, name.Name);
}
return value;
}
internal override bool DeleteBinding(Key name) => _bindingObject.Delete(name.Name);
internal override bool HasThisBinding() => false;
internal override bool HasSuperBinding() => false;
internal override JsValue WithBaseObject() => _withEnvironment ? _bindingObject : Undefined;
internal override bool HasBindings() => _bindingObject._properties?.Count > 0;
internal override string[] GetAllBindingNames()
{
if (_bindingObject is not null)
{
var names = new List(_bindingObject._properties?.Count ?? 0);
foreach (var name in _bindingObject.GetOwnProperties())
{
names.Add(name.Key.ToString());
}
return names.ToArray();
}
return Array.Empty();
}
public override bool Equals(JsValue? other)
{
return ReferenceEquals(_bindingObject, other);
}
internal override JsValue GetThisBinding()
{
throw new NotImplementedException();
}
}