| // 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:analyzer/dart/element/element.dart'; |
| import 'package:analyzer/dart/element/type.dart'; |
| import 'package:source_gen/source_gen.dart'; |
| import 'package:source_helper/source_helper.dart'; |
| |
| /// The name of the generated, private helper for converting [String] to [bool]. |
| const String boolConverterHelperName = r'_$boolConverter'; |
| |
| /// The name of the generated, private helper for handling nullable value |
| /// conversion. |
| const String convertMapValueHelperName = r'_$convertMapValue'; |
| |
| /// The name of the generated, private helper for converting [Duration] to |
| /// [bool]. |
| const String durationDecoderHelperName = r'_$duractionConverter'; |
| |
| /// The name of the generated, private helper for converting [String] to [Enum]. |
| const String enumExtensionHelperName = r'_$fromName'; |
| |
| /// The property/parameter name used to represent the `extra` data that may |
| /// be passed to a route. |
| const String extraFieldName = r'$extra'; |
| |
| /// Shared start of error message related to a likely code issue. |
| const String likelyIssueMessage = 'Should never get here! File an issue!'; |
| |
| const List<_TypeHelper> _helpers = <_TypeHelper>[ |
| _TypeHelperBigInt(), |
| _TypeHelperBool(), |
| _TypeHelperDateTime(), |
| _TypeHelperDouble(), |
| _TypeHelperEnum(), |
| _TypeHelperInt(), |
| _TypeHelperNum(), |
| _TypeHelperString(), |
| _TypeHelperUri(), |
| ]; |
| |
| /// Returns the decoded [String] value for [element], if its type is supported. |
| /// |
| /// Otherwise, throws an [InvalidGenerationSourceError]. |
| String decodeParameter(ParameterElement element) { |
| if (element.isExtraField) { |
| return 'state.${_stateValueAccess(element)}'; |
| } |
| |
| final DartType paramType = element.type; |
| for (final _TypeHelper helper in _helpers) { |
| if (helper._matchesType(paramType)) { |
| return helper._decode(element); |
| } |
| } |
| |
| throw InvalidGenerationSourceError( |
| 'The parameter type ' |
| '`${paramType.getDisplayString(withNullability: false)}` is not supported.', |
| element: element, |
| ); |
| } |
| |
| /// Returns the encoded [String] value for [element], if its type is supported. |
| /// |
| /// Otherwise, throws an [InvalidGenerationSourceError]. |
| String encodeField(PropertyAccessorElement element) { |
| for (final _TypeHelper helper in _helpers) { |
| if (helper._matchesType(element.returnType)) { |
| return helper._encode(element.name, element.returnType); |
| } |
| } |
| |
| throw InvalidGenerationSourceError( |
| 'The return type `${element.returnType}` is not supported.', |
| element: element, |
| ); |
| } |
| |
| /// Gets the name of the `const` map generated to help encode [Enum] types. |
| // TODO(stuartmorgan): Remove this ignore once 'analyze' can be set to |
| // 5.2+ (when Flutter 3.4+ is on stable). |
| // ignore: deprecated_member_use |
| String enumMapName(InterfaceType type) => '_\$${type.element.name}EnumMap'; |
| |
| String _stateValueAccess(ParameterElement element) { |
| if (element.isRequired) { |
| return 'params[${escapeDartString(element.name)}]!'; |
| } |
| |
| if (element.isExtraField) { |
| return 'extra as ${element.type.getDisplayString(withNullability: true)}'; |
| } |
| |
| if (element.isOptional) { |
| return 'queryParams[${escapeDartString(element.name.kebab)}]'; |
| } |
| |
| throw InvalidGenerationSourceError( |
| '$likelyIssueMessage (param not required or optional)', |
| element: element, |
| ); |
| } |
| |
| abstract class _TypeHelper { |
| const _TypeHelper(); |
| |
| String _decode(ParameterElement parameterElement); |
| |
| String _encode(String fieldName, DartType type); |
| |
| bool _matchesType(DartType type); |
| } |
| |
| class _TypeHelperBigInt extends _TypeHelperWithHelper { |
| const _TypeHelperBigInt(); |
| |
| @override |
| String helperName(DartType paramType) => 'BigInt.parse'; |
| |
| @override |
| String _encode(String fieldName, DartType type) => |
| '$fieldName${type.ensureNotNull}.toString()'; |
| |
| @override |
| bool _matchesType(DartType type) => |
| const TypeChecker.fromRuntime(BigInt).isAssignableFromType(type); |
| } |
| |
| class _TypeHelperBool extends _TypeHelperWithHelper { |
| const _TypeHelperBool(); |
| |
| @override |
| String helperName(DartType paramType) => boolConverterHelperName; |
| |
| @override |
| String _encode(String fieldName, DartType type) => |
| '$fieldName${type.ensureNotNull}.toString()'; |
| |
| @override |
| bool _matchesType(DartType type) => type.isDartCoreBool; |
| } |
| |
| class _TypeHelperDateTime extends _TypeHelperWithHelper { |
| const _TypeHelperDateTime(); |
| |
| @override |
| String helperName(DartType paramType) => 'DateTime.parse'; |
| |
| @override |
| String _encode(String fieldName, DartType type) => |
| '$fieldName${type.ensureNotNull}.toString()'; |
| |
| @override |
| bool _matchesType(DartType type) => |
| const TypeChecker.fromRuntime(DateTime).isAssignableFromType(type); |
| } |
| |
| class _TypeHelperDouble extends _TypeHelperWithHelper { |
| const _TypeHelperDouble(); |
| |
| @override |
| String helperName(DartType paramType) => 'double.parse'; |
| |
| @override |
| String _encode(String fieldName, DartType type) => |
| '$fieldName${type.ensureNotNull}.toString()'; |
| |
| @override |
| bool _matchesType(DartType type) => type.isDartCoreDouble; |
| } |
| |
| class _TypeHelperEnum extends _TypeHelperWithHelper { |
| const _TypeHelperEnum(); |
| |
| @override |
| String helperName(DartType paramType) => |
| '${enumMapName(paramType as InterfaceType)}.$enumExtensionHelperName'; |
| |
| @override |
| String _encode(String fieldName, DartType type) => |
| '${enumMapName(type as InterfaceType)}[$fieldName${type.ensureNotNull}]!'; |
| |
| @override |
| bool _matchesType(DartType type) => type.isEnum; |
| } |
| |
| class _TypeHelperInt extends _TypeHelperWithHelper { |
| const _TypeHelperInt(); |
| |
| @override |
| String helperName(DartType paramType) => 'int.parse'; |
| |
| @override |
| String _encode(String fieldName, DartType type) => |
| '$fieldName${type.ensureNotNull}.toString()'; |
| |
| @override |
| bool _matchesType(DartType type) => type.isDartCoreInt; |
| } |
| |
| class _TypeHelperNum extends _TypeHelperWithHelper { |
| const _TypeHelperNum(); |
| |
| @override |
| String helperName(DartType paramType) => 'num.parse'; |
| |
| @override |
| String _encode(String fieldName, DartType type) => |
| '$fieldName${type.ensureNotNull}.toString()'; |
| |
| @override |
| bool _matchesType(DartType type) => type.isDartCoreNum; |
| } |
| |
| class _TypeHelperString extends _TypeHelper { |
| const _TypeHelperString(); |
| |
| @override |
| String _decode(ParameterElement parameterElement) => |
| 'state.${_stateValueAccess(parameterElement)}'; |
| |
| @override |
| String _encode(String fieldName, DartType type) => |
| '$fieldName${type.ensureNotNull}'; |
| |
| @override |
| bool _matchesType(DartType type) => type.isDartCoreString; |
| } |
| |
| class _TypeHelperUri extends _TypeHelperWithHelper { |
| const _TypeHelperUri(); |
| |
| @override |
| String helperName(DartType paramType) => 'Uri.parse'; |
| |
| @override |
| String _encode(String fieldName, DartType type) => |
| '$fieldName${type.ensureNotNull}.toString()'; |
| |
| @override |
| bool _matchesType(DartType type) => |
| const TypeChecker.fromRuntime(Uri).isAssignableFromType(type); |
| } |
| |
| abstract class _TypeHelperWithHelper extends _TypeHelper { |
| const _TypeHelperWithHelper(); |
| |
| String helperName(DartType paramType); |
| |
| @override |
| String _decode(ParameterElement parameterElement) { |
| final DartType paramType = parameterElement.type; |
| |
| if (paramType.isNullableType) { |
| return '$convertMapValueHelperName(' |
| '${escapeDartString(parameterElement.name.kebab)}, ' |
| 'state.queryParams, ' |
| '${helperName(paramType)})'; |
| } |
| |
| return '${helperName(paramType)}' |
| '(state.${_stateValueAccess(parameterElement)})'; |
| } |
| } |
| |
| extension on DartType { |
| String get ensureNotNull => isNullableType ? '!' : ''; |
| } |
| |
| /// Extension helpers on [ParameterElement]. |
| extension ParameterElementExtension on ParameterElement { |
| /// 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; |
| } |