소스 검색

Allow JavaScriptException to be subclassed (#1093)

Co-authored-by: Tim Cassidy <[email protected]>
Co-authored-by: Marko Lahma <[email protected]>
source-transformer 3 년 전
부모
커밋
ea62f4e0fa

+ 93 - 2
Jint.Tests/Runtime/ErrorTests.cs

@@ -24,6 +24,7 @@ var b = a.user.name;
             Assert.Equal(4, e.Location.Start.Line);
             Assert.Equal(15, e.Location.Start.Column);
         }
+
         [Fact]
         public void CanReturnCorrectErrorMessageAndLocation1WithoutReferencedName()
         {
@@ -198,7 +199,7 @@ var b = function(v) {
             engine.SetValue("folder", folder);
 
             var javaScriptException = Assert.Throws<JavaScriptException>(() =>
-           engine.Execute(@"
+                engine.Execute(@"
                 var Test = {
                     recursive: function(folderInstance) {
                         // Enabling the guard here corrects the problem, but hides the hard fault
@@ -210,7 +211,7 @@ var b = function(v) {
                 }
 
                 Test.recursive(folder);"
-           ));
+                ));
 
             Assert.Equal("Cannot read property 'Name' of null", javaScriptException.Message);
             EqualIgnoringNewLineDifferences(@"   at recursive (folderInstance) <anonymous>:6:44
@@ -325,5 +326,95 @@ var x = b(7);";
             actualString = actualString.Replace("\r\n", "\n");
             Assert.Contains(expectedSubstring, actualString);
         }
+
+        [Fact]
+        public void CustomException()
+        {
+            var engine = new Engine();
+            const string filename = "someFile.js";
+            JintJsException jsException = Assert.Throws<JintJsException>(() =>
+            {
+                try
+                {
+                    const string script = @"
+                        var test = 42; // just adding a line for a non zero line offset
+                        throw new Error('blah');
+                    ";
+
+                    engine.Execute(script);
+                }
+                catch (JavaScriptException ex)
+                {
+                    throw new JintJsException(filename, ex);
+                }
+            });
+
+            Assert.Equal(24, jsException.Column);
+            Assert.Equal(3, jsException.LineNumber);
+            Assert.Equal(filename, jsException.Module);
+        }
+
+        [Fact]
+        public void CustomExceptionUsesCopyConstructor()
+        {
+            var engine = new Engine();
+            const string filename = "someFile.js";
+            JintJsException2 jsException = Assert.Throws<JintJsException2>(() =>
+            {
+                try
+                {
+                    const string script = @"
+                        var test = 42; // just adding a line for a non zero line offset
+                        throw new Error('blah');
+                    ";
+
+                    engine.Execute(script);
+                }
+                catch (JavaScriptException ex)
+                {
+                    throw new JintJsException2(filename, ex);
+                }
+            });
+
+            Assert.Equal(24, jsException.Column);
+            Assert.Equal(3, jsException.LineNumber);
+            Assert.Equal(filename, jsException.Module);
+        }
+    }
+
+    public class JintJsException : JavaScriptException
+    {
+        private readonly JavaScriptException _jsException;
+
+        public JintJsException(string moduleName, JavaScriptException jsException) : base(jsException.Error)
+        {
+            Module = moduleName;
+            _jsException = jsException;
+            Location = jsException.Location;
+        }
+
+        public string Module { get; }
+        
+        public override string Message
+        {
+            get
+            {
+                var scriptFilename = (Module != null) ? "Filepath: " + Module + " " : "";
+                var errorMsg = $"{scriptFilename}{_jsException.Message}";
+                return errorMsg;
+            }
+        }
+
+        public override string StackTrace => _jsException.StackTrace;
+    }
+
+    public class JintJsException2 : JavaScriptException
+    {
+        public JintJsException2(string moduleName, JavaScriptException jsException) : base(jsException)
+        {
+            Module = moduleName;
+        }
+
+        public string Module { get; }
     }
 }

+ 62 - 2
Jint.Tests/Runtime/ExtensionMethods/ExtensionMethodsTest.cs

@@ -1,8 +1,11 @@
-using Jint.Native;
+using Jint.Native;
 using Jint.Tests.Runtime.Domain;
 using System.Collections.Generic;
 using System.Linq;
 using Xunit;
+using Xunit.Abstractions;
+using System.IO;
+using System.Text;
 
 namespace Jint.Tests.Runtime.ExtensionMethods
 {
@@ -156,5 +159,62 @@ namespace Jint.Tests.Runtime.ExtensionMethods
             // Thus, the following script will not work as expected.
             // stringList.Select((x, i) => x + i).ToArray().join()
         }
+
+        [Fact]
+        public void GenericTypeExtension()
+        {
+            var options = new Options();
+            options.AddExtensionMethods(typeof(ObservableExtensions));
+
+            var engine = new Engine(options);
+
+            engine.SetValue("log", new System.Action<object>(System.Console.WriteLine));
+
+            NameObservable observable = new NameObservable();
+
+            engine.SetValue("observable", observable);
+            engine.Evaluate(@"
+                log('before');
+                observable.Subscribe((name) =>{
+                    log('observable: subscribe: name: ' + name);
+                });
+
+                observable.UpdateName('foobar');
+                log('after');
+            ");
+
+            Assert.Equal("foobar", observable.Last);
+        }
+
+
+        private class Converter : TextWriter
+        {
+            ITestOutputHelper _output;
+
+            public Converter(ITestOutputHelper output)
+            {
+                _output = output;
+            }
+
+            public override Encoding Encoding
+            {
+                get { return Encoding.ASCII; }
+            }
+
+            public override void WriteLine(string message)
+            {
+                _output.WriteLine(message);
+            }
+
+            public override void WriteLine(string format, params object[] args)
+            {
+                _output.WriteLine(format, args);
+            }
+
+            public override void Write(char value)
+            {
+                throw new System.Exception("This text writer only supports WriteLine(string) and WriteLine(string, params object[]).");
+            }
+        }
     }
-}
+}

