Browse Source

Create DefaultReferenceResolver (#752)

Marko Lahma 5 years ago
parent
commit
5c8a57e770

+ 2 - 4
Jint/Engine.cs

@@ -452,8 +452,7 @@ namespace Jint
 
 
             if (baseValue._type == InternalTypes.Undefined)
             if (baseValue._type == InternalTypes.Undefined)
             {
             {
-                if (_referenceResolver != null &&
-                    _referenceResolver.TryUnresolvableReference(this, reference, out JsValue val))
+                if (_referenceResolver.TryUnresolvableReference(this, reference, out JsValue val))
                 {
                 {
                     return val;
                     return val;
                 }
                 }
@@ -461,8 +460,7 @@ namespace Jint
                 ExceptionHelper.ThrowReferenceError(this, reference);
                 ExceptionHelper.ThrowReferenceError(this, reference);
             }
             }
 
 
-            if (_referenceResolver != null
-                && (baseValue._type & InternalTypes.ObjectEnvironmentRecord) == 0
+            if ((baseValue._type & InternalTypes.ObjectEnvironmentRecord) == 0
                 && _referenceResolver.TryPropertyReference(this, reference, ref baseValue))
                 && _referenceResolver.TryPropertyReference(this, reference, ref baseValue))
             {
             {
                 return baseValue;
                 return baseValue;

+ 33 - 1
Jint/Options.cs

@@ -6,6 +6,7 @@ using System.Reflection;
 using Jint.Native;
 using Jint.Native;
 using Jint.Native.Object;
 using Jint.Native.Object;
 using Jint.Runtime.Interop;
 using Jint.Runtime.Interop;
+using Jint.Runtime.References;
 
 
 namespace Jint
 namespace Jint
 {
 {
@@ -25,7 +26,7 @@ namespace Jint
         private TimeZoneInfo _localTimeZone = TimeZoneInfo.Local;
         private TimeZoneInfo _localTimeZone = TimeZoneInfo.Local;
         private List<Assembly> _lookupAssemblies = new List<Assembly>();
         private List<Assembly> _lookupAssemblies = new List<Assembly>();
         private Predicate<Exception> _clrExceptionsHandler;
         private Predicate<Exception> _clrExceptionsHandler;
-        private IReferenceResolver _referenceResolver;
+        private IReferenceResolver _referenceResolver = DefaultReferenceResolver.Instance;
 
 
         /// <summary>
         /// <summary>
         /// When called, doesn't initialize the global scope.
         /// When called, doesn't initialize the global scope.
@@ -212,5 +213,36 @@ namespace Jint
         internal TimeZoneInfo _LocalTimeZone => _localTimeZone;
         internal TimeZoneInfo _LocalTimeZone => _localTimeZone;
 
 
         internal IReferenceResolver  ReferenceResolver => _referenceResolver;
         internal IReferenceResolver  ReferenceResolver => _referenceResolver;
+        
+        private sealed class DefaultReferenceResolver : IReferenceResolver
+        {
+            public static readonly DefaultReferenceResolver Instance = new DefaultReferenceResolver();
+            
+            private DefaultReferenceResolver()
+            {
+            }
+
+            public bool TryUnresolvableReference(Engine engine, Reference reference, out JsValue value)
+            {
+                value = JsValue.Undefined;
+                return false;
+            }
+
+            public bool TryPropertyReference(Engine engine, Reference reference, ref JsValue value)
+            {
+                return false;
+            }
+
+            public bool TryGetCallable(Engine engine, object callee, out JsValue value)
+            {
+                value = JsValue.Undefined;
+                return false;
+            }
+
+            public bool CheckCoercible(JsValue value)
+            {
+                return false;
+            }
+        }
     }
     }
 }
 }

+ 42 - 0
Jint/Runtime/Interop/IReferenceResolver.cs

@@ -3,11 +3,53 @@ using Jint.Runtime.References;
 
 
 namespace Jint.Runtime.Interop
 namespace Jint.Runtime.Interop
 {
 {
+    /// <summary>
+    /// Reference resolver allows customizing behavior for reference resolving. This can be useful in cases where
+    /// you want to ignore long chain of property accesses that might throw if anything is null or undefined.
+    /// An example of such is <code>var a = obj.field.subField.value</code>. Custom resolver could accept chain to return
+    /// null/undefined on first occurrence. 
+    /// </summary>
     public interface IReferenceResolver
     public interface IReferenceResolver
     {
     {
+        /// <summary>
+        /// When unresolvable reference occurs, check if another value can be provided instead of it. 
+        /// </summary>
+        /// <remarks>
+        /// A reference error will be thrown if this method return false.
+        /// </remarks>
+        /// <param name="engine">The current engine instance.</param>
+        /// <param name="reference">The reference that is being processed.</param>
+        /// <param name="value">Value that should be used instead of undefined.</param>
+        /// <returns>Whether to use <paramref name="value" /> instead of undefined.</returns>
         bool TryUnresolvableReference(Engine engine, Reference reference, out JsValue value);
         bool TryUnresolvableReference(Engine engine, Reference reference, out JsValue value);
+
+        /// <summary>
+        /// When property reference is being processed, resolve to other value if needed.  
+        /// </summary>
+        /// <param name="engine">The current engine instance.</param>
+        /// <param name="reference">The reference that is being processed.</param>
+        /// <param name="value">Value that should be used instead of reference target.</param>
+        /// <returns>Whether to use <paramref name="value" /> instead of reference's value.</returns>
         bool TryPropertyReference(Engine engine, Reference reference, ref JsValue value);
         bool TryPropertyReference(Engine engine, Reference reference, ref JsValue value);
+        
+        /// <summary>
+        /// When evaluating a function call and a target that is not an object is encountered,
+        /// custom implementation can return a value to call. 
+        /// </summary>
+        /// <remarks>
+        /// A reference error will be thrown if this method return false.
+        /// </remarks>
+        /// <param name="engine">The current engine instance.</param>
+        /// <param name="callee">The callee.</param>
+        /// <param name="value">Value that should be used when this method return true. Should be <see cref="ICallable"/>.</param>
+        /// <returns>Whether to use <paramref name="value" /> instead of undefined.</returns>
         bool TryGetCallable(Engine engine, object callee, out JsValue value);
         bool TryGetCallable(Engine engine, object callee, out JsValue value);
+        
+        /// <summary>
+        /// Check whether objects property value is valid.
+        /// </summary>
+        /// <param name="value">The value to check</param>
+        /// <returns>Whether to accept the value.</returns>
         bool CheckCoercible(JsValue value);
         bool CheckCoercible(JsValue value);
     }
     }
 }
 }

+ 2 - 3
Jint/Runtime/Interpreter/Expressions/JintCallExpression.cs

@@ -129,10 +129,9 @@ namespace Jint.Runtime.Interpreter.Expressions
 
 
             if (!func.IsObject())
             if (!func.IsObject())
             {
             {
-                if (_engine._referenceResolver == null || !_engine._referenceResolver.TryGetCallable(_engine, callee, out func))
+                if (!_engine._referenceResolver.TryGetCallable(_engine, callee, out func))
                 {
                 {
-                    ExceptionHelper.ThrowTypeError(_engine,
-                        r == null ? "" : $"Property '{r.GetReferencedName()}' of object is not a function");
+                    ExceptionHelper.ThrowTypeError(_engine, r == null ? "" : $"Property '{r.GetReferencedName()}' of object is not a function");
                 }
                 }
             }
             }
 
 

+ 1 - 1
Jint/Runtime/TypeConverter.cs

@@ -508,7 +508,7 @@ namespace Jint.Runtime
             MemberExpression expression,
             MemberExpression expression,
             string referenceName)
             string referenceName)
         {
         {
-            if (o._type < InternalTypes.Boolean && (engine.Options.ReferenceResolver?.CheckCoercible(o)).GetValueOrDefault() != true)
+            if (o._type < InternalTypes.Boolean && !engine.Options.ReferenceResolver.CheckCoercible(o))
             {
             {
                 ThrowTypeError(engine, o, expression, referenceName);
                 ThrowTypeError(engine, o, expression, referenceName);
             }
             }