[go_router]refactor runtime check to assert (#1362)

diff --git a/packages/go_router/CHANGELOG.md b/packages/go_router/CHANGELOG.md
index b7e3c52..f1481a9 100644
--- a/packages/go_router/CHANGELOG.md
+++ b/packages/go_router/CHANGELOG.md
@@ -1,3 +1,7 @@
+## 3.0.7
+
+- Refactors runtime checks to assertions.
+
 ## 3.0.6
 
 - Exports inherited_go_router.dart file.
diff --git a/packages/go_router/lib/src/go_route.dart b/packages/go_router/lib/src/go_route.dart
index dbe5882..a50831a 100644
--- a/packages/go_router/lib/src/go_route.dart
+++ b/packages/go_router/lib/src/go_route.dart
@@ -21,51 +21,38 @@
     this.builder = _invalidBuilder,
     this.routes = const <GoRoute>[],
     this.redirect = _noRedirection,
-  }) {
-    if (path.isEmpty) {
-      throw Exception('GoRoute path cannot be empty');
-    }
-
-    if (name != null && name!.isEmpty) {
-      throw Exception('GoRoute name cannot be empty');
-    }
-
-    if (pageBuilder == null &&
-        builder == _invalidBuilder &&
-        redirect == _noRedirection) {
-      throw Exception(
-        'GoRoute builder parameter not set\n'
-        'See gorouter.dev/redirection#considerations for details',
-      );
-    }
-
+  })  : assert(path.isNotEmpty, 'GoRoute path cannot be empty'),
+        assert(name == null || name.isNotEmpty, 'GoRoute name cannot be empty'),
+        assert(
+            pageBuilder != null ||
+                builder != _invalidBuilder ||
+                redirect != _noRedirection,
+            'GoRoute builder parameter not set\nSee gorouter.dev/redirection#considerations for details') {
     // cache the path regexp and parameters
     _pathRE = patternToRegExp(path, _pathParams);
 
-    // check path params
-    final Map<String, List<String>> groupedParams =
-        _pathParams.groupListsBy<String>((String p) => p);
-    final Map<String, List<String>> dupParams =
-        Map<String, List<String>>.fromEntries(
-      groupedParams.entries
-          .where((MapEntry<String, List<String>> e) => e.value.length > 1),
-    );
-    if (dupParams.isNotEmpty) {
-      throw Exception(
-        'duplicate path params: ${dupParams.keys.join(', ')}',
+    assert(() {
+      // check path params
+      final Map<String, List<String>> groupedParams =
+          _pathParams.groupListsBy<String>((String p) => p);
+      final Map<String, List<String>> dupParams =
+          Map<String, List<String>>.fromEntries(
+        groupedParams.entries
+            .where((MapEntry<String, List<String>> e) => e.value.length > 1),
       );
-    }
+      assert(dupParams.isEmpty,
+          'duplicate path params: ${dupParams.keys.join(', ')}');
 
-    // check sub-routes
-    for (final GoRoute route in routes) {
-      // check paths
-      if (route.path != '/' &&
-          (route.path.startsWith('/') || route.path.endsWith('/'))) {
-        throw Exception(
-          'sub-route path may not start or end with /: ${route.path}',
-        );
+      // check sub-routes
+      for (final GoRoute route in routes) {
+        // check paths
+        assert(
+            route.path == '/' ||
+                (!route.path.startsWith('/') && !route.path.endsWith('/')),
+            'sub-route path may not start or end with /: ${route.path}');
       }
-    }
+      return true;
+    }());
   }
 
   final List<String> _pathParams = <String>[];
diff --git a/packages/go_router/lib/src/go_route_match.dart b/packages/go_router/lib/src/go_route_match.dart
index e489043..345aa5d 100644
--- a/packages/go_router/lib/src/go_route_match.dart
+++ b/packages/go_router/lib/src/go_route_match.dart
@@ -25,14 +25,14 @@
   })  : assert(subloc.startsWith('/')),
         assert(Uri.parse(subloc).queryParameters.isEmpty),
         assert(fullpath.startsWith('/')),
