diff --git a/packages/go_router_builder/CHANGELOG.md b/packages/go_router_builder/CHANGELOG.md index ac41ce6cdad..5d0d9139722 100644 --- a/packages/go_router_builder/CHANGELOG.md +++ b/packages/go_router_builder/CHANGELOG.md @@ -1,3 +1,7 @@ +## 2.2.1 + +* Cleans up go_router_builder code. + ## 2.2.0 * Adds replace methods to the generated routes. diff --git a/packages/go_router_builder/README.md b/packages/go_router_builder/README.md index 0eff1f07c6a..797231c6be8 100644 --- a/packages/go_router_builder/README.md +++ b/packages/go_router_builder/README.md @@ -379,3 +379,9 @@ class MyGoRouteData extends GoRouteData { ``` An example is available [here](https://github.com/flutter/packages/blob/main/packages/go_router_builder/example/lib/shell_route_with_keys_example.dart). + +## Run tests + +To run unit tests, run command `dart tool/run_tests.dart` from `packages/go_router_builder/`. + +To run tests in examples, run `flutter test` from `packages/go_router_builder/example`. \ No newline at end of file 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 e66590a39f2..1afdf3d735a 100644 --- a/packages/go_router_builder/example/lib/all_types.g.dart +++ b/packages/go_router_builder/example/lib/all_types.g.dart @@ -302,6 +302,12 @@ extension $EnumRouteExtension on EnumRoute { void replace(BuildContext context) => context.replace(location); } +const _$PersonDetailsEnumMap = { + PersonDetails.hobbies: 'hobbies', + PersonDetails.favoriteFood: 'favorite-food', + PersonDetails.favoriteSport: 'favorite-sport', +}; + extension $EnhancedEnumRouteExtension on EnhancedEnumRoute { static EnhancedEnumRoute _fromState(GoRouterState state) => EnhancedEnumRoute( requiredEnumField: _$SportDetailsEnumMap @@ -336,6 +342,13 @@ extension $EnhancedEnumRouteExtension on EnhancedEnumRoute { void replace(BuildContext context) => context.replace(location); } +const _$SportDetailsEnumMap = { + SportDetails.volleyball: 'volleyball', + SportDetails.football: 'football', + SportDetails.tennis: 'tennis', + SportDetails.hockey: 'hockey', +}; + extension $StringRouteExtension on StringRoute { static StringRoute _fromState(GoRouterState state) => StringRoute( requiredStringField: state.pathParameters['requiredStringField']!, @@ -506,6 +519,12 @@ extension $IterableRouteExtension on IterableRoute { void replace(BuildContext context) => context.replace(location); } +const _$CookingRecipeEnumMap = { + CookingRecipe.burger: 'burger', + CookingRecipe.pizza: 'pizza', + CookingRecipe.tacos: 'tacos', +}; + extension $IterableRouteWithDefaultValuesExtension on IterableRouteWithDefaultValues { static IterableRouteWithDefaultValues _fromState(GoRouterState state) => @@ -622,25 +641,6 @@ extension $IterableRouteWithDefaultValuesExtension void replace(BuildContext context) => context.replace(location); } -const _$PersonDetailsEnumMap = { - PersonDetails.hobbies: 'hobbies', - PersonDetails.favoriteFood: 'favorite-food', - PersonDetails.favoriteSport: 'favorite-sport', -}; - -const _$SportDetailsEnumMap = { - SportDetails.volleyball: 'volleyball', - SportDetails.football: 'football', - SportDetails.tennis: 'tennis', - SportDetails.hockey: 'hockey', -}; - -const _$CookingRecipeEnumMap = { - CookingRecipe.burger: 'burger', - CookingRecipe.pizza: 'pizza', - CookingRecipe.tacos: 'tacos', -}; - T? _$convertMapValue( String key, Map map, diff --git a/packages/go_router_builder/example/lib/main.g.dart b/packages/go_router_builder/example/lib/main.g.dart index 9328e5276c9..c52640aff3e 100644 --- a/packages/go_router_builder/example/lib/main.g.dart +++ b/packages/go_router_builder/example/lib/main.g.dart @@ -121,6 +121,12 @@ extension $PersonDetailsRouteExtension on PersonDetailsRoute { context.replace(location, extra: $extra); } +const _$PersonDetailsEnumMap = { + PersonDetails.hobbies: 'hobbies', + PersonDetails.favoriteFood: 'favorite-food', + PersonDetails.favoriteSport: 'favorite-sport', +}; + extension $FamilyCountRouteExtension on FamilyCountRoute { static FamilyCountRoute _fromState(GoRouterState state) => FamilyCountRoute( int.parse(state.pathParameters['count']!), @@ -140,12 +146,6 @@ extension $FamilyCountRouteExtension on FamilyCountRoute { void replace(BuildContext context) => context.replace(location); } -const _$PersonDetailsEnumMap = { - PersonDetails.hobbies: 'hobbies', - PersonDetails.favoriteFood: 'favorite-food', - PersonDetails.favoriteSport: 'favorite-sport', -}; - extension on Map { T _$fromName(String value) => entries.singleWhere((element) => element.value == value).key; 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 index 70b75e4e424..bc655746b61 100644 --- a/packages/go_router_builder/example/lib/shell_route_example.g.dart +++ b/packages/go_router_builder/example/lib/shell_route_example.g.dart @@ -9,32 +9,10 @@ part of 'shell_route_example.dart'; // ************************************************************************** List get $appRoutes => [ - $loginRoute, $myShellRouteData, + $loginRoute, ]; -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); - - Future push(BuildContext context) => context.push(location); - - void pushReplacement(BuildContext context) => - context.pushReplacement(location); - - void replace(BuildContext context) => context.replace(location); -} - RouteBase get $myShellRouteData => ShellRouteData.$route( factory: $MyShellRouteDataExtension._fromState, routes: [ @@ -87,3 +65,25 @@ extension $BarRouteDataExtension on BarRouteData { void replace(BuildContext context) => context.replace(location); } + +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); + + Future push(BuildContext context) => context.push(location); + + void pushReplacement(BuildContext context) => + context.pushReplacement(location); + + void replace(BuildContext context) => context.replace(location); +} 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 index b444c64c83c..31f9d4bca6d 100644 --- 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 @@ -13,8 +13,8 @@ List get $appRoutes => [ ]; RouteBase get $myShellRouteData => ShellRouteData.$route( - factory: $MyShellRouteDataExtension._fromState, navigatorKey: MyShellRouteData.$navigatorKey, + factory: $MyShellRouteDataExtension._fromState, routes: [ GoRouteData.$route( path: '/home', 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 5a1767022db..cf57329f3a3 100644 --- a/packages/go_router_builder/lib/src/go_router_generator.dart +++ b/packages/go_router_builder/lib/src/go_router_generator.dart @@ -19,12 +19,11 @@ const Map _annotations = { }; /// A [Generator] for classes annotated with a typed go route annotation. -class GoRouterGenerator extends GeneratorForAnnotation { +class GoRouterGenerator extends Generator { /// Creates a new instance of [GoRouterGenerator]. const GoRouterGenerator(); - @override - TypeChecker get typeChecker => TypeChecker.any( + TypeChecker get _typeChecker => TypeChecker.any( _annotations.keys.map((String annotation) => TypeChecker.fromUrl('$_routeDataUrl#$annotation')), ); @@ -34,11 +33,7 @@ class GoRouterGenerator extends GeneratorForAnnotation { final Set values = {}; final Set getters = {}; - for (final String annotation in _annotations.keys) { - final TypeChecker typeChecker = - TypeChecker.fromUrl('$_routeDataUrl#$annotation'); - _generateForAnnotation(library, typeChecker, buildStep, values, getters); - } + generateForAnnotation(library, values, getters); if (values.isEmpty) { return ''; @@ -54,19 +49,20 @@ ${getters.map((String e) => "$e,").join('\n')} ].join('\n\n'); } - void _generateForAnnotation( + /// Generates code for the `library` based on annotation. + /// + /// This public method is for testing purposes and should not be called + /// directly. + void generateForAnnotation( LibraryReader library, - TypeChecker typeChecker, - BuildStep buildStep, Set values, Set getters, ) { for (final AnnotatedElement annotatedElement - in library.annotatedWith(typeChecker)) { - final InfoIterable generatedValue = generateForAnnotatedElement( + in library.annotatedWith(_typeChecker)) { + final InfoIterable generatedValue = _generateForAnnotatedElement( annotatedElement.element, annotatedElement.annotation, - buildStep, ); getters.add(generatedValue.routeGetterName); for (final String value in generatedValue) { @@ -76,11 +72,9 @@ ${getters.map((String e) => "$e,").join('\n')} } } - @override - InfoIterable generateForAnnotatedElement( + InfoIterable _generateForAnnotatedElement( Element element, ConstantReader annotation, - BuildStep buildStep, ) { final String typedAnnotation = annotation.objectValue.type!.getDisplayString(withNullability: false); @@ -105,6 +99,7 @@ ${getters.map((String e) => "$e,").join('\n')} ); } - return RouteConfig.fromAnnotation(annotation, element).generateMembers(); + return RouteBaseConfig.fromAnnotation(annotation, element) + .generateMembers(); } } diff --git a/packages/go_router_builder/lib/src/route_config.dart b/packages/go_router_builder/lib/src/route_config.dart index 20d2bba53b1..c009b12c797 100644 --- a/packages/go_router_builder/lib/src/route_config.dart +++ b/packages/go_router_builder/lib/src/route_config.dart @@ -9,6 +9,7 @@ import 'package:analyzer/dart/element/element.dart'; import 'package:analyzer/dart/element/nullability_suffix.dart'; import 'package:analyzer/dart/element/type.dart'; import 'package:collection/collection.dart'; +import 'package:meta/meta.dart'; import 'package:path/path.dart' as p; import 'package:path_to_regexp/path_to_regexp.dart'; import 'package:source_gen/source_gen.dart'; @@ -33,266 +34,70 @@ class InfoIterable extends IterableBase { Iterator get iterator => members.iterator; } -/// Represents a `TypedGoRoute` annotation to the builder. -class RouteConfig { - RouteConfig._( - this._path, - this._name, - this._routeDataClass, - this._parent, - this._key, - this._isShellRoute, - ); - - /// Creates a new [RouteConfig] represented the annotation data in [reader]. - factory RouteConfig.fromAnnotation( - ConstantReader reader, - InterfaceElement element, - ) { - final RouteConfig definition = - RouteConfig._fromAnnotation(reader, element, null); - - if (element != definition._routeDataClass) { - throw InvalidGenerationSourceError( - 'The @TypedGoRoute annotation must have a type parameter that matches ' - 'the annotated element.', - element: element, - ); - } - - return definition; - } - - factory RouteConfig._fromAnnotation( - ConstantReader reader, - InterfaceElement element, - RouteConfig? parent, - ) { - assert(!reader.isNull, 'reader should not be null'); - 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; - String? name; - - if (!isShellRoute) { - final ConstantReader pathValue = reader.read('path'); - if (pathValue.isNull) { - throw InvalidGenerationSourceError( - 'Missing `path` value on annotation.', - element: element, - ); - } - path = pathValue.stringValue; - - final ConstantReader nameValue = reader.read('name'); - name = nameValue.isNull ? null : nameValue.stringValue; - } - - final DartType typeParamType = type.typeArguments.single; - if (typeParamType is! InterfaceType) { - throw InvalidGenerationSourceError( - 'The type parameter on one of the @TypedGoRoute declarations could not ' - 'be parsed.', - element: element, - ); - } - - // TODO(kevmoo): validate that this MUST be a subtype of `GoRouteData` - // 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 InterfaceElement classElement = typeParamType.element; - - final RouteConfig value = RouteConfig._( - path ?? '', - name, - classElement, - parent, - _generateNavigatorKeyGetterCode( - classElement, - keyName: isShellRoute ? r'$navigatorKey' : r'$parentNavigatorKey', - ), - isShellRoute, - ); +/// The configuration to generate class declarations for a ShellRouteData. +class ShellRouteConfig extends RouteBaseConfig { + ShellRouteConfig._({ + required this.navigatorKey, + required super.routeDataClass, + required super.parent, + required super.parentNavigatorKey, + }) : super._(); - value._children.addAll(reader.read('routes').listValue.map((DartObject e) => - RouteConfig._fromAnnotation(ConstantReader(e), element, value))); - - return value; - } - - final List _children = []; - final String _path; - final String? _name; - final InterfaceElement _routeDataClass; - final RouteConfig? _parent; - final String? _key; - final bool _isShellRoute; - - static String? _generateNavigatorKeyGetterCode( - InterfaceElement classElement, { - required String keyName, - }) { - final String? fieldDisplayName = classElement.fields - .where((FieldElement element) { - final DartType type = element.type; - if (!element.isStatic || - element.name != keyName || - type is! ParameterizedType) { - return false; - } - final List 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((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._( - members: _generateMembers().toList(), - routeGetterName: _routeGetterName, - ); - - Iterable _generateMembers() sync* { - final List items = [ - _rootDefinition(), - ]; - - for (final RouteConfig def in _flatten()) { - items.add(def._extensionDefinition()); - } - - _enumDefinitions().forEach(items.add); - - yield* items; + /// The command for calling the navigator key getter from the ShellRouteData. + final String? navigatorKey; - yield* items - .expand( - (String e) => helperNames.entries - .where( - (MapEntry element) => e.contains(element.key)) - .map((MapEntry e) => e.value), - ) - .toSet(); - } - - /// Returns `extension` code. - String _extensionDefinition() { - if (_isShellRoute) { - return ''' + @override + Iterable classDeclarations() => [ + ''' extension $_extensionName on $_className { - static $_className _fromState(GoRouterState state) $_newFromState + static $_className _fromState(GoRouterState state) => const $_className(); } -'''; - } - return ''' -extension $_extensionName on $_className { - static $_className _fromState(GoRouterState state) $_newFromState - - String get location => GoRouteData.\$location($_locationArgs,$_locationQueryParams); - - void go(BuildContext context) => - context.go(location${_extraParam != null ? ', extra: $extraFieldName' : ''}); +''' + ]; - Future push(BuildContext context) => - context.push(location${_extraParam != null ? ', extra: $extraFieldName' : ''}); - - void pushReplacement(BuildContext context) => - context.pushReplacement(location${_extraParam != null ? ', extra: $extraFieldName' : ''}); + @override + String get routeConstructorParameters => + navigatorKey == null ? '' : 'navigatorKey: $navigatorKey,'; - void replace(BuildContext context) => - context.replace(location${_extraParam != null ? ', extra: $extraFieldName' : ''}); + @override + String get routeDataClassName => 'ShellRouteData'; } -'''; - } - - /// Returns this [RouteConfig] and all child [RouteConfig] instances. - Iterable _flatten() sync* { - yield this; - for (final RouteConfig child in _children) { - yield* child._flatten(); - } - } - - late final String _routeGetterName = - r'$' + _className.substring(0, 1).toLowerCase() + _className.substring(1); - /// Returns the `GoRoute` code for the annotated class. - String _rootDefinition() => ''' -RouteBase get $_routeGetterName => ${_routeDefinition()}; -'''; +/// The configuration to generate class declarations for a GoRouteData. +class GoRouteConfig extends RouteBaseConfig { + GoRouteConfig._({ + required this.path, + required this.name, + required super.routeDataClass, + required super.parent, + required super.parentNavigatorKey, + }) : super._(); - /// Returns code representing the constant maps that contain the `enum` to - /// [String] mapping for each referenced enum. - Iterable _enumDefinitions() sync* { - final Set enumParamTypes = {}; + /// The path of the GoRoute to be created by this configuration. + final String path; - for (final RouteConfig routeDef in _flatten()) { - for (final ParameterElement ctorParam in [ - ...routeDef._ctorParams, - ...routeDef._ctorQueryParams, - ]) { - DartType potentialEnumType = ctorParam.type; - if (potentialEnumType is ParameterizedType && - (ctorParam.type as ParameterizedType).typeArguments.isNotEmpty) { - potentialEnumType = - (ctorParam.type as ParameterizedType).typeArguments.first; - } - - if (potentialEnumType.isEnum) { - enumParamTypes.add(potentialEnumType as InterfaceType); - } - } - } - - for (final InterfaceType enumParamType in enumParamTypes) { - yield _enumMapConst(enumParamType); - } - } + /// The name of the GoRoute to be created by this configuration. + final String? name; - ParameterElement? get _extraParam => _ctor.parameters - .singleWhereOrNull((ParameterElement element) => element.isExtraField); + late final Set _pathParams = Set.unmodifiable(_parsedPath + .whereType() + .map((ParameterToken e) => e.name)); - String get _newFromState { - final StringBuffer buffer = StringBuffer('=>'); - if (_ctor.isConst && - _ctorParams.isEmpty && - _ctorQueryParams.isEmpty && - _extraParam == null) { - buffer.writeln('const '); - } + late final List _parsedPath = + List.unmodifiable(parse(_rawJoinedPath)); - final ParameterElement? extraParam = _extraParam; + String get _rawJoinedPath { + final List pathSegments = []; - buffer.writeln('$_className('); - for (final ParameterElement param in [ - ..._ctorParams, - ..._ctorQueryParams, - if (extraParam != null) extraParam, - ]) { - buffer.write(_decodeFor(param)); + RouteBaseConfig? config = this; + while (config != null) { + if (config is GoRouteConfig) { + pathSegments.add(config.path); + } + config = config.parent; } - buffer.writeln(');'); - return buffer.toString(); + return p.url.joinAll(pathSegments.reversed); } // construct path bits using parent bits @@ -316,58 +121,29 @@ RouteBase get $_routeGetterName => ${_routeDefinition()}; return "'${pathItems.join()}'"; } - late final Set _pathParams = Set.unmodifiable(_parsedPath - .whereType() - .map((ParameterToken e) => e.name)); - - late final List _parsedPath = - List.unmodifiable(parse(_rawJoinedPath)); - - String get _rawJoinedPath { - final List pathSegments = []; + ParameterElement? get _extraParam => _ctor.parameters + .singleWhereOrNull((ParameterElement element) => element.isExtraField); - RouteConfig? config = this; - while (config != null) { - pathSegments.add(config._path); - config = config._parent; + String get _fromStateConstructor { + final StringBuffer buffer = StringBuffer('=>'); + if (_ctor.isConst && + _ctorParams.isEmpty && + _ctorQueryParams.isEmpty && + _extraParam == null) { + buffer.writeln('const '); } - return p.url.joinAll(pathSegments.reversed); - } - - String get _className => _routeDataClass.name; - - String get _extensionName => '\$${_className}Extension'; - - String _routeDefinition() { - final String routesBit = _children.isEmpty - ? '' - : ''' -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 - ) -'''; + buffer.writeln('$_className('); + for (final ParameterElement param in [ + ..._ctorParams, + ..._ctorQueryParams, + if (_extraParam != null) _extraParam!, + ]) { + buffer.write(_decodeFor(param)); } - return ''' -GoRouteData.\$route( - path: ${escapeDartString(_path)}, - ${_name != null ? 'name: ${escapeDartString(_name!)},' : ''} - factory: $_extensionName._fromState, - $navigatorKey - $routesBit -) -'''; + buffer.writeln(');'); + + return buffer.toString(); } String _decodeFor(ParameterElement element) { @@ -401,7 +177,7 @@ GoRouteData.\$route( if (field == null) { throw InvalidGenerationSourceError( 'Could not find a field for the path parameter "$fieldName".', - element: _routeDataClass, + element: routeDataClass, ); } @@ -456,19 +232,296 @@ GoRouteData.\$route( .toList(); ConstructorElement get _ctor { - final ConstructorElement? ctor = _routeDataClass.unnamedConstructor; + final ConstructorElement? ctor = routeDataClass.unnamedConstructor; if (ctor == null) { throw InvalidGenerationSourceError( 'Missing default constructor', - element: _routeDataClass, + element: routeDataClass, ); } return ctor; } + @override + Iterable classDeclarations() => [ + _extensionDefinition, + ..._enumDeclarations(), + ]; + + String get _extensionDefinition => ''' +extension $_extensionName on $_className { + static $_className _fromState(GoRouterState state) $_fromStateConstructor + + String get location => GoRouteData.\$location($_locationArgs,$_locationQueryParams); + + void go(BuildContext context) => + context.go(location${_extraParam != null ? ', extra: $extraFieldName' : ''}); + + Future push(BuildContext context) => + context.push(location${_extraParam != null ? ', extra: $extraFieldName' : ''}); + + void pushReplacement(BuildContext context) => + context.pushReplacement(location${_extraParam != null ? ', extra: $extraFieldName' : ''}); + + void replace(BuildContext context) => + context.replace(location${_extraParam != null ? ', extra: $extraFieldName' : ''}); +} +'''; + + /// Returns code representing the constant maps that contain the `enum` to + /// [String] mapping for each referenced enum. + Iterable _enumDeclarations() { + final Set enumParamTypes = {}; + + for (final ParameterElement ctorParam in [ + ..._ctorParams, + ..._ctorQueryParams, + ]) { + DartType potentialEnumType = ctorParam.type; + if (potentialEnumType is ParameterizedType && + (ctorParam.type as ParameterizedType).typeArguments.isNotEmpty) { + potentialEnumType = + (ctorParam.type as ParameterizedType).typeArguments.first; + } + + if (potentialEnumType.isEnum) { + enumParamTypes.add(potentialEnumType as InterfaceType); + } + } + return enumParamTypes.map(_enumMapConst); + } + + @override + String get routeConstructorParameters => ''' + path: ${escapeDartString(path)}, + ${name != null ? 'name: ${escapeDartString(name!)},' : ''} +'''; + + @override + String get routeDataClassName => 'GoRouteData'; +} + +/// Represents a `TypedGoRoute` annotation to the builder. +abstract class RouteBaseConfig { + RouteBaseConfig._({ + required this.routeDataClass, + required this.parent, + required this.parentNavigatorKey, + }); + + /// Creates a new [RouteBaseConfig] represented the annotation data in [reader]. + factory RouteBaseConfig.fromAnnotation( + ConstantReader reader, + InterfaceElement element, + ) { + final RouteBaseConfig definition = + RouteBaseConfig._fromAnnotation(reader, element, null); + + if (element != definition.routeDataClass) { + throw InvalidGenerationSourceError( + 'The @TypedGoRoute annotation must have a type parameter that matches ' + 'the annotated element.', + element: element, + ); + } + + return definition; + } + + factory RouteBaseConfig._fromAnnotation( + ConstantReader reader, + InterfaceElement element, + RouteBaseConfig? parent, + ) { + assert(!reader.isNull, 'reader should not be null'); + 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'; + final DartType typeParamType = type.typeArguments.single; + if (typeParamType is! InterfaceType) { + throw InvalidGenerationSourceError( + 'The type parameter on one of the @TypedGoRoute declarations could not ' + 'be parsed.', + element: element, + ); + } + + // TODO(kevmoo): validate that this MUST be a subtype of `GoRouteData` + // 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 InterfaceElement classElement = typeParamType.element; + + final RouteBaseConfig value; + if (isShellRoute) { + value = ShellRouteConfig._( + routeDataClass: classElement, + parent: parent, + navigatorKey: _generateNavigatorKeyGetterCode( + classElement, + keyName: r'$navigatorKey', + ), + parentNavigatorKey: _generateNavigatorKeyGetterCode( + classElement, + keyName: r'$parentNavigatorKey', + ), + ); + } else { + final ConstantReader pathValue = reader.read('path'); + if (pathValue.isNull) { + throw InvalidGenerationSourceError( + 'Missing `path` value on annotation.', + element: element, + ); + } + + final ConstantReader nameValue = reader.read('name'); + value = GoRouteConfig._( + path: pathValue.stringValue, + name: nameValue.isNull ? null : nameValue.stringValue, + routeDataClass: classElement, + parent: parent, + parentNavigatorKey: _generateNavigatorKeyGetterCode( + classElement, + keyName: r'$parentNavigatorKey', + ), + ); + } + + value._children.addAll(reader.read('routes').listValue.map( + (DartObject e) => RouteBaseConfig._fromAnnotation( + ConstantReader(e), element, value))); + + return value; + } + + final List _children = []; + + /// The `RouteData` class this class represents. + final InterfaceElement routeDataClass; + + /// The parent of this route config. + final RouteBaseConfig? parent; + + /// The parent navigator key string that is used for initialize the + /// `RouteBase` class this config generates. + final String? parentNavigatorKey; + + static String? _generateNavigatorKeyGetterCode( + InterfaceElement classElement, { + required String keyName, + }) { + final String? fieldDisplayName = classElement.fields + .where((FieldElement element) { + final DartType type = element.type; + if (!element.isStatic || + element.name != keyName || + type is! ParameterizedType) { + return false; + } + final List 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((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._( + members: _generateMembers().toList(), + routeGetterName: _routeGetterName, + ); + + Iterable _generateMembers() sync* { + final List items = [ + _rootDefinition(), + ]; + + for (final RouteBaseConfig def in _flatten()) { + items.addAll(def.classDeclarations()); + } + + yield* items; + + yield* items + .expand( + (String e) => helperNames.entries + .where( + (MapEntry element) => e.contains(element.key)) + .map((MapEntry e) => e.value), + ) + .toSet(); + } + + /// Returns this [GoRouteConfig] and all child [GoRouteConfig] instances. + Iterable _flatten() sync* { + yield this; + for (final RouteBaseConfig child in _children) { + yield* child._flatten(); + } + } + + late final String _routeGetterName = + r'$' + _className.substring(0, 1).toLowerCase() + _className.substring(1); + + /// Returns the `GoRoute` code for the annotated class. + String _rootDefinition() => ''' +RouteBase get $_routeGetterName => ${_invokesRouteConstructor()}; +'''; + + String get _className => routeDataClass.name; + + String get _extensionName => '\$${_className}Extension'; + + String _invokesRouteConstructor() { + final String routesBit = _children.isEmpty + ? '' + : ''' +routes: [${_children.map((RouteBaseConfig e) => '${e._invokesRouteConstructor()},').join()}], +'''; + final String parentNavigatorKeyParameter = parentNavigatorKey == null + ? '' + : 'parentNavigatorKey: $parentNavigatorKey,'; + return ''' +$routeDataClassName.\$route( + $routeConstructorParameters + factory: $_extensionName._fromState, + $parentNavigatorKeyParameter + $routesBit + ) +'''; + } + PropertyAccessorElement? _field(String name) => - _routeDataClass.getGetter(name); + routeDataClass.getGetter(name); + + /// The name of `RouteData` subclass this configuration represents. + @protected + String get routeDataClassName; + + /// Additional constructor parameter for invoking route constructor. + @protected + String get routeConstructorParameters; + + /// Returns all class declarations code. + @protected + Iterable classDeclarations(); } String _enumMapConst(InterfaceType type) { diff --git a/packages/go_router_builder/pubspec.yaml b/packages/go_router_builder/pubspec.yaml index 6c6557942de..cc76e163bd8 100644 --- a/packages/go_router_builder/pubspec.yaml +++ b/packages/go_router_builder/pubspec.yaml @@ -2,7 +2,7 @@ name: go_router_builder description: >- A builder that supports generated strongly-typed route helpers for package:go_router -version: 2.2.0 +version: 2.2.1 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 @@ -22,7 +22,7 @@ dependencies: source_helper: ^1.3.0 dev_dependencies: - build_runner: ^2.0.0 - go_router: ^7.0.0 - source_gen_test: ^1.0.0 + build_test: ^2.1.7 + dart_style: 2.3.2 + go_router: ^9.0.0 test: ^1.20.0 diff --git a/packages/go_router_builder/test/builder_test.dart b/packages/go_router_builder/test/builder_test.dart deleted file mode 100644 index 53df1f0cf88..00000000000 --- a/packages/go_router_builder/test/builder_test.dart +++ /dev/null @@ -1,43 +0,0 @@ -// 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:go_router_builder/src/go_router_generator.dart'; -import 'package:path/path.dart' as p; -import 'package:source_gen/source_gen.dart'; -import 'package:source_gen_test/source_gen_test.dart'; - -Future main() async { - initializeBuildLogTracking(); - final LibraryReader testReader = await initializeLibraryReaderForDirectory( - p.join('test', 'test_inputs'), - '_go_router_builder_test_input.dart', - ); - - testAnnotatedElements( - testReader, - const GoRouterGenerator(), - expectedAnnotatedTests: _expectedAnnotatedTests, - ); -} - -const Set _expectedAnnotatedTests = { - 'AppliedToWrongClassType', - 'BadPathParam', - 'ExtraValueRoute', - 'RequiredExtraValueRoute', - 'MissingPathValue', - 'MissingTypeAnnotation', - 'NullableRequiredParamInPath', - 'NullableRequiredParamNotInPath', - 'NonNullableRequiredParamNotInPath', - 'UnsupportedType', - 'theAnswer', - 'EnumParam', - 'DefaultValueRoute', - 'NullableDefaultValueRoute', - 'IterableWithEnumRoute', - 'IterableDefaultValueRoute', - 'NamedRoute', - 'NamedEscapedRoute', -}; 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 deleted file mode 100644 index f0cb772581a..00000000000 --- a/packages/go_router_builder/test/test_inputs/_go_router_builder_test_input.dart +++ /dev/null @@ -1,470 +0,0 @@ -// 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:go_router/go_router.dart'; -import 'package:source_gen_test/annotations.dart'; - -@ShouldThrow('The @TypedGoRoute annotation can only be applied to classes.') -@TypedGoRoute(path: 'bob') // ignore: invalid_annotation_target -const int theAnswer = 42; - -// This test should be removed: dart enforces the `required` keyword. -@ShouldThrow('Missing `path` value on annotation.') -// ignore:missing_required_argument -@TypedGoRoute() -class MissingPathValue extends GoRouteData {} - -@ShouldThrow( - 'The @TypedGoRoute annotation can only be applied to classes that extend or ' - 'implement `GoRouteData`.', -) -@TypedGoRoute(path: 'bob') -class AppliedToWrongClassType {} - -@ShouldThrow( - 'The @TypedGoRoute annotation must have a type parameter that matches the ' - 'annotated element.', -) -@TypedGoRoute(path: 'bob') -class MissingTypeAnnotation extends GoRouteData {} - -@ShouldThrow( - 'Could not find a field for the path parameter "id".', -) -@TypedGoRoute(path: 'bob/:id') -class BadPathParam extends GoRouteData {} - -@ShouldThrow( - 'The parameter type `Stopwatch` is not supported.', -) -@TypedGoRoute(path: 'bob/:id') -class UnsupportedType extends GoRouteData { - UnsupportedType({required this.id}); - final Stopwatch id; -} - -@ShouldThrow( - 'Required parameters in the path cannot be nullable.', -) -@TypedGoRoute(path: 'bob/:id') -class NullableRequiredParamInPath extends GoRouteData { - NullableRequiredParamInPath({required this.id}); - final int? id; -} - -@ShouldGenerate(r''' -RouteBase get $nullableRequiredParamNotInPath => GoRouteData.$route( - path: 'bob', - factory: $NullableRequiredParamNotInPathExtension._fromState, - ); - -extension $NullableRequiredParamNotInPathExtension - on NullableRequiredParamNotInPath { - static NullableRequiredParamNotInPath _fromState(GoRouterState state) => - NullableRequiredParamNotInPath( - id: _$convertMapValue('id', state.queryParameters, int.parse), - ); - - String get location => GoRouteData.$location( - 'bob', - queryParams: { - if (id != null) 'id': id!.toString(), - }, - ); - - void go(BuildContext context) => context.go(location); - - Future push(BuildContext context) => context.push(location); - - void pushReplacement(BuildContext context) => - context.pushReplacement(location); - - void replace(BuildContext context) => context.replace(location); -} - -T? _$convertMapValue( - String key, - Map map, - T Function(String) converter, -) { - final value = map[key]; - return value == null ? null : converter(value); -} -''') -@TypedGoRoute(path: 'bob') -class NullableRequiredParamNotInPath extends GoRouteData { - NullableRequiredParamNotInPath({required this.id}); - final int? id; -} - -@ShouldGenerate(r''' -RouteBase get $nonNullableRequiredParamNotInPath => GoRouteData.$route( - path: 'bob', - factory: $NonNullableRequiredParamNotInPathExtension._fromState, - ); - -extension $NonNullableRequiredParamNotInPathExtension - on NonNullableRequiredParamNotInPath { - static NonNullableRequiredParamNotInPath _fromState(GoRouterState state) => - NonNullableRequiredParamNotInPath( - id: int.parse(state.queryParameters['id']!), - ); - - String get location => GoRouteData.$location( - 'bob', - queryParams: { - 'id': id.toString(), - }, - ); - - void go(BuildContext context) => context.go(location); - - Future push(BuildContext context) => context.push(location); - - void pushReplacement(BuildContext context) => - context.pushReplacement(location); - - void replace(BuildContext context) => context.replace(location); -} -''') -@TypedGoRoute(path: 'bob') -class NonNullableRequiredParamNotInPath extends GoRouteData { - NonNullableRequiredParamNotInPath({required this.id}); - final int id; -} - -@ShouldGenerate(r''' -RouteBase get $enumParam => GoRouteData.$route( - path: '/:y', - factory: $EnumParamExtension._fromState, - ); - -extension $EnumParamExtension on EnumParam { - static EnumParam _fromState(GoRouterState state) => EnumParam( - y: _$EnumTestEnumMap._$fromName(state.pathParameters['y']!), - ); - - String get location => GoRouteData.$location( - '/${Uri.encodeComponent(_$EnumTestEnumMap[y]!)}', - ); - - void go(BuildContext context) => context.go(location); - - Future push(BuildContext context) => context.push(location); - - void pushReplacement(BuildContext context) => - context.pushReplacement(location); - - void replace(BuildContext context) => context.replace(location); -} - -const _$EnumTestEnumMap = { - EnumTest.a: 'a', - EnumTest.b: 'b', - EnumTest.c: 'c', -}; - -extension on Map { - T _$fromName(String value) => - entries.singleWhere((element) => element.value == value).key; -} -''') -@TypedGoRoute(path: '/:y') -class EnumParam extends GoRouteData { - EnumParam({required this.y}); - final EnumTest y; -} - -enum EnumTest { - a(1), - b(3), - c(5); - - const EnumTest(this.x); - final int x; -} - -@ShouldGenerate(r''' -RouteBase get $defaultValueRoute => GoRouteData.$route( - path: '/default-value-route', - factory: $DefaultValueRouteExtension._fromState, - ); - -extension $DefaultValueRouteExtension on DefaultValueRoute { - static DefaultValueRoute _fromState(GoRouterState state) => DefaultValueRoute( - param: - _$convertMapValue('param', state.queryParameters, int.parse) ?? 0, - ); - - String get location => GoRouteData.$location( - '/default-value-route', - queryParams: { - if (param != 0) 'param': param.toString(), - }, - ); - - void go(BuildContext context) => context.go(location); - - Future push(BuildContext context) => context.push(location); - - void pushReplacement(BuildContext context) => - context.pushReplacement(location); - - void replace(BuildContext context) => context.replace(location); -} - -T? _$convertMapValue( - String key, - Map map, - T Function(String) converter, -) { - final value = map[key]; - return value == null ? null : converter(value); -} -''') -@TypedGoRoute(path: '/default-value-route') -class DefaultValueRoute extends GoRouteData { - DefaultValueRoute({this.param = 0}); - final int param; -} - -@ShouldGenerate(r''' -RouteBase get $extraValueRoute => GoRouteData.$route( - path: '/default-value-route', - factory: $ExtraValueRouteExtension._fromState, - ); - -extension $ExtraValueRouteExtension on ExtraValueRoute { - static ExtraValueRoute _fromState(GoRouterState state) => ExtraValueRoute( - param: - _$convertMapValue('param', state.queryParameters, int.parse) ?? 0, - $extra: state.extra as int?, - ); - - String get location => GoRouteData.$location( - '/default-value-route', - queryParams: { - if (param != 0) 'param': param.toString(), - }, - ); - - void go(BuildContext context) => context.go(location, extra: $extra); - - Future push(BuildContext context) => - context.push(location, extra: $extra); - - void pushReplacement(BuildContext context) => - context.pushReplacement(location, extra: $extra); - - void replace(BuildContext context) => - context.replace(location, extra: $extra); -} - -T? _$convertMapValue( - String key, - Map map, - T Function(String) converter, -) { - final value = map[key]; - return value == null ? null : converter(value); -} -''') -@TypedGoRoute(path: '/default-value-route') -class ExtraValueRoute extends GoRouteData { - ExtraValueRoute({this.param = 0, this.$extra}); - final int param; - final int? $extra; -} - -@ShouldGenerate(r''' -RouteBase get $requiredExtraValueRoute => GoRouteData.$route( - path: '/default-value-route', - factory: $RequiredExtraValueRouteExtension._fromState, - ); - -extension $RequiredExtraValueRouteExtension on RequiredExtraValueRoute { - static RequiredExtraValueRoute _fromState(GoRouterState state) => - RequiredExtraValueRoute( - $extra: state.extra as int, - ); - - String get location => GoRouteData.$location( - '/default-value-route', - ); - - void go(BuildContext context) => context.go(location, extra: $extra); - - Future push(BuildContext context) => - context.push(location, extra: $extra); - - void pushReplacement(BuildContext context) => - context.pushReplacement(location, extra: $extra); - - void replace(BuildContext context) => - context.replace(location, extra: $extra); -} -''') -@TypedGoRoute(path: '/default-value-route') -class RequiredExtraValueRoute extends GoRouteData { - RequiredExtraValueRoute({required this.$extra}); - final int $extra; -} - -@ShouldThrow( - 'Default value used with a nullable type. Only non-nullable type can have a default value.', - todo: 'Remove the default value or make the type non-nullable.', -) -@TypedGoRoute(path: '/nullable-default-value-route') -class NullableDefaultValueRoute extends GoRouteData { - NullableDefaultValueRoute({this.param = 0}); - final int? param; -} - -@ShouldGenerate(r''' -RouteBase get $iterableWithEnumRoute => GoRouteData.$route( - path: '/iterable-with-enum', - factory: $IterableWithEnumRouteExtension._fromState, - ); - -extension $IterableWithEnumRouteExtension on IterableWithEnumRoute { - static IterableWithEnumRoute _fromState(GoRouterState state) => - IterableWithEnumRoute( - param: state.queryParametersAll['param'] - ?.map(_$EnumOnlyUsedInIterableEnumMap._$fromName), - ); - - String get location => GoRouteData.$location( - '/iterable-with-enum', - queryParams: { - if (param != null) - 'param': - param?.map((e) => _$EnumOnlyUsedInIterableEnumMap[e]).toList(), - }, - ); - - void go(BuildContext context) => context.go(location); - - Future push(BuildContext context) => context.push(location); - - void pushReplacement(BuildContext context) => - context.pushReplacement(location); - - void replace(BuildContext context) => context.replace(location); -} - -const _$EnumOnlyUsedInIterableEnumMap = { - EnumOnlyUsedInIterable.a: 'a', - EnumOnlyUsedInIterable.b: 'b', - EnumOnlyUsedInIterable.c: 'c', -}; - -extension on Map { - T _$fromName(String value) => - entries.singleWhere((element) => element.value == value).key; -} -''') -@TypedGoRoute(path: '/iterable-with-enum') -class IterableWithEnumRoute extends GoRouteData { - IterableWithEnumRoute({this.param}); - - final Iterable? param; -} - -enum EnumOnlyUsedInIterable { - a, - b, - c, -} - -@ShouldGenerate(r''' -RouteBase get $iterableDefaultValueRoute => GoRouteData.$route( - path: '/iterable-default-value-route', - factory: $IterableDefaultValueRouteExtension._fromState, - ); - -extension $IterableDefaultValueRouteExtension on IterableDefaultValueRoute { - static IterableDefaultValueRoute _fromState(GoRouterState state) => - IterableDefaultValueRoute( - param: - state.queryParametersAll['param']?.map(int.parse) ?? const [0], - ); - - String get location => GoRouteData.$location( - '/iterable-default-value-route', - queryParams: { - if (param != const [0]) - 'param': param.map((e) => e.toString()).toList(), - }, - ); - - void go(BuildContext context) => context.go(location); - - Future push(BuildContext context) => context.push(location); - - void pushReplacement(BuildContext context) => - context.pushReplacement(location); - - void replace(BuildContext context) => context.replace(location); -} -''') -@TypedGoRoute(path: '/iterable-default-value-route') -class IterableDefaultValueRoute extends GoRouteData { - IterableDefaultValueRoute({this.param = const [0]}); - final Iterable param; -} - -@ShouldGenerate(r''' -RouteBase get $namedRoute => GoRouteData.$route( - path: '/named-route', - name: 'namedRoute', - factory: $NamedRouteExtension._fromState, - ); - -extension $NamedRouteExtension on NamedRoute { - static NamedRoute _fromState(GoRouterState state) => NamedRoute(); - - String get location => GoRouteData.$location( - '/named-route', - ); - - void go(BuildContext context) => context.go(location); - - Future push(BuildContext context) => context.push(location); - - void pushReplacement(BuildContext context) => - context.pushReplacement(location); - - void replace(BuildContext context) => context.replace(location); -} -''') -@TypedGoRoute(path: '/named-route', name: 'namedRoute') -class NamedRoute extends GoRouteData {} - -@ShouldGenerate(r''' -RouteBase get $namedEscapedRoute => GoRouteData.$route( - path: '/named-route', - name: r'named$Route', - factory: $NamedEscapedRouteExtension._fromState, - ); - -extension $NamedEscapedRouteExtension on NamedEscapedRoute { - static NamedEscapedRoute _fromState(GoRouterState state) => - NamedEscapedRoute(); - - String get location => GoRouteData.$location( - '/named-route', - ); - - void go(BuildContext context) => context.go(location); - - Future push(BuildContext context) => context.push(location); - - void pushReplacement(BuildContext context) => - context.pushReplacement(location); - - void replace(BuildContext context) => context.replace(location); -} -''') -@TypedGoRoute(path: '/named-route', name: r'named$Route') -class NamedEscapedRoute extends GoRouteData {} diff --git a/packages/go_router_builder/test_inputs/bad_path_pattern.dart b/packages/go_router_builder/test_inputs/bad_path_pattern.dart new file mode 100644 index 00000000000..b6c6012ecbb --- /dev/null +++ b/packages/go_router_builder/test_inputs/bad_path_pattern.dart @@ -0,0 +1,8 @@ +// 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:go_router/go_router.dart'; + +@TypedGoRoute(path: 'bob/:id') +class BadPathParam extends GoRouteData {} diff --git a/packages/go_router_builder/test_inputs/bad_path_pattern.dart.expect b/packages/go_router_builder/test_inputs/bad_path_pattern.dart.expect new file mode 100644 index 00000000000..2dc7bdc16e8 --- /dev/null +++ b/packages/go_router_builder/test_inputs/bad_path_pattern.dart.expect @@ -0,0 +1 @@ +Could not find a field for the path parameter "id". diff --git a/packages/go_router_builder/test_inputs/default_value.dart b/packages/go_router_builder/test_inputs/default_value.dart new file mode 100644 index 00000000000..209e7f4f52f --- /dev/null +++ b/packages/go_router_builder/test_inputs/default_value.dart @@ -0,0 +1,11 @@ +// 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:go_router/go_router.dart'; + +@TypedGoRoute(path: '/default-value-route') +class DefaultValueRoute extends GoRouteData { + DefaultValueRoute({this.param = 0}); + final int param; +} diff --git a/packages/go_router_builder/test_inputs/default_value.dart.expect b/packages/go_router_builder/test_inputs/default_value.dart.expect new file mode 100644 index 00000000000..364970d3f17 --- /dev/null +++ b/packages/go_router_builder/test_inputs/default_value.dart.expect @@ -0,0 +1,36 @@ +RouteBase get $defaultValueRoute => GoRouteData.$route( + path: '/default-value-route', + factory: $DefaultValueRouteExtension._fromState, + ); + +extension $DefaultValueRouteExtension on DefaultValueRoute { + static DefaultValueRoute _fromState(GoRouterState state) => DefaultValueRoute( + param: + _$convertMapValue('param', state.queryParameters, int.parse) ?? 0, + ); + + String get location => GoRouteData.$location( + '/default-value-route', + queryParams: { + if (param != 0) 'param': param.toString(), + }, + ); + + void go(BuildContext context) => context.go(location); + + Future push(BuildContext context) => context.push(location); + + void pushReplacement(BuildContext context) => + context.pushReplacement(location); + + void replace(BuildContext context) => context.replace(location); +} + +T? _$convertMapValue( + String key, + Map map, + T Function(String) converter, +) { + final value = map[key]; + return value == null ? null : converter(value); +} diff --git a/packages/go_router_builder/test_inputs/enum_parameter.dart b/packages/go_router_builder/test_inputs/enum_parameter.dart new file mode 100644 index 00000000000..72b41486b88 --- /dev/null +++ b/packages/go_router_builder/test_inputs/enum_parameter.dart @@ -0,0 +1,20 @@ +// 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:go_router/go_router.dart'; + +@TypedGoRoute(path: '/:y') +class EnumParam extends GoRouteData { + EnumParam({required this.y}); + final EnumTest y; +} + +enum EnumTest { + a(1), + b(3), + c(5); + + const EnumTest(this.x); + final int x; +} diff --git a/packages/go_router_builder/test_inputs/enum_parameter.dart.expect b/packages/go_router_builder/test_inputs/enum_parameter.dart.expect new file mode 100644 index 00000000000..c7dcba7146d --- /dev/null +++ b/packages/go_router_builder/test_inputs/enum_parameter.dart.expect @@ -0,0 +1,34 @@ +RouteBase get $enumParam => GoRouteData.$route( + path: '/:y', + factory: $EnumParamExtension._fromState, + ); + +extension $EnumParamExtension on EnumParam { + static EnumParam _fromState(GoRouterState state) => EnumParam( + y: _$EnumTestEnumMap._$fromName(state.pathParameters['y']!), + ); + + String get location => GoRouteData.$location( + '/${Uri.encodeComponent(_$EnumTestEnumMap[y]!)}', + ); + + void go(BuildContext context) => context.go(location); + + Future push(BuildContext context) => context.push(location); + + void pushReplacement(BuildContext context) => + context.pushReplacement(location); + + void replace(BuildContext context) => context.replace(location); +} + +const _$EnumTestEnumMap = { + EnumTest.a: 'a', + EnumTest.b: 'b', + EnumTest.c: 'c', +}; + +extension on Map { + T _$fromName(String value) => + entries.singleWhere((element) => element.value == value).key; +} diff --git a/packages/go_router_builder/test_inputs/extra_value.dart b/packages/go_router_builder/test_inputs/extra_value.dart new file mode 100644 index 00000000000..60e80917212 --- /dev/null +++ b/packages/go_router_builder/test_inputs/extra_value.dart @@ -0,0 +1,12 @@ +// 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:go_router/go_router.dart'; + +@TypedGoRoute(path: '/default-value-route') +class ExtraValueRoute extends GoRouteData { + ExtraValueRoute({this.param = 0, this.$extra}); + final int param; + final int? $extra; +} diff --git a/packages/go_router_builder/test_inputs/extra_value.dart.expect b/packages/go_router_builder/test_inputs/extra_value.dart.expect new file mode 100644 index 00000000000..acf8c2b785b --- /dev/null +++ b/packages/go_router_builder/test_inputs/extra_value.dart.expect @@ -0,0 +1,39 @@ +RouteBase get $extraValueRoute => GoRouteData.$route( + path: '/default-value-route', + factory: $ExtraValueRouteExtension._fromState, + ); + +extension $ExtraValueRouteExtension on ExtraValueRoute { + static ExtraValueRoute _fromState(GoRouterState state) => ExtraValueRoute( + param: + _$convertMapValue('param', state.queryParameters, int.parse) ?? 0, + $extra: state.extra as int?, + ); + + String get location => GoRouteData.$location( + '/default-value-route', + queryParams: { + if (param != 0) 'param': param.toString(), + }, + ); + + void go(BuildContext context) => context.go(location, extra: $extra); + + Future push(BuildContext context) => + context.push(location, extra: $extra); + + void pushReplacement(BuildContext context) => + context.pushReplacement(location, extra: $extra); + + void replace(BuildContext context) => + context.replace(location, extra: $extra); +} + +T? _$convertMapValue( + String key, + Map map, + T Function(String) converter, +) { + final value = map[key]; + return value == null ? null : converter(value); +} diff --git a/packages/go_router_builder/test_inputs/iterable_with_default_value.dart b/packages/go_router_builder/test_inputs/iterable_with_default_value.dart new file mode 100644 index 00000000000..3abd7a92318 --- /dev/null +++ b/packages/go_router_builder/test_inputs/iterable_with_default_value.dart @@ -0,0 +1,11 @@ +// 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:go_router/go_router.dart'; + +@TypedGoRoute(path: '/iterable-default-value-route') +class IterableDefaultValueRoute extends GoRouteData { + IterableDefaultValueRoute({this.param = const [0]}); + final Iterable param; +} diff --git a/packages/go_router_builder/test_inputs/iterable_with_default_value.dart.expect b/packages/go_router_builder/test_inputs/iterable_with_default_value.dart.expect new file mode 100644 index 00000000000..4bcda178441 --- /dev/null +++ b/packages/go_router_builder/test_inputs/iterable_with_default_value.dart.expect @@ -0,0 +1,29 @@ +RouteBase get $iterableDefaultValueRoute => GoRouteData.$route( + path: '/iterable-default-value-route', + factory: $IterableDefaultValueRouteExtension._fromState, + ); + +extension $IterableDefaultValueRouteExtension on IterableDefaultValueRoute { + static IterableDefaultValueRoute _fromState(GoRouterState state) => + IterableDefaultValueRoute( + param: + state.queryParametersAll['param']?.map(int.parse) ?? const [0], + ); + + String get location => GoRouteData.$location( + '/iterable-default-value-route', + queryParams: { + if (param != const [0]) + 'param': param.map((e) => e.toString()).toList(), + }, + ); + + void go(BuildContext context) => context.go(location); + + Future push(BuildContext context) => context.push(location); + + void pushReplacement(BuildContext context) => + context.pushReplacement(location); + + void replace(BuildContext context) => context.replace(location); +} diff --git a/packages/go_router_builder/test_inputs/iterable_with_enum.dart b/packages/go_router_builder/test_inputs/iterable_with_enum.dart new file mode 100644 index 00000000000..afdb7689c7b --- /dev/null +++ b/packages/go_router_builder/test_inputs/iterable_with_enum.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:go_router/go_router.dart'; + +@TypedGoRoute(path: '/iterable-with-enum') +class IterableWithEnumRoute extends GoRouteData { + IterableWithEnumRoute({this.param}); + + final Iterable? param; +} + +enum EnumOnlyUsedInIterable { + a, + b, + c, +} diff --git a/packages/go_router_builder/test_inputs/iterable_with_enum.dart.expect b/packages/go_router_builder/test_inputs/iterable_with_enum.dart.expect new file mode 100644 index 00000000000..20431568324 --- /dev/null +++ b/packages/go_router_builder/test_inputs/iterable_with_enum.dart.expect @@ -0,0 +1,41 @@ +RouteBase get $iterableWithEnumRoute => GoRouteData.$route( + path: '/iterable-with-enum', + factory: $IterableWithEnumRouteExtension._fromState, + ); + +extension $IterableWithEnumRouteExtension on IterableWithEnumRoute { + static IterableWithEnumRoute _fromState(GoRouterState state) => + IterableWithEnumRoute( + param: state.queryParametersAll['param'] + ?.map(_$EnumOnlyUsedInIterableEnumMap._$fromName), + ); + + String get location => GoRouteData.$location( + '/iterable-with-enum', + queryParams: { + if (param != null) + 'param': + param?.map((e) => _$EnumOnlyUsedInIterableEnumMap[e]).toList(), + }, + ); + + void go(BuildContext context) => context.go(location); + + Future push(BuildContext context) => context.push(location); + + void pushReplacement(BuildContext context) => + context.pushReplacement(location); + + void replace(BuildContext context) => context.replace(location); +} + +const _$EnumOnlyUsedInIterableEnumMap = { + EnumOnlyUsedInIterable.a: 'a', + EnumOnlyUsedInIterable.b: 'b', + EnumOnlyUsedInIterable.c: 'c', +}; + +extension on Map { + T _$fromName(String value) => + entries.singleWhere((element) => element.value == value).key; +} diff --git a/packages/go_router_builder/test_inputs/missing_type_annotation.dart b/packages/go_router_builder/test_inputs/missing_type_annotation.dart new file mode 100644 index 00000000000..2b3ff58dda5 --- /dev/null +++ b/packages/go_router_builder/test_inputs/missing_type_annotation.dart @@ -0,0 +1,8 @@ +// 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:go_router/go_router.dart'; + +@TypedGoRoute(path: 'bob') +class MissingTypeAnnotation extends GoRouteData {} diff --git a/packages/go_router_builder/test_inputs/missing_type_annotation.dart.expect b/packages/go_router_builder/test_inputs/missing_type_annotation.dart.expect new file mode 100644 index 00000000000..d82af829f66 --- /dev/null +++ b/packages/go_router_builder/test_inputs/missing_type_annotation.dart.expect @@ -0,0 +1 @@ +The @TypedGoRoute annotation must have a type parameter that matches the annotated element. diff --git a/packages/go_router_builder/test_inputs/named_escaped_route.dart b/packages/go_router_builder/test_inputs/named_escaped_route.dart new file mode 100644 index 00000000000..eb5e26bc563 --- /dev/null +++ b/packages/go_router_builder/test_inputs/named_escaped_route.dart @@ -0,0 +1,8 @@ +// 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:go_router/go_router.dart'; + +@TypedGoRoute(path: '/named-route', name: r'named$Route') +class NamedEscapedRoute extends GoRouteData {} diff --git a/packages/go_router_builder/test_inputs/named_escaped_route.dart.expect b/packages/go_router_builder/test_inputs/named_escaped_route.dart.expect new file mode 100644 index 00000000000..e4d75ec8163 --- /dev/null +++ b/packages/go_router_builder/test_inputs/named_escaped_route.dart.expect @@ -0,0 +1,23 @@ +RouteBase get $namedEscapedRoute => GoRouteData.$route( + path: '/named-route', + name: r'named$Route', + factory: $NamedEscapedRouteExtension._fromState, + ); + +extension $NamedEscapedRouteExtension on NamedEscapedRoute { + static NamedEscapedRoute _fromState(GoRouterState state) => + NamedEscapedRoute(); + + String get location => GoRouteData.$location( + '/named-route', + ); + + void go(BuildContext context) => context.go(location); + + Future push(BuildContext context) => context.push(location); + + void pushReplacement(BuildContext context) => + context.pushReplacement(location); + + void replace(BuildContext context) => context.replace(location); +} diff --git a/packages/go_router_builder/test_inputs/named_route.dart b/packages/go_router_builder/test_inputs/named_route.dart new file mode 100644 index 00000000000..3e45f7e476f --- /dev/null +++ b/packages/go_router_builder/test_inputs/named_route.dart @@ -0,0 +1,8 @@ +// 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:go_router/go_router.dart'; + +@TypedGoRoute(path: '/named-route', name: 'namedRoute') +class NamedRoute extends GoRouteData {} diff --git a/packages/go_router_builder/test_inputs/named_route.dart.expect b/packages/go_router_builder/test_inputs/named_route.dart.expect new file mode 100644 index 00000000000..9d4b705530c --- /dev/null +++ b/packages/go_router_builder/test_inputs/named_route.dart.expect @@ -0,0 +1,22 @@ +RouteBase get $namedRoute => GoRouteData.$route( + path: '/named-route', + name: 'namedRoute', + factory: $NamedRouteExtension._fromState, + ); + +extension $NamedRouteExtension on NamedRoute { + static NamedRoute _fromState(GoRouterState state) => NamedRoute(); + + String get location => GoRouteData.$location( + '/named-route', + ); + + void go(BuildContext context) => context.go(location); + + Future push(BuildContext context) => context.push(location); + + void pushReplacement(BuildContext context) => + context.pushReplacement(location); + + void replace(BuildContext context) => context.replace(location); +} diff --git a/packages/go_router_builder/test_inputs/nullable_default_value.dart b/packages/go_router_builder/test_inputs/nullable_default_value.dart new file mode 100644 index 00000000000..dcf2ea8e9f1 --- /dev/null +++ b/packages/go_router_builder/test_inputs/nullable_default_value.dart @@ -0,0 +1,11 @@ +// 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:go_router/go_router.dart'; + +@TypedGoRoute(path: '/nullable-default-value-route') +class NullableDefaultValueRoute extends GoRouteData { + NullableDefaultValueRoute({this.param = 0}); + final int? param; +} diff --git a/packages/go_router_builder/test_inputs/nullable_default_value.dart.expect b/packages/go_router_builder/test_inputs/nullable_default_value.dart.expect new file mode 100644 index 00000000000..46215c7a4b9 --- /dev/null +++ b/packages/go_router_builder/test_inputs/nullable_default_value.dart.expect @@ -0,0 +1 @@ +Default value used with a nullable type. Only non-nullable type can have a default value. diff --git a/packages/go_router_builder/test_inputs/required_extra_value.dart b/packages/go_router_builder/test_inputs/required_extra_value.dart new file mode 100644 index 00000000000..98d6e7a9469 --- /dev/null +++ b/packages/go_router_builder/test_inputs/required_extra_value.dart @@ -0,0 +1,11 @@ +// 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:go_router/go_router.dart'; + +@TypedGoRoute(path: '/default-value-route') +class RequiredExtraValueRoute extends GoRouteData { + RequiredExtraValueRoute({required this.$extra}); + final int $extra; +} diff --git a/packages/go_router_builder/test_inputs/required_extra_value.dart.expect b/packages/go_router_builder/test_inputs/required_extra_value.dart.expect new file mode 100644 index 00000000000..108cb124458 --- /dev/null +++ b/packages/go_router_builder/test_inputs/required_extra_value.dart.expect @@ -0,0 +1,26 @@ +RouteBase get $requiredExtraValueRoute => GoRouteData.$route( + path: '/default-value-route', + factory: $RequiredExtraValueRouteExtension._fromState, + ); + +extension $RequiredExtraValueRouteExtension on RequiredExtraValueRoute { + static RequiredExtraValueRoute _fromState(GoRouterState state) => + RequiredExtraValueRoute( + $extra: state.extra as int, + ); + + String get location => GoRouteData.$location( + '/default-value-route', + ); + + void go(BuildContext context) => context.go(location, extra: $extra); + + Future push(BuildContext context) => + context.push(location, extra: $extra); + + void pushReplacement(BuildContext context) => + context.pushReplacement(location, extra: $extra); + + void replace(BuildContext context) => + context.replace(location, extra: $extra); +} diff --git a/packages/go_router_builder/test_inputs/required_parameters_in_path_cannnot_be_null.dart b/packages/go_router_builder/test_inputs/required_parameters_in_path_cannnot_be_null.dart new file mode 100644 index 00000000000..17cadfb6453 --- /dev/null +++ b/packages/go_router_builder/test_inputs/required_parameters_in_path_cannnot_be_null.dart @@ -0,0 +1,11 @@ +// 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:go_router/go_router.dart'; + +@TypedGoRoute(path: 'bob/:id') +class NullableRequiredParamInPath extends GoRouteData { + NullableRequiredParamInPath({required this.id}); + final int? id; +} diff --git a/packages/go_router_builder/test_inputs/required_parameters_in_path_cannnot_be_null.dart.expect b/packages/go_router_builder/test_inputs/required_parameters_in_path_cannnot_be_null.dart.expect new file mode 100644 index 00000000000..933e5310dfd --- /dev/null +++ b/packages/go_router_builder/test_inputs/required_parameters_in_path_cannnot_be_null.dart.expect @@ -0,0 +1 @@ +Required parameters in the path cannot be nullable. diff --git a/packages/go_router_builder/test_inputs/required_parameters_not_in_path_can_be_null.dart b/packages/go_router_builder/test_inputs/required_parameters_not_in_path_can_be_null.dart new file mode 100644 index 00000000000..60c0c58cc1e --- /dev/null +++ b/packages/go_router_builder/test_inputs/required_parameters_not_in_path_can_be_null.dart @@ -0,0 +1,11 @@ +// 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:go_router/go_router.dart'; + +@TypedGoRoute(path: 'bob') +class NullableRequiredParamNotInPath extends GoRouteData { + NullableRequiredParamNotInPath({required this.id}); + final int? id; +} diff --git a/packages/go_router_builder/test_inputs/required_parameters_not_in_path_can_be_null.dart.expect b/packages/go_router_builder/test_inputs/required_parameters_not_in_path_can_be_null.dart.expect new file mode 100644 index 00000000000..eeaa458d24b --- /dev/null +++ b/packages/go_router_builder/test_inputs/required_parameters_not_in_path_can_be_null.dart.expect @@ -0,0 +1,37 @@ +RouteBase get $nullableRequiredParamNotInPath => GoRouteData.$route( + path: 'bob', + factory: $NullableRequiredParamNotInPathExtension._fromState, + ); + +extension $NullableRequiredParamNotInPathExtension + on NullableRequiredParamNotInPath { + static NullableRequiredParamNotInPath _fromState(GoRouterState state) => + NullableRequiredParamNotInPath( + id: _$convertMapValue('id', state.queryParameters, int.parse), + ); + + String get location => GoRouteData.$location( + 'bob', + queryParams: { + if (id != null) 'id': id!.toString(), + }, + ); + + void go(BuildContext context) => context.go(location); + + Future push(BuildContext context) => context.push(location); + + void pushReplacement(BuildContext context) => + context.pushReplacement(location); + + void replace(BuildContext context) => context.replace(location); +} + +T? _$convertMapValue( + String key, + Map map, + T Function(String) converter, +) { + final value = map[key]; + return value == null ? null : converter(value); +} diff --git a/packages/go_router_builder/test_inputs/required_query_parameter.dart b/packages/go_router_builder/test_inputs/required_query_parameter.dart new file mode 100644 index 00000000000..d5b5a814136 --- /dev/null +++ b/packages/go_router_builder/test_inputs/required_query_parameter.dart @@ -0,0 +1,11 @@ +// 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:go_router/go_router.dart'; + +@TypedGoRoute(path: 'bob') +class NonNullableRequiredParamNotInPath extends GoRouteData { + NonNullableRequiredParamNotInPath({required this.id}); + final int id; +} diff --git a/packages/go_router_builder/test_inputs/required_query_parameter.dart.expect b/packages/go_router_builder/test_inputs/required_query_parameter.dart.expect new file mode 100644 index 00000000000..7034c49a798 --- /dev/null +++ b/packages/go_router_builder/test_inputs/required_query_parameter.dart.expect @@ -0,0 +1,28 @@ +RouteBase get $nonNullableRequiredParamNotInPath => GoRouteData.$route( + path: 'bob', + factory: $NonNullableRequiredParamNotInPathExtension._fromState, + ); + +extension $NonNullableRequiredParamNotInPathExtension + on NonNullableRequiredParamNotInPath { + static NonNullableRequiredParamNotInPath _fromState(GoRouterState state) => + NonNullableRequiredParamNotInPath( + id: int.parse(state.queryParameters['id']!), + ); + + String get location => GoRouteData.$location( + 'bob', + queryParams: { + 'id': id.toString(), + }, + ); + + void go(BuildContext context) => context.go(location); + + Future push(BuildContext context) => context.push(location); + + void pushReplacement(BuildContext context) => + context.pushReplacement(location); + + void replace(BuildContext context) => context.replace(location); +} diff --git a/packages/go_router_builder/test_inputs/unsupported_type.dart b/packages/go_router_builder/test_inputs/unsupported_type.dart new file mode 100644 index 00000000000..55df2a674d1 --- /dev/null +++ b/packages/go_router_builder/test_inputs/unsupported_type.dart @@ -0,0 +1,11 @@ +// 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:go_router/go_router.dart'; + +@TypedGoRoute(path: 'bob/:id') +class UnsupportedType extends GoRouteData { + UnsupportedType({required this.id}); + final Stopwatch id; +} diff --git a/packages/go_router_builder/test_inputs/unsupported_type.dart.expect b/packages/go_router_builder/test_inputs/unsupported_type.dart.expect new file mode 100644 index 00000000000..c53b98f0b5a --- /dev/null +++ b/packages/go_router_builder/test_inputs/unsupported_type.dart.expect @@ -0,0 +1 @@ +The parameter type `Stopwatch` is not supported. diff --git a/packages/go_router_builder/test_inputs/wrong_class_type.dart b/packages/go_router_builder/test_inputs/wrong_class_type.dart new file mode 100644 index 00000000000..ae6c77bb852 --- /dev/null +++ b/packages/go_router_builder/test_inputs/wrong_class_type.dart @@ -0,0 +1,8 @@ +// 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:go_router/go_router.dart'; + +@TypedGoRoute(path: 'bob') +class AppliedToWrongClassType {} diff --git a/packages/go_router_builder/test_inputs/wrong_class_type.dart.expect b/packages/go_router_builder/test_inputs/wrong_class_type.dart.expect new file mode 100644 index 00000000000..50c40bd6c5f --- /dev/null +++ b/packages/go_router_builder/test_inputs/wrong_class_type.dart.expect @@ -0,0 +1 @@ +The @TypedGoRoute annotation can only be applied to classes that extend or implement `GoRouteData`. diff --git a/packages/go_router_builder/tool/run_tests.dart b/packages/go_router_builder/tool/run_tests.dart new file mode 100644 index 00000000000..39cdb4c7563 --- /dev/null +++ b/packages/go_router_builder/tool/run_tests.dart @@ -0,0 +1,61 @@ +// 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 'dart:io'; + +import 'package:analyzer/dart/element/element.dart'; +import 'package:build/build.dart'; +import 'package:build_test/build_test.dart'; +import 'package:dart_style/dart_style.dart' as dart_style; +import 'package:go_router_builder/src/go_router_generator.dart'; +import 'package:path/path.dart' as p; +import 'package:source_gen/source_gen.dart'; +import 'package:test/test.dart'; + +const GoRouterGenerator generator = GoRouterGenerator(); + +Future main() async { + final dart_style.DartFormatter formatter = dart_style.DartFormatter(); + final Directory dir = Directory('test_inputs'); + final List testFiles = dir + .listSync() + .whereType() + .where((File f) => f.path.endsWith('.dart')) + .toList(); + for (final File file in testFiles) { + final String fileName = file.path.split('/').last; + final File expectFile = File(p.join('${file.path}.expect')); + if (!expectFile.existsSync()) { + throw Exception('A text input must have a .expect file. ' + 'Found test input $fileName with out an expect file.'); + } + final String expectResult = expectFile.readAsStringSync().trim(); + test('verify $fileName', () async { + final String targetLibraryAssetId = '__test__|${file.path}'; + final LibraryElement element = await resolveSources( + { + targetLibraryAssetId: file.readAsStringSync(), + }, + (Resolver resolver) async { + final AssetId assetId = AssetId.parse(targetLibraryAssetId); + return resolver.libraryFor(assetId); + }, + ); + final LibraryReader reader = LibraryReader(element); + final Set results = {}; + try { + generator.generateForAnnotation(reader, results, {}); + } on InvalidGenerationSourceError catch (e) { + expect(expectResult, e.message.trim()); + return; + } + + final String generated = formatter + .format(results.join('\n\n')) + .trim() + .replaceAll('\r\n', '\n'); + expect(generated, equals(expectResult.replaceAll('\r\n', '\n'))); + }); + } +} diff --git a/script/configs/allowed_pinned_deps.yaml b/script/configs/allowed_pinned_deps.yaml index e1d7f549e03..c5ee0187b90 100644 --- a/script/configs/allowed_pinned_deps.yaml +++ b/script/configs/allowed_pinned_deps.yaml @@ -1,4 +1,4 @@ -# The list of external dependencies that are allowed as unpinned dependencies. +# The list of external dependencies that are allowed as pinned dependencies. # See https://github.com/flutter/flutter/wiki/Contributing-to-Plugins-and-Packages#Dependencies # # All entries here should have an explanation for why they are here. @@ -17,6 +17,7 @@ ## Allowed by default # Dart-owned +- dart_style - mockito # Google-owned diff --git a/script/configs/allowed_unpinned_deps.yaml b/script/configs/allowed_unpinned_deps.yaml index ddb3b897d8b..283f4f198e7 100644 --- a/script/configs/allowed_unpinned_deps.yaml +++ b/script/configs/allowed_unpinned_deps.yaml @@ -1,4 +1,4 @@ -# The list of external dependencies that are allowed as pinned dependencies. +# The list of external dependencies that are allowed as unpinned dependencies. # See https://github.com/flutter/flutter/wiki/Contributing-to-Plugins-and-Packages#Dependencies # # All entries here should have an explanation for why they are here, either @@ -20,7 +20,6 @@ - build_verify - google_maps - path_to_regexp -- source_gen_test - win32 ## Allowed by default @@ -32,6 +31,7 @@ - build - build_config - build_runner +- build_test - collection - convert - crypto