Add support for Firefox anonymous stackTraces (#55)

Co-authored-by: Sinegovsky Ivan <ivan.sinegovsky@team.wrike.com>
diff --git a/CHANGELOG.md b/CHANGELOG.md
index d4bfa07..faf6eeb 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,7 @@
+## 1.9.4-dev
+
+* Added support for firefox anonymous stack traces
+
 ## 1.9.3
 
 * Set max SDK version to `<3.0.0`.
diff --git a/lib/src/frame.dart b/lib/src/frame.dart
index bbe5c79..53ae619 100644
--- a/lib/src/frame.dart
+++ b/lib/src/frame.dart
@@ -30,6 +30,11 @@
 final _v8EvalLocation =
     new RegExp(r'^eval at (?:\S.*?) \((.*)\)(?:, .*?:\d+:\d+)?$');
 
+// anonymous/<@http://pub.dartlang.org/stuff.js line 693 > Function:3:40
+// anonymous/<@http://pub.dartlang.org/stuff.js line 693 > eval:3:40
+final _firefoxEvalLocation =
+    new RegExp(r"(\S+)@(\S+) line (\d+) >.* (Function|eval):\d+:\d+");
+
 // .VW.call$0@http://pub.dartlang.org/stuff.dart.js:560
 // .VW.call$0("arg")@http://pub.dartlang.org/stuff.dart.js:560
 // .VW.call$0/name<@http://pub.dartlang.org/stuff.dart.js:560
@@ -205,11 +210,33 @@
   /// be retrieved.
   factory Frame.parseIE(String frame) => new Frame.parseV8(frame);
 
+  /// Parses a string representation of a Firefox 'eval' or 'function' stack frame.
+  ///
+  /// for example:
+  /// anonymous/<@http://pub.dartlang.org/stuff.js line 693 > Function:3:40
+  /// anonymous/<@http://pub.dartlang.org/stuff.js line 693 > eval:3:40
+  factory Frame._parseFirefoxEval(String frame) =>
+      _catchFormatException(frame, () {
+        final match = _firefoxEvalLocation.firstMatch(frame);
+        if (match == null) return new UnparsedFrame(frame);
+        var member = match[1].replaceAll('/<', '');
+        final uri = _uriOrPathToUri(match[2]);
+        final line = int.parse(match[3]);
+        if (member.isEmpty || member == 'anonymous') {
+          member = '<fn>';
+        }
+        return new Frame(uri, line, null, member);
+      });
+
   /// Parses a string representation of a Firefox stack frame.
   factory Frame.parseFirefox(String frame) => _catchFormatException(frame, () {
         var match = _firefoxSafariFrame.firstMatch(frame);
         if (match == null) return new UnparsedFrame(frame);
 
+        if (match[3].contains(' line ')) {
+          return Frame._parseFirefoxEval(frame);
+        }
+
         // Normally this is a URI, but in a jsshell trace it can be a path.
         var uri = _uriOrPathToUri(match[3]);
 
diff --git a/lib/src/trace.dart b/lib/src/trace.dart
index 972c33e..9baff1c 100644
--- a/lib/src/trace.dart
+++ b/lib/src/trace.dart
@@ -27,6 +27,14 @@
 /// though it is possible for the message to match this as well.
 final _v8TraceLine = new RegExp(r"    ?at ");
 
+/// A RegExp to match Firefox's eval and Function stack traces.
+/// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error/stack
+/// These stack traces looks like:
+/// anonymous/<@http://pub.dartlang.org/stuff.js line 693 > Function:3:40
+/// anonymous/<@http://pub.dartlang.org/stuff.js line 693 > eval:3:40
+final _firefoxEvalTrace =
+    new RegExp(r"@\S+ line \d+ >.* (Function|eval):\d+:\d+");
+
 /// A RegExp to match Firefox and Safari's stack traces.
 ///
 /// Firefox and Safari have very similar stack trace formats, so we use the same
@@ -120,7 +128,8 @@
       if (trace.isEmpty) return new Trace(<Frame>[]);
       if (trace.contains(_v8Trace)) return new Trace.parseV8(trace);
       if (trace.contains("\tat ")) return new Trace.parseJSCore(trace);
-      if (trace.contains(_firefoxSafariTrace)) {
+      if (trace.contains(_firefoxSafariTrace) ||
+          trace.contains(_firefoxEvalTrace)) {
         return new Trace.parseFirefox(trace);
       }
       if (trace.contains(chainGap)) return new Chain.parse(trace).toTrace();
diff --git a/pubspec.yaml b/pubspec.yaml
index 6774615..8715eae 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -7,7 +7,7 @@
 #
 # When the major version is upgraded, you *must* update that version constraint
 # in pub to stay in sync with this.
-version: 1.9.3
+version: 1.9.4-dev
 
 description: A package for manipulating stack traces and printing them readably.
 author: 'Dart Team <misc@dartlang.org>'
diff --git a/test/frame_test.dart b/test/frame_test.dart
index 42df4cd..1ec3c99 100644
--- a/test/frame_test.dart
+++ b/test/frame_test.dart
@@ -236,6 +236,52 @@
   });
 
   group('.parseFirefox/.parseSafari', () {
+    test('parses a Firefox stack trace with anonymous function', () {
+      var trace = new Trace.parse('''
+Foo._bar@http://pub.dartlang.org/stuff.js:18056:12
+anonymous/<@http://pub.dartlang.org/stuff.js line 693 > Function:3:40
+baz@http://pub.dartlang.org/buz.js:56355:55
+        ''');
+      expect(trace.frames[0].uri,
+          equals(Uri.parse("http://pub.dartlang.org/stuff.js")));
+      expect(trace.frames[0].line, equals(18056));
+      expect(trace.frames[0].column, equals(12));
+      expect(trace.frames[0].member, equals("Foo._bar"));
+      expect(trace.frames[1].uri,
+          equals(Uri.parse("http://pub.dartlang.org/stuff.js")));
+      expect(trace.frames[1].line, equals(693));
+      expect(trace.frames[1].column, isNull);
+      expect(trace.frames[1].member, equals("<fn>"));
+      expect(trace.frames[2].uri,
+          equals(Uri.parse("http://pub.dartlang.org/buz.js")));
+      expect(trace.frames[2].line, equals(56355));
+      expect(trace.frames[2].column, equals(55));
+      expect(trace.frames[2].member, equals("baz"));
+    });
+
+    test(
+        'parses a Firefox stack trace with nested evals in anonymous function correctly',
+        () {
+      var trace = new Trace.parse('''
+        Foo._bar@http://pub.dartlang.org/stuff.js:18056:12
+        anonymous@file:///C:/example.html line 7 > eval line 1 > eval:1:1
+        anonymous@file:///C:/example.html line 45 > Function:1:1 
+        ''');
+      expect(trace.frames[0].uri,
+          equals(Uri.parse("http://pub.dartlang.org/stuff.js")));
+      expect(trace.frames[0].line, equals(18056));
+      expect(trace.frames[0].column, equals(12));
+      expect(trace.frames[0].member, equals("Foo._bar"));
+      expect(trace.frames[1].uri, equals(Uri.parse("file:///C:/example.html")));
+      expect(trace.frames[1].line, equals(7));
+      expect(trace.frames[1].column, isNull);
+      expect(trace.frames[1].member, equals("<fn>"));
+      expect(trace.frames[2].uri, equals(Uri.parse("file:///C:/example.html")));
+      expect(trace.frames[2].line, equals(45));
+      expect(trace.frames[2].column, isNull);
+      expect(trace.frames[2].member, equals("<fn>"));
+    });
+
     test('parses a simple stack frame correctly', () {
       var frame = new Frame.parseFirefox(
           ".VW.call\$0@http://pub.dartlang.org/stuff.dart.js:560");