-        assert(Uri.parse(fullpath).queryParameters.isEmpty) {
-    if (kDebugMode) {
-      for (final MapEntry<String, String> p in encodedParams.entries) {
-        assert(p.value == Uri.encodeComponent(Uri.decodeComponent(p.value)),
-            'encodedParams[${p.key}] is not encoded properly: "${p.value}"');
-      }
-    }
-  }
+        assert(Uri.parse(fullpath).queryParameters.isEmpty),
+        assert(() {
+          for (final MapEntry<String, String> p in encodedParams.entries) {
+            assert(p.value == Uri.encodeComponent(Uri.decodeComponent(p.value)),
+                'encodedParams[${p.key}] is not encoded properly: "${p.value}"');
+          }
+          return true;
+        }());
 
   // ignore: public_member_api_docs
   factory GoRouteMatch.matchNamed({
@@ -45,22 +45,21 @@
   }) {
     assert(route.name != null);
     assert(route.name!.toLowerCase() == name.toLowerCase());
-
-    // check that we have all the params we need
-    final List<String> paramNames = <String>[];
-    patternToRegExp(fullpath, paramNames);
-    for (final String paramName in paramNames) {
-      if (!params.containsKey(paramName)) {
-        throw Exception('missing param "$paramName" for $fullpath');
+    assert(() {
+      // check that we have all the params we need
+      final List<String> paramNames = <String>[];
+      patternToRegExp(fullpath, paramNames);
+      for (final String paramName in paramNames) {
+        assert(params.containsKey(paramName),
+            'missing param "$paramName" for $fullpath');
       }
-    }
 
-    // check that we have don't have extra params
-    for (final String key in params.keys) {
-      if (!paramNames.contains(key)) {
-        throw Exception('unknown param "$key" for $fullpath');
+      // check that we have don't have extra params
+      for (final String key in params.keys) {
+        assert(paramNames.contains(key), 'unknown param "$key" for $fullpath');
       }
-    }
+      return true;
+    }());
 
     final Map<String, String> encodedParams = <String, String>{
       for (final MapEntry<String, String> param in params.entries)
diff --git a/packages/go_router/lib/src/go_router_delegate.dart b/packages/go_router/lib/src/go_router_delegate.dart
index 791fb77..a0cdb59 100644
--- a/packages/go_router/lib/src/go_router_delegate.dart
+++ b/packages/go_router/lib/src/go_router_delegate.dart
@@ -38,14 +38,14 @@
     required this.debugLogDiagnostics,
     required this.routerNeglect,
     this.restorationScopeId,
-  }) {
-    // check top-level route paths are valid
-    for (final GoRoute route in routes) {
-      if (!route.path.startsWith('/')) {
-        throw Exception('top-level path must start with "/": ${route.path}');
-      }
-    }
-
+  }) : assert(() {
+          // check top-level route paths are valid
+          for (final GoRoute route in routes) {
+            assert(route.path.startsWith('/'),
+                'top-level path must start with "/": ${route.path}');
+          }
+          return true;
+        }()) {
     // cache the set of named routes for fast lookup
     _cacheNamedRoutes(routes, '', _namedMatches);
 
@@ -110,10 +110,8 @@
 
       if (route.name != null) {
         final String name = route.name!.toLowerCase();
-        if (namedFullpaths.containsKey(name)) {
-          throw Exception('duplication fullpaths for name "$name":'
-              '${namedFullpaths[name]!.fullpath}, $fullpath');
-        }
+        assert(!namedFullpaths.containsKey(name),
+            'duplication fullpaths for name "$name":${namedFullpaths[name]!.fullpath}, $fullpath');
 
         // we only have a partial match until we have a location;
         // we're really only caching the route and fullpath at this point
@@ -154,12 +152,9 @@
       params: params,
       queryParams: queryParams,
     );
-    if (match == null) {
-      throw Exception('unknown route name: $name');
-    }
-
-    assert(identical(match.queryParams, queryParams));
-    return _addQueryParams(match.subloc, queryParams);
+    assert(match != null, 'unknown route name: $name');
+    assert(identical(match!.queryParams, queryParams));
+    return _addQueryParams(match!.subloc, queryParams);
   }
 
   /// Navigate to the given location.
@@ -179,12 +174,8 @@
   /// Pop the top page off the GoRouter's page stack.
   void pop() {
     _matches.remove(_matches.last);
-    if (_matches.isEmpty) {
-      throw Exception(
-        'have popped the last page off of the stack; '
-        'there are no pages left to show',
-      );
-    }
+    assert(_matches.isNotEmpty,
+        'have popped the last page off of the stack; there are no pages left to show');
     notifyListeners();
   }
 
@@ -301,29 +292,28 @@
           return false;
         }
 
