[go_router_builder] Add ShellRoute support to go_router_builder (#3439)

[go_router_builder] Add ShellRoute support to go_router_builder
diff --git a/packages/go_router_builder/CHANGELOG.md b/packages/go_router_builder/CHANGELOG.md
index b1f15fb..3fcea0b 100644
--- a/packages/go_router_builder/CHANGELOG.md
+++ b/packages/go_router_builder/CHANGELOG.md
@@ -1,3 +1,7 @@
+## 1.2.0
+
+* Adds Support for ShellRoute
+
 ## 1.1.7
 
 * Supports default values for `Set`, `List` and `Iterable` route parameters.
diff --git a/packages/go_router_builder/README.md b/packages/go_router_builder/README.md
index e7d3a96..e911d33 100644
--- a/packages/go_router_builder/README.md
+++ b/packages/go_router_builder/README.md
@@ -327,3 +327,44 @@
     ),
 }
 ```
+
+## TypedShellRoute and navigator keys
+
+There may be situations were a child route of a shell needs to be displayed on a
+different navigator. This kind of scenarios can be achieved by declaring a
+**static** navigator key named:
+
+- `$navigatorKey` for ShellRoutes
+- `$parentNavigatorKey` for GoRoutes
+
+Example:
+
+```dart
+// For ShellRoutes:
+final GlobalKey<NavigatorState> shellNavigatorKey = GlobalKey<NavigatorState>();
+
+class MyShellRouteData extends ShellRouteData {
+  const MyShellRouteData();
+
+  static final GlobalKey<NavigatorState> $navigatorKey = shellNavigatorKey;
+
+  @override
+  Widget builder(BuildContext context, GoRouterState state, Widget navigator) {
+    // ...
+  }
+}
+
+// For GoRoutes:
+class MyGoRouteData extends GoRouteData {
+  const MyGoRouteData();
+
+  static final GlobalKey<NavigatorState> $parentNavigatorKey = rootNavigatorKey;
+
+  @override
+  Widget build(BuildContext context, GoRouterState state) {
+    // ...
+  }
+}
+```
+
+An example is available [here](https://github.com/flutter/packages/blob/main/packages/go_router_builder/example/lib/shell_route_with_keys_example.dart).
diff --git a/packages/go_router_builder/example/lib/all_types.g.dart b/packages/go_router_builder/example/lib/all_types.g.dart
index f38dadc..0dc37d5 100644
--- a/packages/go_router_builder/example/lib/all_types.g.dart
+++ b/packages/go_router_builder/example/lib/all_types.g.dart
@@ -8,11 +8,11 @@
 // GoRouterGenerator
 // **************************************************************************
 
-List<GoRoute> get $appRoutes => [
+List<RouteBase> get $appRoutes => [
       $allTypesBaseRoute,
     ];
 
-GoRoute get $allTypesBaseRoute => GoRouteData.$route(
+RouteBase get $allTypesBaseRoute => GoRouteData.$route(
       path: '/',
       factory: $AllTypesBaseRouteExtension._fromState,
       routes: [
diff --git a/packages/go_router_builder/example/lib/main.g.dart b/packages/go_router_builder/example/lib/main.g.dart
index ff7a236..ba15ccd 100644
--- a/packages/go_router_builder/example/lib/main.g.dart
+++ b/packages/go_router_builder/example/lib/main.g.dart
@@ -8,12 +8,12 @@
 // GoRouterGenerator
 // **************************************************************************
 
-List<GoRoute> get $appRoutes => [
+List<RouteBase> get $appRoutes => [
       $homeRoute,
       $loginRoute,
     ];
 
-GoRoute get $homeRoute => GoRouteData.$route(
+RouteBase get $homeRoute => GoRouteData.$route(
       path: '/',
       factory: $HomeRouteExtension._fromState,
       routes: [
@@ -118,7 +118,7 @@
       entries.singleWhere((element) => element.value == value).key;
 }
 
-GoRoute get $loginRoute => GoRouteData.$route(
+RouteBase get $loginRoute => GoRouteData.$route(
       path: '/login',
       factory: $LoginRouteExtension._fromState,
     );
diff --git a/packages/go_router_builder/example/lib/shell_route_example.dart b/packages/go_router_builder/example/lib/shell_route_example.dart
new file mode 100644
index 0000000..2662ba8
--- /dev/null
+++ b/packages/go_router_builder/example/lib/shell_route_example.dart
@@ -0,0 +1,153 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// ignore_for_file: public_member_api_docs
+
+import 'package:flutter/material.dart';
+import 'package:go_router/go_router.dart';
+
+part 'shell_route_example.g.dart';
+
+void main() => runApp(App());
+
+class App extends StatelessWidget {
+  App({super.key});
+
+  @override
+  Widget build(BuildContext context) => MaterialApp.router(
+        routerConfig: _router,
+      );
+
+  final GoRouter _router = GoRouter(
+    routes: $appRoutes,
+    initialLocation: '/foo',
+  );
+}
+
+class HomeScreen extends StatelessWidget {
+  const HomeScreen({super.key});
+
+  @override
+  Widget build(BuildContext context) => Scaffold(
+        appBar: AppBar(title: const Text('foo')),
+      );
+}
+
+@TypedShellRoute<MyShellRouteData>(
+  routes: <TypedRoute<RouteData>>[
+    TypedGoRoute<FooRouteData>(path: '/foo'),
+    TypedGoRoute<BarRouteData>(path: '/bar'),
+  ],
+)
+class MyShellRouteData extends ShellRouteData {
+  const MyShellRouteData();
+
+  @override
+  Widget builder(
+    BuildContext context,
+    GoRouterState state,
+    Widget navigator,
+  ) {
+    return MyShellRouteScreen(child: navigator);
+  }
+}
+
+class FooRouteData extends GoRouteData {
+  const FooRouteData();
+
+  @override
+  Widget build(BuildContext context, GoRouterState state) {
+    return const FooScreen();
+  }
+}
+
+class BarRouteData extends GoRouteData {
+  const BarRouteData();
+
+  @override
+  Widget build(BuildContext context, GoRouterState state) {
+    return const BarScreen();
+  }
+}
+
+class MyShellRouteScreen extends StatelessWidget {
+  const MyShellRouteScreen({required this.child, super.key});
+
+  final Widget child;
+
+  int getCurrentIndex(BuildContext context) {
+    final String location = GoRouter.of(context).location;
+    if (location == '/bar') {
+      return 1;
+    }
+    return 0;
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    final int currentIndex = getCurrentIndex(context);
+    return Scaffold(
+      body: child,
+      bottomNavigationBar: BottomNavigationBar(
+        currentIndex: currentIndex,
+        items: const <BottomNavigationBarItem>[
+          BottomNavigationBarItem(
+            icon: Icon(Icons.home),
+            label: 'Foo',
+          ),
+          BottomNavigationBarItem(
+            icon: Icon(Icons.business),
+            label: 'Bar',
+          ),
+        ],
+        onTap: (int index) {
+          switch (index) {
+            case 0:
+              const FooRouteData().go(context);
+              break;
+            case 1:
+              const BarRouteData().go(context);
+              break;
+          }
+        },
+      ),
+    );
+  }
+}
+
+class FooScreen extends StatelessWidget {
+  const FooScreen({super.key});
+
+  @override
+  Widget build(BuildContext context) {
+    return const Text('Foo');
+  }
+}
+
+class BarScreen extends StatelessWidget {
+  const BarScreen({super.key});
+
+  @override
+  Widget build(BuildContext context) {
+    return const Text('Bar');
+  }
+}
+
+@TypedGoRoute<LoginRoute>(path: '/login')
+class LoginRoute extends GoRouteData {
+  const LoginRoute();
+
+  @override
+  Widget build(BuildContext context, GoRouterState state) =>
+      const LoginScreen();
+}
+
+class LoginScreen extends StatelessWidget {
+  const LoginScreen({super.key});
+
+  @override
+  Widget build(BuildContext context) {
+    return const Text('Login');
+  }
+}
diff --git a/packages/go_router_builder/example/lib/shell_route_example.g.dart b/packages/go_router_builder/example/lib/shell_route_example.g.dart
new file mode 100644
index 0000000..5315025
--- /dev/null
+++ b/packages/go_router_builder/example/lib/shell_route_example.g.dart
@@ -0,0 +1,83 @@
+// GENERATED CODE - DO NOT MODIFY BY HAND
+
+// ignore_for_file: always_specify_types, public_member_api_docs
+
+part of 'shell_route_example.dart';
+
+// **************************************************************************
+// GoRouterGenerator
+// **************************************************************************
+
+List<RouteBase> get $appRoutes => [
+      $loginRoute,
+      $myShellRouteData,
+    ];
+
+RouteBase get $loginRoute => GoRouteData.$route(
+      path: '/login',
+      factory: $LoginRouteExtension._fromState,
+    );
+
+extension $LoginRouteExtension on LoginRoute {
+  static LoginRoute _fromState(GoRouterState state) => const LoginRoute();
+
+  String get location => GoRouteData.$location(
+        '/login',
+      );
+
+  void go(BuildContext context) => context.go(location);
+
+  void push(BuildContext context) => context.push(location);
+
+  void pushReplacement(BuildContext context) =>
+      context.pushReplacement(location);
+}
+
+RouteBase get $myShellRouteData => ShellRouteData.$route(
+      factory: $MyShellRouteDataExtension._fromState,
+      routes: [
+        GoRouteData.$route(
+          path: '/foo',
+          factory: $FooRouteDataExtension._fromState,
+        ),
+        GoRouteData.$route(
+          path: '/bar',
+          factory: $BarRouteDataExtension._fromState,
+        ),
+      ],
+    );
+
+extension $MyShellRouteDataExtension on MyShellRouteData {
+  static MyShellRouteData _fromState(GoRouterState state) =>
+      const MyShellRouteData();
+}
+
+extension $FooRouteDataExtension on FooRouteData {
+  static FooRouteData _fromState(GoRouterState state) => const FooRouteData();
+
+  String get location => GoRouteData.$location(
+        '/foo',
+      );
+
+  void go(BuildContext context) => context.go(location);
+
+  void push(BuildContext context) => context.push(location);
+
+  void pushReplacement(BuildContext context) =>
+      context.pushReplacement(location);
+}
+
+extension $BarRouteDataExtension on BarRouteData {
+  static BarRouteData _fromState(GoRouterState state) => const BarRouteData();
+
+  String get location => GoRouteData.$location(
+        '/bar',
+      );
+
+  void go(BuildContext context) => context.go(location);
+
+  void push(BuildContext context) => context.push(location);
+
+  void pushReplacement(BuildContext context) =>
+      context.pushReplacement(location);
+}
diff --git a/packages/go_router_builder/example/lib/shell_route_with_keys_example.dart b/packages/go_router_builder/example/lib/shell_route_with_keys_example.dart
new file mode 100644
index 0000000..bc6521e
--- /dev/null
+++ b/packages/go_router_builder/example/lib/shell_route_with_keys_example.dart
@@ -0,0 +1,169 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// ignore_for_file: public_member_api_docs
+
+import 'package:flutter/material.dart';
+import 'package:go_router/go_router.dart';
+
+part 'shell_route_with_keys_example.g.dart';
+
+void main() => runApp(App());
+
+final GlobalKey<NavigatorState> rootNavigatorKey = GlobalKey<NavigatorState>();
+final GlobalKey<NavigatorState> shellNavigatorKey = GlobalKey<NavigatorState>();
+
+class App extends StatelessWidget {
+  App({super.key});
+
+  @override
+  Widget build(BuildContext context) => MaterialApp.router(
+        routerConfig: _router,
+      );
+
+  final GoRouter _router = GoRouter(
+    routes: $appRoutes,
+    initialLocation: '/home',
+    navigatorKey: rootNavigatorKey,
+  );
+}
+
+@TypedShellRoute<MyShellRouteData>(
+  routes: <TypedRoute<RouteData>>[
+    TypedGoRoute<HomeRouteData>(path: '/home'),
+    TypedGoRoute<UsersRouteData>(
+      path: '/users',
+      routes: <TypedGoRoute<UserRouteData>>[
+        TypedGoRoute<UserRouteData>(path: ':id'),
+      ],
+    ),
+  ],
+)
+class MyShellRouteData extends ShellRouteData {
+  const MyShellRouteData();
+
+  static final GlobalKey<NavigatorState> $navigatorKey = shellNavigatorKey;
+
+  @override
+  Widget builder(BuildContext context, GoRouterState state, Widget navigator) {
+    return MyShellRouteScreen(child: navigator);
+  }
+}
+
+class MyShellRouteScreen extends StatelessWidget {
+  const MyShellRouteScreen({required this.child, super.key});
+
+  final Widget child;
+
+  int getCurrentIndex(BuildContext context) {
+    final String location = GoRouter.of(context).location;
+    if (location.startsWith('/users')) {
+      return 1;
+    }
+    return 0;
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    final int selectedIndex = getCurrentIndex(context);
+
+    return Scaffold(
+      body: Row(
+        children: <Widget>[
+          NavigationRail(
+            destinations: const <NavigationRailDestination>[
+              NavigationRailDestination(
+                icon: Icon(Icons.home),
+                label: Text('Home'),
+              ),
+              NavigationRailDestination(
+                icon: Icon(Icons.group),
+                label: Text('Users'),
+              ),
+            ],
+            selectedIndex: selectedIndex,
+            onDestinationSelected: (int index) {
+              switch (index) {
+                case 0:
+                  const HomeRouteData().go(context);
+                  break;
+                case 1:
+                  const UsersRouteData().go(context);
+                  break;
+              }
+            },
+          ),
+          const VerticalDivider(thickness: 1, width: 1),
+          Expanded(child: child),
+        ],
+      ),
+    );
+  }
+}
+
+class HomeRouteData extends GoRouteData {
+  const HomeRouteData();
+
+  @override
+  Widget build(BuildContext context, GoRouterState state) {
+    return const Center(child: Text('The home page'));
+  }
+}
+
+class UsersRouteData extends GoRouteData {
+  const UsersRouteData();
+
+  @override
+  Widget build(BuildContext context, GoRouterState state) {
+    return ListView(
+      children: <Widget>[
+        for (int userID = 1; userID <= 3; userID++)
+          ListTile(
+            title: Text('User $userID'),
+            onTap: () => UserRouteData(id: userID).go(context),
+          ),
+      ],
+    );
+  }
+}
+
+class DialogPage extends Page<void> {
+  /// A page to display a dialog.
+  const DialogPage({required this.child, super.key});
+
+  /// The widget to be displayed which is usually a [Dialog] widget.
+  final Widget child;
+
+  @override
+  Route<void> createRoute(BuildContext context) {
+    return DialogRoute<void>(
+      context: context,
+      settings: this,
+      builder: (BuildContext context) => child,
+    );
+  }
+}
+
+class UserRouteData extends GoRouteData {
+  const UserRouteData({required this.id});
+
+  // Without this static key, the dialog will not cover the navigation rail.
+  static final GlobalKey<NavigatorState> $parentNavigatorKey = rootNavigatorKey;
+
+  final int id;
+
+  @override
+  Page<void> buildPage(BuildContext context, GoRouterState state) {
+    return DialogPage(
+      key: state.pageKey,
+      child: Center(
+        child: SizedBox(
+          width: 300,
+          height: 300,
+          child: Card(child: Center(child: Text('User $id'))),
+        ),
+      ),
+    );
+  }
+}
diff --git a/packages/go_router_builder/example/lib/shell_route_with_keys_example.g.dart b/packages/go_router_builder/example/lib/shell_route_with_keys_example.g.dart
new file mode 100644
index 0000000..523af4e
--- /dev/null
+++ b/packages/go_router_builder/example/lib/shell_route_with_keys_example.g.dart
@@ -0,0 +1,88 @@
+// GENERATED CODE - DO NOT MODIFY BY HAND
+
+// ignore_for_file: always_specify_types, public_member_api_docs
+
+part of 'shell_route_with_keys_example.dart';
+
+// **************************************************************************
+// GoRouterGenerator
+// **************************************************************************
+
+List<RouteBase> get $appRoutes => [
+      $myShellRouteData,
+    ];
+
+RouteBase get $myShellRouteData => ShellRouteData.$route(
+      factory: $MyShellRouteDataExtension._fromState,
+      navigatorKey: MyShellRouteData.$navigatorKey,
+      routes: [
+        GoRouteData.$route(
+          path: '/home',
+          factory: $HomeRouteDataExtension._fromState,
+        ),
+        GoRouteData.$route(
+          path: '/users',
+          factory: $UsersRouteDataExtension._fromState,
+          routes: [
+            GoRouteData.$route(
+              path: ':id',
+              factory: $UserRouteDataExtension._fromState,
+              parentNavigatorKey: UserRouteData.$parentNavigatorKey,
+            ),
+          ],
+        ),
+      ],
+    );
+
+extension $MyShellRouteDataExtension on MyShellRouteData {
+  static MyShellRouteData _fromState(GoRouterState state) =>
+      const MyShellRouteData();
+}
+
+extension $HomeRouteDataExtension on HomeRouteData {
+  static HomeRouteData _fromState(GoRouterState state) => const HomeRouteData();
+
+  String get location => GoRouteData.$location(
+        '/home',
+      );
+
+  void go(BuildContext context) => context.go(location);
+
+  void push(BuildContext context) => context.push(location);
+
+  void pushReplacement(BuildContext context) =>
+      context.pushReplacement(location);
+}
+
+extension $UsersRouteDataExtension on UsersRouteData {
+  static UsersRouteData _fromState(GoRouterState state) =>
+      const UsersRouteData();
+
+  String get location => GoRouteData.$location(
+        '/users',
+      );
+
+  void go(BuildContext context) => context.go(location);
+
+  void push(BuildContext context) => context.push(location);
+
+  void pushReplacement(BuildContext context) =>
+      context.pushReplacement(location);
+}
+
+extension $UserRouteDataExtension on UserRouteData {
+  static UserRouteData _fromState(GoRouterState state) => UserRouteData(
+        id: int.parse(state.params['id']!),
+      );
+
+  String get location => GoRouteData.$location(
+        '/users/${Uri.encodeComponent(id.toString())}',
+      );
+
+  void go(BuildContext context) => context.go(location);
+
+  void push(BuildContext context) => context.push(location);
+
+  void pushReplacement(BuildContext context) =>
+      context.pushReplacement(location);
+}
diff --git a/packages/go_router_builder/example/lib/simple_example.g.dart b/packages/go_router_builder/example/lib/simple_example.g.dart
index 6f9eb6a..4edd92a 100644
--- a/packages/go_router_builder/example/lib/simple_example.g.dart
+++ b/packages/go_router_builder/example/lib/simple_example.g.dart
@@ -8,11 +8,11 @@
 // GoRouterGenerator
 // **************************************************************************
 
-List<GoRoute> get $appRoutes => [
+List<RouteBase> get $appRoutes => [
       $homeRoute,
     ];
 
-GoRoute get $homeRoute => GoRouteData.$route(
+RouteBase get $homeRoute => GoRouteData.$route(
       path: '/',
       factory: $HomeRouteExtension._fromState,
       routes: [
diff --git a/packages/go_router_builder/example/pubspec.yaml b/packages/go_router_builder/example/pubspec.yaml
index 4fd36fc..b8b7c9f 100644
--- a/packages/go_router_builder/example/pubspec.yaml
+++ b/packages/go_router_builder/example/pubspec.yaml
@@ -12,7 +12,7 @@
   provider: 6.0.5
 
 dev_dependencies:
-  build_runner: ^2.0.0
+  build_runner: ^2.3.0
   build_verify: ^3.1.0
   flutter_test:
     sdk: flutter
diff --git a/packages/go_router_builder/example/test/all_types_test.dart b/packages/go_router_builder/example/test/all_types_test.dart
index ec25b3d..99a3824 100644
--- a/packages/go_router_builder/example/test/all_types_test.dart
+++ b/packages/go_router_builder/example/test/all_types_test.dart
@@ -2,7 +2,6 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-// import 'package:flutter/material.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter_test/flutter_test.dart';
 import 'package:go_router_builder_example/all_types.dart';
diff --git a/packages/go_router_builder/example/test/shell_route_test.dart b/packages/go_router_builder/example/test/shell_route_test.dart
new file mode 100644
index 0000000..3919970
--- /dev/null
+++ b/packages/go_router_builder/example/test/shell_route_test.dart
@@ -0,0 +1,17 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'package:flutter_test/flutter_test.dart';
+import 'package:go_router_builder_example/shell_route_example.dart';
+
+void main() {
+  testWidgets('Navigate from /foo to /bar', (WidgetTester tester) async {
+    await tester.pumpWidget(App());
+    expect(find.byType(FooScreen), findsOneWidget);
+
+    await tester.tap(find.text('Bar'));
+    await tester.pumpAndSettle();
+    expect(find.byType(BarScreen), findsOneWidget);
+  });
+}
diff --git a/packages/go_router_builder/example/test/shell_route_with_keys_test.dart b/packages/go_router_builder/example/test/shell_route_with_keys_test.dart
new file mode 100644
index 0000000..2c7bd59
--- /dev/null
+++ b/packages/go_router_builder/example/test/shell_route_with_keys_test.dart
@@ -0,0 +1,18 @@
+// Copyright 2013 The Flutter Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+import 'package:flutter/material.dart';
+import 'package:flutter_test/flutter_test.dart';
+import 'package:go_router_builder_example/shell_route_with_keys_example.dart';
+
+void main() {
+  testWidgets('Navigate from /home to /users', (WidgetTester tester) async {
+    await tester.pumpWidget(App());
+    expect(find.text('The home page'), findsOneWidget);
+
+    await tester.tap(find.byIcon(Icons.group));
+    await tester.pumpAndSettle();
+    expect(find.byType(ListTile), findsNWidgets(3));
+  });
+}
diff --git a/packages/go_router_builder/lib/src/go_router_generator.dart b/packages/go_router_builder/lib/src/go_router_generator.dart
index 42ef4ca..5a17670 100644
--- a/packages/go_router_builder/lib/src/go_router_generator.dart
+++ b/packages/go_router_builder/lib/src/go_router_generator.dart
@@ -11,22 +11,56 @@
 
 import 'route_config.dart';
 
-/// A [Generator] for classes annotated with `TypedGoRoute`.
+const String _routeDataUrl = 'package:go_router/src/route_data.dart';
+
+const Map<String, String> _annotations = <String, String>{
+  'TypedGoRoute': 'GoRouteData',
+  'TypedShellRoute': 'ShellRouteData',
+};
+
+/// A [Generator] for classes annotated with a typed go route annotation.
 class GoRouterGenerator extends GeneratorForAnnotation<void> {
   /// Creates a new instance of [GoRouterGenerator].
   const GoRouterGenerator();
 
   @override
-  TypeChecker get typeChecker => const TypeChecker.fromUrl(
-        'package:go_router/src/route_data.dart#TypedGoRoute',
+  TypeChecker get typeChecker => TypeChecker.any(
+        _annotations.keys.map((String annotation) =>
+            TypeChecker.fromUrl('$_routeDataUrl#$annotation')),
       );
 
   @override
   FutureOr<String> generate(LibraryReader library, BuildStep buildStep) async {
     final Set<String> values = <String>{};
-
     final Set<String> getters = <String>{};
 
+    for (final String annotation in _annotations.keys) {
+      final TypeChecker typeChecker =
+          TypeChecker.fromUrl('$_routeDataUrl#$annotation');
+      _generateForAnnotation(library, typeChecker, buildStep, values, getters);
+    }
+
+    if (values.isEmpty) {
+      return '';
+    }
+
+    return <String>[
+      '''
+List<RouteBase> get \$appRoutes => [
+${getters.map((String e) => "$e,").join('\n')}
+    ];
+''',
+      ...values,
+    ].join('\n\n');
+  }
+
+  void _generateForAnnotation(
+    LibraryReader library,
+    TypeChecker typeChecker,
+    BuildStep buildStep,
+    Set<String> values,
+    Set<String> getters,
+  ) {
     for (final AnnotatedElement annotatedElement
         in library.annotatedWith(typeChecker)) {
       final InfoIterable generatedValue = generateForAnnotatedElement(
@@ -40,19 +74,6 @@
         values.add(value);
       }
     }
-
-    if (values.isEmpty) {
-      return '';
-    }
-
-    return <String>[
-      '''
-List<GoRoute> get \$appRoutes => [
-${getters.map((String e) => "$e,").join('\n')}
-    ];
-''',
-      ...values,
-    ].join('\n\n');
   }
 
   @override
@@ -61,18 +82,25 @@
     ConstantReader annotation,
     BuildStep buildStep,
   ) {
+    final String typedAnnotation =
+        annotation.objectValue.type!.getDisplayString(withNullability: false);
+    final String type =
+        typedAnnotation.substring(0, typedAnnotation.indexOf('<'));
+    final String routeData = _annotations[type]!;
     if (element is! ClassElement) {
       throw InvalidGenerationSourceError(
-        'The @TypedGoRoute annotation can only be applied to classes.',
+        'The @$type annotation can only be applied to classes.',
         element: element,
       );
     }
 
-    if (!element.allSupertypes.any((InterfaceType element) =>
-        _goRouteDataChecker.isExactlyType(element))) {
+    final TypeChecker dataChecker =
+        TypeChecker.fromUrl('$_routeDataUrl#$routeData');
+    if (!element.allSupertypes
+        .any((InterfaceType element) => dataChecker.isExactlyType(element))) {
       throw InvalidGenerationSourceError(
-        'The @TypedGoRoute annotation can only be applied to classes that '
-        'extend or implement `GoRouteData`.',
+        'The @$type annotation can only be applied to classes that '
+        'extend or implement `$routeData`.',
         element: element,
       );
     }
@@ -80,7 +108,3 @@
     return RouteConfig.fromAnnotation(annotation, element).generateMembers();
   }
 }
-
-const TypeChecker _goRouteDataChecker = TypeChecker.fromUrl(
-  'package:go_router/src/route_data.dart#GoRouteData',
-);
diff --git a/packages/go_router_builder/lib/src/route_config.dart b/packages/go_router_builder/lib/src/route_config.dart
index cfc4863..9d7c1d9 100644
--- a/packages/go_router_builder/lib/src/route_config.dart
+++ b/packages/go_router_builder/lib/src/route_config.dart
@@ -39,6 +39,8 @@
     this._path,
     this._routeDataClass,
     this._parent,
+    this._key,
+    this._isShellRoute,
   );
 
   /// Creates a new [RouteConfig] represented the annotation data in [reader].
@@ -66,19 +68,26 @@
     RouteConfig? parent,
   ) {
     assert(!reader.isNull, 'reader should not be null');
-    final ConstantReader pathValue = reader.read('path');
-    if (pathValue.isNull) {
-      throw InvalidGenerationSourceError(
-        'Missing `path` value on annotation.',
-        element: element,
-      );
+    final InterfaceType type = reader.objectValue.type! as InterfaceType;
+    // TODO(stuartmorgan): Remove this ignore once 'analyze' can be set to
+    // 5.2+ (when Flutter 3.4+ is on stable).
+    // ignore: deprecated_member_use
+    final bool isShellRoute = type.element.name == 'TypedShellRoute';
+
+    String? path;
+
+    if (!isShellRoute) {
+      final ConstantReader pathValue = reader.read('path');
+      if (pathValue.isNull) {
+        throw InvalidGenerationSourceError(
+          'Missing `path` value on annotation.',
+          element: element,
+        );
+      }
+      path = pathValue.stringValue;
     }
 
-    final String path = pathValue.stringValue;
-
-    final InterfaceType type = reader.objectValue.type! as InterfaceType;
     final DartType typeParamType = type.typeArguments.single;
-
     if (typeParamType is! InterfaceType) {
       throw InvalidGenerationSourceError(
         'The type parameter on one of the @TypedGoRoute declarations could not '
@@ -93,7 +102,16 @@
     // ignore: deprecated_member_use
     final InterfaceElement classElement = typeParamType.element;
 
-    final RouteConfig value = RouteConfig._(path, classElement, parent);
+    final RouteConfig value = RouteConfig._(
+      path ?? '',
+      classElement,
+      parent,
+      _generateNavigatorKeyGetterCode(
+        classElement,
+        keyName: isShellRoute ? r'$navigatorKey' : r'$parentNavigatorKey',
+      ),
+      isShellRoute,
+    );
 
     value._children.addAll(reader.read('routes').listValue.map((DartObject e) =>
         RouteConfig._fromAnnotation(ConstantReader(e), element, value)));
@@ -105,6 +123,42 @@
   final String _path;
   final InterfaceElement _routeDataClass;
   final RouteConfig? _parent;
+  final String? _key;
+  final bool _isShellRoute;
+
+  static String? _generateNavigatorKeyGetterCode(
+    InterfaceElement classElement, {
+    required String keyName,
+  }) {
+    bool whereStatic(FieldElement element) => element.isStatic;
+    bool whereKeyName(FieldElement element) => element.name == keyName;
+    final String? fieldDisplayName = classElement.fields
+        .where(whereStatic)
+        .where(whereKeyName)
+        .where((FieldElement element) {
+          final DartType type = element.type;
+          if (type is! ParameterizedType) {
+            return false;
+          }
+          final List<DartType> typeArguments = type.typeArguments;
+          if (typeArguments.length != 1) {
+            return false;
+          }
+          final DartType typeArgument = typeArguments.single;
+          if (typeArgument.getDisplayString(withNullability: false) ==
+              'NavigatorState') {
+            return true;
+          }
+          return false;
+        })
+        .map<String>((FieldElement e) => e.displayName)
+        .firstOrNull;
+
+    if (fieldDisplayName == null) {
+      return null;
+    }
+    return '${classElement.name}.$fieldDisplayName';
+  }
 
   /// Generates all of the members that correspond to `this`.
   InfoIterable generateMembers() => InfoIterable._(
@@ -136,7 +190,15 @@
   }
 
   /// Returns `extension` code.
-  String _extensionDefinition() => '''
+  String _extensionDefinition() {
+    if (_isShellRoute) {
+      return '''
+extension $_extensionName on $_className {
+  static $_className _fromState(GoRouterState state) $_newFromState
+}
+''';
+    }
+    return '''
 extension $_extensionName on $_className {
   static $_className _fromState(GoRouterState state) $_newFromState
 
@@ -152,6 +214,7 @@
       context.pushReplacement(location${_extraParam != null ? ', extra: $extraFieldName' : ''});
 }
 ''';
+  }
 
   /// Returns this [RouteConfig] and all child [RouteConfig] instances.
   Iterable<RouteConfig> _flatten() sync* {
@@ -166,7 +229,7 @@
 
   /// Returns the `GoRoute` code for the annotated class.
   String _rootDefinition() => '''
-GoRoute get $_routeGetterName => ${_routeDefinition()};
+RouteBase get $_routeGetterName => ${_routeDefinition()};
 ''';
 
   /// Returns code representing the constant maps that contain the `enum` to
@@ -271,11 +334,25 @@
         : '''
 routes: [${_children.map((RouteConfig e) => '${e._routeDefinition()},').join()}],
 ''';
-
+    final String navigatorKeyParameterName =
+        _isShellRoute ? 'navigatorKey' : 'parentNavigatorKey';
+    final String navigatorKey = _key == null || _key!.isEmpty
+        ? ''
+        : '$navigatorKeyParameterName: $_key,';
+    if (_isShellRoute) {
+      return '''
+  ShellRouteData.\$route(
+    factory: $_extensionName._fromState,
+    $navigatorKey
+    $routesBit
+  )
+''';
+    }
     return '''
 GoRouteData.\$route(
       path: ${escapeDartString(_path)},
       factory: $_extensionName._fromState,
+      $navigatorKey
       $routesBit
 )
 ''';
diff --git a/packages/go_router_builder/pubspec.yaml b/packages/go_router_builder/pubspec.yaml
index 5bc629a..99d3ba3 100644
--- a/packages/go_router_builder/pubspec.yaml
+++ b/packages/go_router_builder/pubspec.yaml
@@ -2,7 +2,7 @@
 description: >-
   A builder that supports generated strongly-typed route helpers for
   package:go_router
-version: 1.1.7
+version: 1.2.0
 repository: https://github.com/flutter/packages/tree/main/packages/go_router_builder
 issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+go_router_builder%22
 
@@ -10,7 +10,7 @@
   sdk: ">=2.18.0 <4.0.0"
 
 dependencies:
-  analyzer: '>=4.4.0 <6.0.0'
+  analyzer: ">=4.4.0 <6.0.0"
   async: ^2.8.0
   build: ^2.0.0
   build_config: ^1.0.0
@@ -23,6 +23,6 @@
 
 dev_dependencies:
   build_runner: ^2.0.0
-  go_router: ^5.0.0
+  go_router: ^6.0.10
   source_gen_test: ^1.0.0
   test: ^1.20.0
diff --git a/packages/go_router_builder/test/test_inputs/_go_router_builder_test_input.dart b/packages/go_router_builder/test/test_inputs/_go_router_builder_test_input.dart
index 9875d2e..2b843c1 100644
--- a/packages/go_router_builder/test/test_inputs/_go_router_builder_test_input.dart
+++ b/packages/go_router_builder/test/test_inputs/_go_router_builder_test_input.dart
@@ -72,7 +72,7 @@
 }
 
 @ShouldGenerate(r'''
-GoRoute get $enumParam => GoRouteData.$route(
+RouteBase get $enumParam => GoRouteData.$route(
       path: '/:y',
       factory: $EnumParamExtension._fromState,
     );
@@ -121,7 +121,7 @@
 }
 
 @ShouldGenerate(r'''
-GoRoute get $defaultValueRoute => GoRouteData.$route(
+RouteBase get $defaultValueRoute => GoRouteData.$route(
       path: '/default-value-route',
       factory: $DefaultValueRouteExtension._fromState,
     );
@@ -162,7 +162,7 @@
 }
 
 @ShouldGenerate(r'''
-GoRoute get $extraValueRoute => GoRouteData.$route(
+RouteBase get $extraValueRoute => GoRouteData.$route(
       path: '/default-value-route',
       factory: $ExtraValueRouteExtension._fromState,
     );
@@ -215,7 +215,7 @@
 }
 
 @ShouldGenerate(r'''
-GoRoute get $iterableWithEnumRoute => GoRouteData.$route(
+RouteBase get $iterableWithEnumRoute => GoRouteData.$route(
       path: '/iterable-with-enum',
       factory: $IterableWithEnumRouteExtension._fromState,
     );
@@ -269,7 +269,7 @@
 }
 
 @ShouldGenerate(r'''
-GoRoute get $iterableDefaultValueRoute => GoRouteData.$route(
+RouteBase get $iterableDefaultValueRoute => GoRouteData.$route(
       path: '/iterable-default-value-route',
       factory: $IterableDefaultValueRouteExtension._fromState,
     );