+ 100 - 0
Jint.Tests/Runtime/ExtensionMethods/ObservableExtensions.cs

@@ -0,0 +1,100 @@
+using System;
+using System.Collections.Generic;
+
+namespace Jint.Tests.Runtime.ExtensionMethods
+{
+    internal class Subscribe<T> : IObserver<T>
+    {
+        private readonly Action<T> onNext;
+        private readonly Action<Exception> onError;
+        private readonly Action onCompleted;
+
+        int isStopped = 0;
+
+        public Subscribe(Action<T> onNext, Action<Exception> onError, Action onCompleted)
+        {
+            this.onNext = onNext;
+            this.onError = onError;
+            this.onCompleted = onCompleted;
+        }
+
+        public void OnNext(T value)
+        {
+            if (isStopped == 0)
+            {
+                onNext(value);
+            }
+        }
+
+        public void OnError(Exception error)
+        {
+            onError(error);
+        }
+
+
+        public void OnCompleted()
+        {
+            onCompleted();
+        }
+    }
+
+    public static partial class ObservableExtensions
+    {
+        public static void Subscribe<T>(this IObservable<T> source, Action<T> onNext)
+        {
+            var subs = new Subscribe<T>(onNext, null, null);
+            source.Subscribe(subs);
+        }
+    }
+
+    public class NameObservable : IObservable<string>
+    {
+        private List<IObserver<string>> observers = new List<IObserver<string>>();
+
+        public string Last { get; private set; }
+
+        public IDisposable Subscribe(IObserver<string> observer)
+        {
+            if (!observers.Contains(observer))
+                observers.Add(observer);
+            return new Unsubscriber(observers, observer);
+        }
+
+        private class Unsubscriber : IDisposable
+        {
+            private List<IObserver<string>> _observers;
+            private IObserver<string> _observer;
+
+            public Unsubscriber(List<IObserver<string>> observers, IObserver<string> observer)
+            {
+                this._observers = observers;
+                this._observer = observer;
+            }
+
+            public void Dispose()
+            {
+                if (_observer != null && _observers.Contains(_observer))
+                    _observers.Remove(_observer);
+            }
+        }
+
+        public void UpdateName(string name)
+        {
+            Last = name;
+            foreach (var observer in observers)
+            {
+                observer.OnNext(name);
+            }
+        }
+
+        public void CommitName()
+        {
+            foreach (var observer in observers.ToArray())
+            {
+                observer.OnCompleted();
+            }
+
+            observers.Clear();
+        }
+    }
+}