-        if (Uri.tryParse(redir) == null) {
-          throw Exception('invalid redirect: $redir');
-        }
+        assert(Uri.tryParse(redir) != null, 'invalid redirect: $redir');
 
-        if (redirects.contains(redir)) {
-          redirects.add(redir);
-          final String msg =
-              'redirect loop detected: ${redirects.join(' => ')}';
-          throw Exception(msg);
-        }
+        assert(
+            !redirects.contains(redir),
+            'redirect loop detected: ${<String>[
+              ...redirects,
+              redir
+            ].join(' => ')}');
+        assert(
+            redirects.length < redirectLimit,
+            'too many redirects: ${<String>[
+              ...redirects,
+              redir
+            ].join(' => ')}');
 
         redirects.add(redir);
-        if (redirects.length - 1 > redirectLimit) {
-          final String msg = 'too many redirects: ${redirects.join(' => ')}';
-          throw Exception(msg);
-        }
-
         log.info('redirecting to $redir');
         return true;
       }
 
       // keep looping till we're done redirecting
-      for (;;) {
+      while (true) {
         final String loc = redirects.last;
 
         // check for top-level redirect
@@ -449,23 +439,20 @@
       extra: extra,
     ).toList();
 
-    if (matchStacks.isEmpty) {
-      throw Exception('no routes for location: $location');
-    }
+    assert(matchStacks.isNotEmpty, 'no routes for location: $location');
+    assert(() {
+      if (matchStacks.length > 1) {
+        final StringBuffer sb = StringBuffer()
+          ..writeln('too many routes for location: $location');
 
-    if (matchStacks.length > 1) {
-      final StringBuffer sb = StringBuffer()
-        ..writeln('too many routes for location: $location');
+        for (final List<GoRouteMatch> stack in matchStacks) {
+          sb.writeln(
+              '\t${stack.map((GoRouteMatch m) => m.route.path).join(' => ')}');
+        }
 
-      for (final List<GoRouteMatch> stack in matchStacks) {
-        sb.writeln(
-            '\t${stack.map((GoRouteMatch m) => m.route.path).join(' => ')}');
+        assert(false, sb.toString());
       }
 
-      throw Exception(sb.toString());
-    }
-
-    if (kDebugMode) {
       assert(matchStacks.length == 1);
       final GoRouteMatch match = matchStacks.first.last;
       final String loc1 = _addQueryParams(match.subloc, match.queryParams);
@@ -475,7 +462,8 @@
       // NOTE: match the lower case, since subloc is canonicalized to match the
       // path case whereas the location can be any case
       assert(loc1.toLowerCase() == loc2.toLowerCase(), '$loc1 != $loc2');
-    }
+      return true;
+    }());
 
     return matchStacks.first;
   }
diff --git a/packages/go_router/pubspec.yaml b/packages/go_router/pubspec.yaml
index 038fa10..48a8987 100644
--- a/packages/go_router/pubspec.yaml
+++ b/packages/go_router/pubspec.yaml
@@ -1,7 +1,7 @@
 name: go_router
 description: A declarative router for Flutter based on Navigation 2 supporting
   deep linking, data-driven routes and more
-version: 3.0.6
+version: 3.0.7
 repository: https://github.com/flutter/packages/tree/main/packages/go_router
 issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+go_router%22
 
diff --git a/packages/go_router/test/go_route_test.dart b/packages/go_router/test/go_route_test.dart
index 63b2912..c20d224 100644
--- a/packages/go_router/test/go_route_test.dart
+++ b/packages/go_router/test/go_route_test.dart
@@ -7,6 +7,10 @@
 
 void main() {
   test('throws when a builder is not set', () {
-    expect(() => GoRoute(path: '/'), throwsException);
+    expect(() => GoRoute(path: '/'), throwsA(isAssertionError));
+  });
+
+  test('throws when a path is empty', () {
+    expect(() => GoRoute(path: ''), throwsA(isAssertionError));
   });
 }
diff --git a/packages/go_router/test/go_router_delegate_test.dart b/packages/go_router/test/go_router_delegate_test.dart
index d577856..a715332 100644
--- a/packages/go_router/test/go_router_delegate_test.dart
+++ b/packages/go_router/test/go_router_delegate_test.dart
@@ -45,7 +45,7 @@
         () => delegate
           ..pop()
           ..pop(),
-        throwsException,
+        throwsA(isAssertionError),
       );
     });
   });
