diff --git a/packages/go_router_builder/CHANGELOG.md b/packages/go_router_builder/CHANGELOG.md index b9ef60077e8..d2ad363cf89 100644 --- a/packages/go_router_builder/CHANGELOG.md +++ b/packages/go_router_builder/CHANGELOG.md @@ -1,6 +1,10 @@ -## NEXT +## 3.1.0 -- Restricts `build` to versions less than 2.5.0. +- Updates dependencies to use the latest `analyzer`, `build`, and `source_gen`. +- Updates dev dependencies to use the latest `build_test`. +- Migrates to the `element2` API. +- Improves test code formatting consistency. +- Updates minimum supported SDK version to Flutter 3.29/Dart 3.7. ## 3.0.1 diff --git a/packages/go_router_builder/README.md b/packages/go_router_builder/README.md index 91545bda047..62a8d94b3af 100644 --- a/packages/go_router_builder/README.md +++ b/packages/go_router_builder/README.md @@ -8,12 +8,12 @@ To use `go_router_builder`, you need to have the following dependencies in ```yaml dependencies: # ...along with your other dependencies - go_router: ^9.0.3 + go_router: ^16.0.0 dev_dependencies: # ...along with your other dev-dependencies - build_runner: ^2.0.0 - go_router_builder: ^2.3.0 + build_runner: ^2.6.0 + go_router_builder: ^3.1.0 ``` ### Source code diff --git a/packages/go_router_builder/lib/go_router_builder.dart b/packages/go_router_builder/lib/go_router_builder.dart index 2a4cc6bd541..f07aecb09ca 100644 --- a/packages/go_router_builder/lib/go_router_builder.dart +++ b/packages/go_router_builder/lib/go_router_builder.dart @@ -21,7 +21,5 @@ import 'src/go_router_generator.dart'; /// `go_router`. /// /// Not meant to be invoked by hand-authored code. -Builder goRouterBuilder(BuilderOptions options) => SharedPartBuilder( - const [GoRouterGenerator()], - 'go_router', - ); +Builder goRouterBuilder(BuilderOptions options) => + SharedPartBuilder(const [GoRouterGenerator()], 'go_router'); 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 88803b29e5c..c18631bdc70 100644 --- a/packages/go_router_builder/lib/src/go_router_generator.dart +++ b/packages/go_router_builder/lib/src/go_router_generator.dart @@ -4,7 +4,7 @@ import 'dart:async'; -import 'package:analyzer/dart/element/element.dart'; +import 'package:analyzer/dart/element/element2.dart'; import 'package:analyzer/dart/element/type.dart'; import 'package:build/build.dart'; import 'package:source_gen/source_gen.dart'; @@ -27,9 +27,10 @@ class GoRouterGenerator extends Generator { const GoRouterGenerator(); TypeChecker get _typeChecker => TypeChecker.any( - _annotations.keys.map((String annotation) => - TypeChecker.fromUrl('$_routeDataUrl#$annotation')), - ); + _annotations.keys.map( + (String annotation) => TypeChecker.fromUrl('$_routeDataUrl#$annotation'), + ), + ); @override FutureOr generate(LibraryReader library, BuildStep buildStep) async { @@ -61,40 +62,43 @@ ${getters.map((String e) => "$e,").join('\n')} Set values, Set getters, ) { - for (final AnnotatedElement annotatedElement - in library.annotatedWith(_typeChecker)) { + for (final AnnotatedElement annotatedElement in library.annotatedWith( + _typeChecker, + )) { final InfoIterable generatedValue = _generateForAnnotatedElement( annotatedElement.element, annotatedElement.annotation, ); getters.add(generatedValue.routeGetterName); - for (final String value in generatedValue) { - assert(value.length == value.trim().length); - values.add(value); - } + values.addAll(generatedValue.members); } } InfoIterable _generateForAnnotatedElement( - Element element, + Element2 element, ConstantReader annotation, ) { - final String typedAnnotation = - withoutNullability(annotation.objectValue.type!.getDisplayString()); - final String type = - typedAnnotation.substring(0, typedAnnotation.indexOf('<')); + final String typedAnnotation = withoutNullability( + annotation.objectValue.type!.getDisplayString(), + ); + final String type = typedAnnotation.substring( + 0, + typedAnnotation.indexOf('<'), + ); final String routeData = _annotations[type]!; - if (element is! ClassElement) { + if (element is! ClassElement2) { throw InvalidGenerationSourceError( 'The @$type annotation can only be applied to classes.', element: element, ); } - final TypeChecker dataChecker = - TypeChecker.fromUrl('$_routeDataUrl#$routeData'); - if (!element.allSupertypes - .any((InterfaceType element) => dataChecker.isExactlyType(element))) { + final TypeChecker dataChecker = TypeChecker.fromUrl( + '$_routeDataUrl#$routeData', + ); + if (!element.allSupertypes.any( + (InterfaceType element) => dataChecker.isExactlyType(element), + )) { throw InvalidGenerationSourceError( 'The @$type annotation can only be applied to classes that ' 'extend or implement `$routeData`.', @@ -102,7 +106,9 @@ ${getters.map((String e) => "$e,").join('\n')} ); } - return RouteBaseConfig.fromAnnotation(annotation, element) - .generateMembers(); + return RouteBaseConfig.fromAnnotation( + annotation, + element, + ).generateMembers(); } } diff --git a/packages/go_router_builder/lib/src/path_utils.dart b/packages/go_router_builder/lib/src/path_utils.dart index c47a96f89eb..9dc38ef515d 100644 --- a/packages/go_router_builder/lib/src/path_utils.dart +++ b/packages/go_router_builder/lib/src/path_utils.dart @@ -15,9 +15,9 @@ final RegExp _parameterRegExp = RegExp(r':(\w+)(\((?:\\.|[^\\()])+\))?'); /// final pathParameters = pathParametersFromPattern(pattern); // {'id', 'bookId'} /// ``` Set pathParametersFromPattern(String pattern) => { - for (final RegExpMatch match in _parameterRegExp.allMatches(pattern)) - match[1]!, - }; + for (final RegExpMatch match in _parameterRegExp.allMatches(pattern)) + match[1]!, +}; /// Reconstructs the full path from a [pattern] and path parameters. /// diff --git a/packages/go_router_builder/lib/src/route_config.dart b/packages/go_router_builder/lib/src/route_config.dart index d95663d15ac..5a157c289fc 100644 --- a/packages/go_router_builder/lib/src/route_config.dart +++ b/packages/go_router_builder/lib/src/route_config.dart @@ -6,7 +6,7 @@ import 'dart:collection'; import 'package:analyzer/dart/ast/ast.dart'; import 'package:analyzer/dart/constant/value.dart'; -import 'package:analyzer/dart/element/element.dart'; +import 'package:analyzer/dart/element/element2.dart'; import 'package:analyzer/dart/element/nullability_suffix.dart'; import 'package:analyzer/dart/element/type.dart'; import 'package:collection/collection.dart'; @@ -20,10 +20,7 @@ import 'type_helpers.dart'; /// Custom [Iterable] implementation with extra info. class InfoIterable extends IterableBase { - InfoIterable._({ - required this.members, - required this.routeGetterName, - }); + InfoIterable._({required this.members, required this.routeGetterName}); /// Name of the getter associated with `this`. final String routeGetterName; @@ -60,21 +57,21 @@ class ShellRouteConfig extends RouteBaseConfig { @override Iterable classDeclarations() { - if (routeDataClass.unnamedConstructor == null) { + if (routeDataClass.unnamedConstructor2 == null) { throw InvalidGenerationSourceError( 'The ShellRouteData "$_className" class must have an unnamed constructor.', element: routeDataClass, ); } - final bool isConst = routeDataClass.unnamedConstructor!.isConst; + final bool isConst = routeDataClass.unnamedConstructor2!.isConst; return [ ''' extension $_extensionName on $_className { static $_className _fromState(GoRouterState state) =>${isConst ? ' const' : ''} $_className(); } - ''' + ''', ]; } @@ -117,12 +114,12 @@ class StatefulShellRouteConfig extends RouteBaseConfig { @override Iterable classDeclarations() => [ - ''' + ''' extension $_extensionName on $_className { - static $_className _fromState(GoRouterState state) =>${routeDataClass.unnamedConstructor!.isConst ? ' const' : ''} $_className(); + static $_className _fromState(GoRouterState state) =>${routeDataClass.unnamedConstructor2!.isConst ? ' const' : ''} $_className(); } -''' - ]; +''', + ]; @override String get routeConstructorParameters => @@ -212,8 +209,9 @@ class GoRouteConfig extends RouteBaseConfig { /// The parent navigator key. final String? parentNavigatorKey; - late final Set _pathParams = - pathParametersFromPattern(_rawJoinedPath); + late final Set _pathParams = pathParametersFromPattern( + _rawJoinedPath, + ); String get _rawJoinedPath { final List pathSegments = []; @@ -258,8 +256,10 @@ class GoRouteConfig extends RouteBaseConfig { return "'$location'"; } - ParameterElement? get _extraParam => _ctor.parameters - .singleWhereOrNull((ParameterElement element) => element.isExtraField); + FormalParameterElement? get _extraParam => + _ctor.formalParameters.singleWhereOrNull( + (FormalParameterElement element) => element.isExtraField, + ); String get _fromStateConstructor { final StringBuffer buffer = StringBuffer('=>'); @@ -271,7 +271,7 @@ class GoRouteConfig extends RouteBaseConfig { } buffer.writeln('$_className('); - for (final ParameterElement param in [ + for (final FormalParameterElement param in [ ..._ctorParams, ..._ctorQueryParams, if (_extraParam != null) _extraParam!, @@ -293,10 +293,10 @@ class GoRouteConfig extends RouteBaseConfig { return '\n$_className get $selfFieldName => this as $_className;\n'; } - String _decodeFor(ParameterElement element) { + String _decodeFor(FormalParameterElement element) { if (element.isRequired) { if (element.type.nullabilitySuffix == NullabilitySuffix.question && - _pathParams.contains(element.name)) { + _pathParams.contains(element.displayName)) { throw InvalidGenerationSourceError( 'Required parameters in the path cannot be nullable.', element: element, @@ -310,7 +310,7 @@ class GoRouteConfig extends RouteBaseConfig { } if (element.isNamed) { - return '${element.name}: $fromStateExpression,'; + return '${element.displayName}: $fromStateExpression,'; } throw InvalidGenerationSourceError( @@ -320,7 +320,7 @@ class GoRouteConfig extends RouteBaseConfig { } String _encodeFor(String fieldName) { - final PropertyAccessorElement? field = _field(fieldName); + final PropertyAccessorElement2? field = _field(fieldName); if (field == null) { throw InvalidGenerationSourceError( 'Could not find a field for the path parameter "$fieldName".', @@ -338,8 +338,8 @@ class GoRouteConfig extends RouteBaseConfig { final StringBuffer buffer = StringBuffer('queryParams: {\n'); - for (final ParameterElement param in _ctorQueryParams) { - final String parameterName = param.name; + for (final FormalParameterElement param in _ctorQueryParams) { + final String parameterName = param.displayName; final List conditions = []; if (param.hasDefaultValue) { @@ -356,7 +356,8 @@ class GoRouteConfig extends RouteBaseConfig { if (conditions.isNotEmpty) { line = 'if (${conditions.join(' && ')}) '; } - line += '${escapeDartString(parameterName.kebab)}: ' + line += + '${escapeDartString(parameterName.kebab)}: ' '${_encodeFor(parameterName)},'; buffer.writeln(line); @@ -367,21 +368,25 @@ class GoRouteConfig extends RouteBaseConfig { return buffer.toString(); } - late final List _ctorParams = - _ctor.parameters.where((ParameterElement element) { - if (_pathParams.contains(element.name)) { - return true; - } - return false; - }).toList(); + late final List _ctorParams = + _ctor.formalParameters.where((FormalParameterElement element) { + if (_pathParams.contains(element.displayName)) { + return true; + } + return false; + }).toList(); - late final List _ctorQueryParams = _ctor.parameters - .where((ParameterElement element) => - !_pathParams.contains(element.name) && !element.isExtraField) - .toList(); + late final List _ctorQueryParams = + _ctor.formalParameters + .where( + (FormalParameterElement element) => + !_pathParams.contains(element.displayName) && + !element.isExtraField, + ) + .toList(); - ConstructorElement get _ctor { - final ConstructorElement? ctor = routeDataClass.unnamedConstructor; + ConstructorElement2 get _ctor { + final ConstructorElement2? ctor = routeDataClass.unnamedConstructor2; if (ctor == null) { throw InvalidGenerationSourceError( @@ -394,12 +399,13 @@ class GoRouteConfig extends RouteBaseConfig { @override Iterable classDeclarations() => [ - _mixinDefinition, - ..._enumDeclarations(), - ]; + _mixinDefinition, + ..._enumDeclarations(), + ]; String get _mixinDefinition { - final bool hasMixin = getNodeDeclaration(routeDataClass) + final bool hasMixin = + getNodeDeclaration(routeDataClass) ?.withClause ?.mixinTypes .any((NamedType e) => e.name2.toString() == _mixinName) ?? @@ -443,7 +449,7 @@ mixin $_mixinName on GoRouteData { Iterable _enumDeclarations() { final Set enumParamTypes = {}; - for (final ParameterElement ctorParam in [ + for (final FormalParameterElement ctorParam in [ ..._ctorParams, ..._ctorQueryParams, ]) { @@ -481,18 +487,18 @@ mixin $_mixinName on GoRouteData { /// Represents a `TypedGoRoute` annotation to the builder. abstract class RouteBaseConfig { - RouteBaseConfig._({ - required this.routeDataClass, - required this.parent, - }); + RouteBaseConfig._({required this.routeDataClass, required this.parent}); /// Creates a new [RouteBaseConfig] represented the annotation data in [reader]. factory RouteBaseConfig.fromAnnotation( ConstantReader reader, - InterfaceElement element, + InterfaceElement2 element, ) { - final RouteBaseConfig definition = - RouteBaseConfig._fromAnnotation(reader, element, null); + final RouteBaseConfig definition = RouteBaseConfig._fromAnnotation( + reader, + element, + null, + ); if (element != definition.routeDataClass) { throw InvalidGenerationSourceError( @@ -507,7 +513,7 @@ abstract class RouteBaseConfig { factory RouteBaseConfig._fromAnnotation( ConstantReader reader, - InterfaceElement element, + InterfaceElement2 element, RouteBaseConfig? parent, ) { assert(!reader.isNull, 'reader should not be null'); @@ -523,7 +529,7 @@ abstract class RouteBaseConfig { } // TODO(kevmoo): validate that this MUST be a subtype of `GoRouteData` - final InterfaceElement classElement = typeParamType.element; + final InterfaceElement2 classElement = typeParamType.element3; final RouteBaseConfig value; switch (typeName) { @@ -615,11 +621,18 @@ abstract class RouteBaseConfig { throw UnsupportedError('Unrecognized type $typeName'); } - value._children.addAll(reader - .read(_generateChildrenGetterName(typeName)) - .listValue - .map((DartObject e) => RouteBaseConfig._fromAnnotation( - ConstantReader(e), element, value))); + value._children.addAll( + reader + .read(_generateChildrenGetterName(typeName)) + .listValue + .map( + (DartObject e) => RouteBaseConfig._fromAnnotation( + ConstantReader(e), + element, + value, + ), + ), + ); return value; } @@ -627,7 +640,7 @@ abstract class RouteBaseConfig { final List _children = []; /// The `RouteData` class this class represents. - final InterfaceElement routeDataClass; + final InterfaceElement2 routeDataClass; /// The parent of this route config. final RouteBaseConfig? parent; @@ -639,61 +652,63 @@ abstract class RouteBaseConfig { : 'routes'; } - static String? _generateParameterGetterCode(InterfaceElement classElement, - {required String parameterName}) { - final String? fieldDisplayName = classElement.fields - .where((FieldElement element) { - if (!element.isStatic || element.name != parameterName) { - return false; - } - if (parameterName - .toLowerCase() - .contains(RegExp('navigatorKey | observers'))) { - final DartType type = element.type; - if (type is! ParameterizedType) { - return false; - } - final List typeArguments = type.typeArguments; - if (typeArguments.length != 1) { - return false; - } - final DartType typeArgument = typeArguments.single; - if (withoutNullability(typeArgument.getDisplayString()) != - 'NavigatorState') { - return false; - } - } - return true; - }) - .map((FieldElement e) => e.displayName) - .firstOrNull; + static String? _generateParameterGetterCode( + InterfaceElement2 classElement, { + required String parameterName, + }) { + final String? fieldDisplayName = + classElement.fields2 + .where((FieldElement2 element) { + if (!element.isStatic || element.displayName != parameterName) { + return false; + } + if (parameterName.toLowerCase().contains( + RegExp('navigatorKey | observers'), + )) { + final DartType type = element.type; + if (type is! ParameterizedType) { + return false; + } + final List typeArguments = type.typeArguments; + if (typeArguments.length != 1) { + return false; + } + final DartType typeArgument = typeArguments.single; + if (withoutNullability(typeArgument.getDisplayString()) != + 'NavigatorState') { + return false; + } + } + return true; + }) + .map((FieldElement2 e) => e.displayName) + .firstOrNull; if (fieldDisplayName != null) { - return '${classElement.name}.$fieldDisplayName'; + return '${classElement.displayName}.$fieldDisplayName'; } - final String? methodDisplayName = classElement.methods - .where((MethodElement element) { - return element.isStatic && element.name == parameterName; - }) - .map((MethodElement e) => e.displayName) - .firstOrNull; + final String? methodDisplayName = + classElement.methods2 + .where((MethodElement2 element) { + return element.isStatic && element.displayName == parameterName; + }) + .map((MethodElement2 e) => e.displayName) + .firstOrNull; if (methodDisplayName != null) { - return '${classElement.name}.$methodDisplayName'; + return '${classElement.displayName}.$methodDisplayName'; } return null; } /// Generates all of the members that correspond to `this`. InfoIterable generateMembers() => InfoIterable._( - members: _generateMembers().toList(), - routeGetterName: _routeGetterName, - ); + members: _generateMembers().toList(), + routeGetterName: _routeGetterName, + ); Iterable _generateMembers() sync* { - final List items = [ - _rootDefinition(), - ]; + final List items = [_rootDefinition()]; for (final RouteBaseConfig def in _flatten()) { items.addAll(def.classDeclarations()); @@ -705,7 +720,8 @@ abstract class RouteBaseConfig { .expand( (String e) => helperNames.entries .where( - (MapEntry element) => e.contains(element.key)) + (MapEntry element) => e.contains(element.key), + ) .map((MapEntry e) => e.value), ) .toSet(); @@ -727,16 +743,17 @@ abstract class RouteBaseConfig { RouteBase get $_routeGetterName => ${_invokesRouteConstructor()}; '''; - String get _className => routeDataClass.name; + String get _className => routeDataClass.displayName; String get _mixinName => '_\$$_className'; String get _extensionName => '\$${_className}Extension'; String _invokesRouteConstructor() { - final String routesBit = _children.isEmpty - ? '' - : ''' + final String routesBit = + _children.isEmpty + ? '' + : ''' ${_generateChildrenGetterName(routeDataClassName)}: [${_children.map((RouteBaseConfig e) => '${e._invokesRouteConstructor()},').join()}], '''; @@ -749,8 +766,8 @@ $routeDataClassName.$dataConvertionFunctionName( '''; } - PropertyAccessorElement? _field(String name) => - routeDataClass.getGetter(name); + PropertyAccessorElement2? _field(String name) => + routeDataClass.getGetter2(name); /// The name of `RouteData` subclass this configuration represents. @protected @@ -780,10 +797,11 @@ String _enumMapConst(InterfaceType type) { final StringBuffer buffer = StringBuffer('const ${enumMapName(type)} = {'); - for (final FieldElement enumField in type.element.fields - .where((FieldElement element) => element.isEnumConstant)) { + for (final FieldElement2 enumField in type.element3.fields2.where( + (FieldElement2 element) => element.isEnumConstant, + )) { buffer.writeln( - '$enumName.${enumField.name}: ${escapeDartString(enumField.name.kebab)},', + '$enumName.${enumField.displayName}: ${escapeDartString(enumField.displayName.kebab)},', ); } diff --git a/packages/go_router_builder/lib/src/type_helpers.dart b/packages/go_router_builder/lib/src/type_helpers.dart index 5f809c335d3..4ac62d2c278 100644 --- a/packages/go_router_builder/lib/src/type_helpers.dart +++ b/packages/go_router_builder/lib/src/type_helpers.dart @@ -5,7 +5,7 @@ import 'package:analyzer/dart/analysis/results.dart'; import 'package:analyzer/dart/analysis/session.dart'; import 'package:analyzer/dart/ast/ast.dart'; -import 'package:analyzer/dart/element/element.dart'; +import 'package:analyzer/dart/element/element2.dart'; import 'package:analyzer/dart/element/type.dart'; import 'package:source_gen/source_gen.dart'; import 'package:source_helper/source_helper.dart'; @@ -53,7 +53,10 @@ const List<_TypeHelper> _helpers = <_TypeHelper>[ /// Returns the decoded [String] value for [element], if its type is supported. /// /// Otherwise, throws an [InvalidGenerationSourceError]. -String decodeParameter(ParameterElement element, Set pathParameters) { +String decodeParameter( + FormalParameterElement element, + Set pathParameters, +) { if (element.isExtraField) { return 'state.${_stateValueAccess(element, pathParameters)}'; } @@ -82,11 +85,13 @@ String decodeParameter(ParameterElement element, Set pathParameters) { /// Returns the encoded [String] value for [element], if its type is supported. /// /// Otherwise, throws an [InvalidGenerationSourceError]. -String encodeField(PropertyAccessorElement element) { +String encodeField(PropertyAccessorElement2 element) { for (final _TypeHelper helper in _helpers) { if (helper._matchesType(element.returnType)) { return helper._encode( - '$selfFieldName.${element.name}', element.returnType); + '$selfFieldName.${element.displayName}', + element.returnType, + ); } } @@ -97,16 +102,17 @@ String encodeField(PropertyAccessorElement element) { } /// Returns an AstNode type from a InterfaceElement. -T? getNodeDeclaration(InterfaceElement element) { +T? getNodeDeclaration(InterfaceElement2 element) { final AnalysisSession? session = element.session; if (session == null) { return null; } final ParsedLibraryResult parsedLibrary = - session.getParsedLibraryByElement(element.library) as ParsedLibraryResult; - final ElementDeclarationResult? declaration = - parsedLibrary.getElementDeclaration(element); + session.getParsedLibraryByElement2(element.library2) + as ParsedLibraryResult; + final FragmentDeclarationResult? declaration = parsedLibrary + .getFragmentDeclaration(element.firstFragment); final AstNode? node = declaration?.node; return node is T ? node : null; @@ -115,11 +121,17 @@ T? getNodeDeclaration(InterfaceElement element) { /// Returns the comparison of a parameter with its default value. /// /// Otherwise, throws an [InvalidGenerationSourceError]. -String compareField(ParameterElement param, String value1, String value2) { +String compareField( + FormalParameterElement param, + String value1, + String value2, +) { for (final _TypeHelper helper in _helpers) { if (helper._matchesType(param.type)) { return helper._compare( - '$selfFieldName.${param.name}', param.defaultValueCode!); + '$selfFieldName.${param.displayName}', + param.defaultValueCode!, + ); } } @@ -132,7 +144,10 @@ String compareField(ParameterElement param, String value1, String value2) { /// Gets the name of the `const` map generated to help encode [Enum] types. String enumMapName(InterfaceType type) => '_\$${type.element.name}EnumMap'; -String _stateValueAccess(ParameterElement element, Set pathParameters) { +String _stateValueAccess( + FormalParameterElement element, + Set pathParameters, +) { if (element.isExtraField) { // ignore: avoid_redundant_argument_values return 'extra as ${element.type.getDisplayString()}'; @@ -141,11 +156,11 @@ String _stateValueAccess(ParameterElement element, Set pathParameters) { late String access; final String suffix = !element.type.isNullableType && !element.hasDefaultValue ? '!' : ''; - if (pathParameters.contains(element.name)) { - access = 'pathParameters[${escapeDartString(element.name)}]$suffix'; + if (pathParameters.contains(element.displayName)) { + access = 'pathParameters[${escapeDartString(element.displayName)}]$suffix'; } else { access = - 'uri.queryParameters[${escapeDartString(element.name.kebab)}]$suffix'; + 'uri.queryParameters[${escapeDartString(element.displayName.kebab)}]$suffix'; } return access; @@ -165,7 +180,10 @@ abstract class _TypeHelper { const _TypeHelper(); /// Decodes the value from its string representation in the URL. - String _decode(ParameterElement parameterElement, Set pathParameters); + String _decode( + FormalParameterElement parameterElement, + Set pathParameters, + ); /// Encodes the value from its string representation in the URL. String _encode(String fieldName, DartType type); @@ -306,8 +324,9 @@ class _TypeHelperString extends _TypeHelper { @override String _decode( - ParameterElement parameterElement, Set pathParameters) => - 'state.${_stateValueAccess(parameterElement, pathParameters)}'; + FormalParameterElement parameterElement, + Set pathParameters, + ) => 'state.${_stateValueAccess(parameterElement, pathParameters)}'; @override String _encode(String fieldName, DartType type) => fieldName; @@ -344,7 +363,9 @@ class _TypeHelperIterable extends _TypeHelperWithHelper { @override String _decode( - ParameterElement parameterElement, Set pathParameters) { + FormalParameterElement parameterElement, + Set pathParameters, + ) { if (parameterElement.type is ParameterizedType) { final DartType iterableType = (parameterElement.type as ParameterizedType).typeArguments.first; @@ -378,15 +399,17 @@ class _TypeHelperIterable extends _TypeHelperWithHelper { // get correct type for iterable String iterableCaster = ''; String fallBack = ''; - if (const TypeChecker.fromRuntime(List) - .isAssignableFromType(parameterElement.type)) { + if (const TypeChecker.fromRuntime( + List, + ).isAssignableFromType(parameterElement.type)) { iterableCaster += '?.toList()'; if (!parameterElement.type.isNullableType && !parameterElement.hasDefaultValue) { fallBack = '?? const []'; } - } else if (const TypeChecker.fromRuntime(Set) - .isAssignableFromType(parameterElement.type)) { + } else if (const TypeChecker.fromRuntime( + Set, + ).isAssignableFromType(parameterElement.type)) { iterableCaster += '?.toSet()'; if (!parameterElement.type.isNullableType && !parameterElement.hasDefaultValue) { @@ -396,11 +419,11 @@ class _TypeHelperIterable extends _TypeHelperWithHelper { return ''' (state.uri.queryParametersAll[ - ${escapeDartString(parameterElement.name.kebab)}] + ${escapeDartString(parameterElement.displayName.kebab)}] ?.map($entriesTypeDecoder)$convertToNotNull)$iterableCaster$fallBack'''; } return ''' -state.uri.queryParametersAll[${escapeDartString(parameterElement.name.kebab)}]'''; +state.uri.queryParametersAll[${escapeDartString(parameterElement.displayName.kebab)}]'''; } @override @@ -441,9 +464,11 @@ abstract class _TypeHelperWithHelper extends _TypeHelper { @override String _decode( - ParameterElement parameterElement, Set pathParameters) { + FormalParameterElement parameterElement, + Set pathParameters, + ) { final DartType paramType = parameterElement.type; - final String parameterName = parameterElement.name; + final String parameterName = parameterElement.displayName; if (!pathParameters.contains(parameterName) && (paramType.isNullableType || parameterElement.hasDefaultValue)) { @@ -463,23 +488,22 @@ extension DartTypeExtension on DartType { String get ensureNotNull => isNullableType ? '!' : ''; } -/// Extension helpers on [ParameterElement]. -extension ParameterElementExtension on ParameterElement { +/// Extension helpers on [FormalParameterElement]. +extension FormalParameterElementExtension on FormalParameterElement { /// Convenient helper on top of [isRequiredPositional] and [isRequiredNamed]. bool get isRequired => isRequiredPositional || isRequiredNamed; /// Returns `true` if `this` has a name that matches [extraFieldName]; - bool get isExtraField => name == extraFieldName; + bool get isExtraField => displayName == extraFieldName; } /// An error thrown when a default value is used with a nullable type. class NullableDefaultValueError extends InvalidGenerationSourceError { /// An error thrown when a default value is used with a nullable type. - NullableDefaultValueError( - Element element, - ) : super( - '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.', - element: element, - ); + NullableDefaultValueError(Element2 element) + : super( + '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.', + element: element, + ); } diff --git a/packages/go_router_builder/pubspec.yaml b/packages/go_router_builder/pubspec.yaml index 7d7dde0c893..acac5fed4f8 100644 --- a/packages/go_router_builder/pubspec.yaml +++ b/packages/go_router_builder/pubspec.yaml @@ -2,34 +2,31 @@ name: go_router_builder description: >- A builder that supports generated strongly-typed route helpers for package:go_router -version: 3.0.1 +version: 3.1.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 environment: - sdk: ^3.6.0 - flutter: ">=3.27.0" + sdk: ^3.7.0 + flutter: ">=3.29.0" dependencies: - analyzer: ">=6.9.0 <8.0.0" + analyzer: ">=7.4.0 <8.0.0" async: ^2.8.0 - # The upper bound is to work around a breaking change in `build` that was not - # versioned according to semver, and broke `build_test` 2.x. See - # https://github.com/flutter/flutter/issues/170832 - build: ">=2.0.0 <2.5.0" + build: ^3.0.0 build_config: ^1.0.0 collection: ^1.14.0 meta: ^1.7.0 path: ^1.8.0 - source_gen: ">=1.4.0 <3.0.0" + source_gen: ^3.0.0 source_helper: ^1.3.4 dev_dependencies: - build_test: ^2.1.7 + build_test: ^3.3.0 dart_style: '>=2.3.7 <4.0.0' flutter: sdk: flutter - go_router: ^15.1.0 + go_router: ^16.0.0 leak_tracker_flutter_testing: ">=3.0.0" package_config: ^2.1.1 pub_semver: ^2.1.5 diff --git a/packages/go_router_builder/test/path_utils_test.dart b/packages/go_router_builder/test/path_utils_test.dart index fdd62bac0fa..7d9ccd1c291 100644 --- a/packages/go_router_builder/test/path_utils_test.dart +++ b/packages/go_router_builder/test/path_utils_test.dart @@ -24,15 +24,20 @@ void main() { expect(patternToPath('/', const {}), '/'); expect(patternToPath('/user', const {}), '/user'); expect( - patternToPath('/user/:id', const {'id': 'user-id'}), - '/user/user-id'); + patternToPath('/user/:id', const {'id': 'user-id'}), + '/user/user-id', + ); expect( - patternToPath( - '/user/:id/book', const {'id': 'user-id'}), - '/user/user-id/book'); + patternToPath('/user/:id/book', const { + 'id': 'user-id', + }), + '/user/user-id/book', + ); expect( - patternToPath('/user/:id/book/:bookId', - const {'id': 'user-id', 'bookId': 'book-id'}), + patternToPath('/user/:id/book/:bookId', const { + 'id': 'user-id', + 'bookId': 'book-id', + }), '/user/user-id/book/book-id', ); }); diff --git a/packages/go_router_builder/test_inputs/iterable_with_enum.dart b/packages/go_router_builder/test_inputs/iterable_with_enum.dart index 0c0543ca289..e1b88984aa9 100644 --- a/packages/go_router_builder/test_inputs/iterable_with_enum.dart +++ b/packages/go_router_builder/test_inputs/iterable_with_enum.dart @@ -13,8 +13,4 @@ class IterableWithEnumRoute extends GoRouteData with _$IterableWithEnumRoute { final Iterable? param; } -enum EnumOnlyUsedInIterable { - a, - b, - c, -} +enum EnumOnlyUsedInIterable { a, b, c } diff --git a/packages/go_router_builder/test_inputs/required_nullable_type_arguments_extra_value.dart b/packages/go_router_builder/test_inputs/required_nullable_type_arguments_extra_value.dart index 88b1deeba68..b1da22510db 100644 --- a/packages/go_router_builder/test_inputs/required_nullable_type_arguments_extra_value.dart +++ b/packages/go_router_builder/test_inputs/required_nullable_type_arguments_extra_value.dart @@ -7,7 +7,8 @@ import 'package:go_router/go_router.dart'; mixin _$RequiredNullableTypeArgumentsExtraValueRoute {} @TypedGoRoute( - path: '/default-value-route') + path: '/default-value-route', +) class RequiredNullableTypeArgumentsExtraValueRoute extends GoRouteData with _$RequiredNullableTypeArgumentsExtraValueRoute { RequiredNullableTypeArgumentsExtraValueRoute({required this.$extra}); diff --git a/packages/go_router_builder/tool/run_tests.dart b/packages/go_router_builder/tool/run_tests.dart index 6c0e55286a6..02ec8083270 100644 --- a/packages/go_router_builder/tool/run_tests.dart +++ b/packages/go_router_builder/tool/run_tests.dart @@ -5,7 +5,7 @@ import 'dart:io'; import 'dart:isolate'; -import 'package:analyzer/dart/element/element.dart'; +import 'package:analyzer/dart/element/element2.dart'; import 'package:build/build.dart'; import 'package:build_test/build_test.dart'; import 'package:dart_style/dart_style.dart' as dart_style; @@ -19,32 +19,37 @@ import 'package:test/test.dart'; const GoRouterGenerator generator = GoRouterGenerator(); Future main() async { - final dart_style.DartFormatter formatter = - dart_style.DartFormatter(languageVersion: await _packageVersion()); + final dart_style.DartFormatter formatter = dart_style.DartFormatter( + languageVersion: await _packageVersion(), + ); final Directory dir = Directory('test_inputs'); - final List testFiles = dir - .listSync() - .whereType() - .where((File f) => f.path.endsWith('.dart')) - .toList(); + 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.'); + 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(), - }, + // Normalize path separators for cross-platform compatibility + final String path = file.path.replaceAll(r'\', '/'); + final String targetLibraryAssetId = '__test__|$path'; + final LibraryElement2 element = await resolveSources( + {targetLibraryAssetId: file.readAsStringSync()}, (Resolver resolver) async { final AssetId assetId = AssetId.parse(targetLibraryAssetId); return resolver.libraryFor(assetId); }, + readAllSourcesFromFilesystem: true, ); final LibraryReader reader = LibraryReader(element); final Set results = {}; @@ -55,18 +60,22 @@ Future main() async { return; } - final String generated = formatter - .format(results.join('\n\n')) - .trim() - .replaceAll('\r\n', '\n'); - expect(generated, equals(expectResult.replaceAll('\r\n', '\n'))); + // Apply consistent formatting to both generated and expected code for comparison. + final String generated = formatter.format( + results.join().replaceAll('\n', ''), + ); + final String expected = formatter.format( + expectResult.replaceAll('\n', ''), + ); + expect(generated, equals(expected)); }, timeout: const Timeout(Duration(seconds: 100))); } } Future _packageVersion() async { - final PackageConfig packageConfig = - await loadPackageConfigUri(Isolate.packageConfigSync!); + final PackageConfig packageConfig = await loadPackageConfigUri( + Isolate.packageConfigSync!, + ); final Uri pkgUri = Platform.script.resolve('../pubspec.yaml'); final Package? package = packageConfig.packageOf(pkgUri); if (package == null) {