+ 14 - 0
Jint/Runtime/Interop/TypeResolver.cs

@@ -280,6 +280,20 @@ namespace Jint.Runtime.Interop
                 }
             }
 
+            // TPC: need to grab the extension methods here - for overloads
+            MethodInfo[] extensionMethods;
+            if (engine._extensionMethods.TryGetExtensionMethods(type, out extensionMethods))
+            {
+                foreach (var methodInfo in extensionMethods)
+                {
+                    if (memberNameComparer.Equals(methodInfo.Name, memberName))
+                    {
+                        methods ??= new List<MethodInfo>();
+                        methods.Add(methodInfo);
+                    }
+                }
+            }
+
             if (methods?.Count > 0)
             {
                 accessor = new MethodAccessor(MethodDescriptor.Build(methods));

+ 22 - 7
Jint/Runtime/JavaScriptException.cs

@@ -1,4 +1,4 @@
-#nullable enable
+#nullable enable
 
 using System;
 using Esprima;
@@ -19,7 +19,7 @@ namespace Jint.Runtime
         }
 
         public JavaScriptException(ErrorConstructor errorConstructor, string? message, Exception? innerException)
-             : base(message, innerException)
+            : base(message, innerException)
         {
             Error = errorConstructor.Construct(new JsValue[] { message });
         }
@@ -35,6 +35,19 @@ namespace Jint.Runtime
             Error = error;
         }
 
+        // Copy constructors
+        public JavaScriptException(JavaScriptException exception, Exception? innerException) : base(exception.Message, exception.InnerException)
+        {
+            Error = exception.Error;
+            Location = exception.Location;
+        }
+
+        public JavaScriptException(JavaScriptException exception) : base(exception.Message)
+        {
+            Error = exception.Error;
+            Location = exception.Location;
+        }
+
         internal JavaScriptException SetLocation(Location location)
         {
             Location = location;
@@ -91,12 +104,12 @@ namespace Jint.Runtime
                 var callstack = oi.Get(CommonProperties.Stack, Error);
 
                 return callstack.IsUndefined()
-                    ? null 
+                    ? null
                     : callstack.AsString();
             }
         }
 
-        public Location Location { get; private set; }
+        public Location Location { get; protected set; }
 
         public int LineNumber => Location.Start.Line;
 
@@ -110,7 +123,7 @@ namespace Jint.Runtime
             var innerExceptionString = InnerException?.ToString() ?? "";
             const string endOfInnerExceptionResource = "--- End of inner exception stack trace ---";
             var stackTrace = StackTrace;
- 
+
             using var rent = StringBuilderPool.Rent();
             var sb = rent.Builder;
             sb.Append(className);
@@ -119,6 +132,7 @@ namespace Jint.Runtime
                 sb.Append(": ");
                 sb.Append(message);
             }
+
             if (InnerException != null)
             {
                 sb.Append(Environment.NewLine);
@@ -128,13 +142,14 @@ namespace Jint.Runtime
                 sb.Append("   ");
                 sb.Append(endOfInnerExceptionResource);
             }
+
             if (stackTrace != null)
             {
                 sb.Append(Environment.NewLine);
                 sb.Append(stackTrace);
             }
- 
+
             return rent.ToString();
         }
     }
-}
+}