diff --git a/packages/go_router/test/go_router_test.dart b/packages/go_router/test/go_router_test.dart
index 718aa6e..bc0804e 100644
--- a/packages/go_router/test/go_router_test.dart
+++ b/packages/go_router/test/go_router_test.dart
@@ -56,7 +56,7 @@
     test('empty path', () {
       expect(() {
         GoRoute(path: '');
-      }, throwsException);
+      }, throwsA(isAssertionError));
     });
 
     test('leading / on sub-route', () {
@@ -71,7 +71,7 @@
             ),
           ],
         );
-      }, throwsException);
+      }, throwsA(isAssertionError));
     });
 
     test('trailing / on sub-route', () {
@@ -86,7 +86,7 @@
             ),
           ],
         );
-      }, throwsException);
+      }, throwsA(isAssertionError));
     });
 
     test('lack of leading / on top-level route', () {
@@ -95,7 +95,7 @@
           GoRoute(path: 'foo', builder: _dummy),
         ];
         _router(routes);
-      }, throwsException);
+      }, throwsA(isAssertionError));
     });
 
     test('match no routes', () {
@@ -460,13 +460,13 @@
 
       expect(() {
         _router(routes);
-      }, throwsException);
+      }, throwsA(isAssertionError));
     });
 
     test('empty name', () {
       expect(() {
         GoRoute(name: '', path: '/');
-      }, throwsException);
+      }, throwsA(isAssertionError));
     });
 
     test('match no routes', () {
@@ -476,7 +476,7 @@
         ];
         final GoRouter router = _router(routes);
         router.goNamed('work');
-      }, throwsException);
+      }, throwsA(isAssertionError));
     });
 
     test('match 2nd top level route', () {
@@ -612,7 +612,7 @@
       expect(() {
         final GoRouter router = _router(routes);
         router.goNamed('person', params: <String, String>{'fid': 'f2'});
-      }, throwsException);
+      }, throwsA(isAssertionError));
     });
 
     test('match case insensitive w/ params', () {
@@ -661,7 +661,7 @@
       expect(() {
         final GoRouter router = _router(routes);
         router.goNamed('family');
-      }, throwsException);
+      }, throwsA(isAssertionError));
     });
 
     test('too many params', () {
@@ -677,7 +677,7 @@
         final GoRouter router = _router(routes);
         router.goNamed('family',
             params: <String, String>{'fid': 'f2', 'pid': 'p1'});
-      }, throwsException);
+      }, throwsA(isAssertionError));
     });
 
     test('sparsely named routes', () {