Line data Source code
1 : part of rx_bloc_generator; 2 : 3 : /// Supported types of streams 4 : class _BlocEventStreamTypes { 5 : /// Constants feel more comfortable than strings 6 : static const String publish = 'PublishSubject'; 7 : static const String behavior = 'BehaviorSubject'; 8 : } 9 : 10 : /// String utilities 11 : extension _StringExtensions on String { 12 : /// Capitalizes the first letter of the word 13 5 : String capitalize() => '${this[0].toUpperCase()}${substring(1)}'; 14 : 15 : /// Converts string to red string when printed in terminal 16 2 : String toRedString() => '\x1B[31m${this}\x1B[0m'; 17 : } 18 : 19 : /// It is the main [DartFormatter] 20 : extension _SpecExtensions on Spec { 21 3 : String toDartCodeString() => DartFormatter().format( 22 1 : accept( 23 1 : DartEmitter(allocator: Allocator.none, useNullSafetySyntax: true), 24 1 : ).toString(), 25 : ); 26 : } 27 : 28 : extension _StateFieldElement on FieldElement { 29 3 : String get stateFieldName => '_${name}State'; 30 : 31 4 : String get stateMethodName => '_mapTo${name.capitalize()}State'; 32 : } 33 : 34 : extension _EventMethodElement on MethodElement { 35 : /// The event field name in the generated file 36 3 : String get eventFieldName => '_\$${name}Event'; 37 : 38 : /// The name of the arguments class that will be generated if 39 : /// the event contains more than one parameter 40 4 : String get eventArgumentsClassName => '_${name.capitalize()}EventArgs'; 41 : 42 : /// Is the the [RxBlocEvent.seed] annotation is provided 43 2 : bool get hasSeedAnnotation => RegExp(r'(?<=seed: ).*(?=\)|,)') 44 3 : .hasMatch(_rxBlocEventAnnotation?.toSource() ?? ''); 45 : 46 : /// Provides the stream generic type 47 : /// 48 : /// Example: 49 : /// if `fetchNews(int param)` then -> PublishSubject<int> 50 : /// if `fetchNews(String param)` then -> PublishSubject<String> 51 : /// if `fetchNews(int p1, int p2)` then -> PublishSubject<_FetchNewsEventArgs> 52 4 : List<Reference> get streamTypeArguments => [refer(publishSubjectGenericType)]; 53 : 54 : /// Provides the BehaviorSubject.seeded arguments as [List] of [Expression] 55 : /// Throws an [_RxBlocGeneratorException] if a seed is provided but 56 : /// the stream is not [RxBlocEventType.behaviour] 57 1 : List<Expression> get seedPositionalArguments { 58 2 : if (hasSeedAnnotation && !isBehavior) { 59 3 : throw _RxBlocGeneratorException('Event `$name` with type `PublishSubject`' 60 : ' can not have a `seed` parameter.'); 61 : } 62 : 63 2 : return [_seededArgument]; 64 : } 65 : 66 : /// Provides the BehaviorSubject.seeded arguments as an [Expression] 67 1 : Expression get _seededArgument { 68 1 : var seedArgumentsMatch = RegExp(r'(?<=seed: ).*(?=\)|,)') 69 3 : .allMatches(_rxBlocEventAnnotation?.toSource() ?? '') 70 3 : .map<String>((m) => m.group(0) ?? ''); 71 : 72 1 : if (seedArgumentsMatch.isEmpty) { 73 0 : throw _RxBlocGeneratorException( 74 0 : 'Event `$name` seed value is missing or is null.'); 75 : } 76 : 77 1 : var seedArguments = seedArgumentsMatch.toString(); 78 2 : return refer( 79 : // ignore: lines_longer_than_80_chars 80 2 : '${isUsingArgumentClass && !seedArguments.contains('(const') ? 'const ' : ''}' 81 3 : '${seedArguments.substring(1, seedArguments.length - 1)}', 82 : ); 83 : } 84 : 85 : /// Provides the stream type based on the [RxBlocEventType] annotation 86 2 : String get eventStreamType => isBehavior 87 1 : ? _BlocEventStreamTypes.behavior + 88 3 : (hasSeedAnnotation ? '<$publishSubjectGenericType>' : '') 89 : : _BlocEventStreamTypes.publish; 90 : 91 : /// Provides the first annotation as [ElementAnnotation] if exists 92 1 : ElementAnnotation? get _eventAnnotation => 93 : // TODO(Diev): Check if 94 5 : metadata.isNotEmpty && metadata.first is ElementAnnotation 95 2 : ? metadata.first 96 : : null; 97 : 98 : /// Provides the [RxBlocEvent] annotation as [DartObject] if exists 99 1 : DartObject? get _computedRxBlocEventAnnotation => 100 2 : _rxBlocEventAnnotation?.computeConstantValue(); 101 : 102 : /// Provides the [RxBlocEvent] annotation as [ElementAnnotation] if exists 103 2 : ElementAnnotation? get _rxBlocEventAnnotation => _eventAnnotation 104 1 : ?.computeConstantValue() 105 1 : ?.type 106 2 : ?.getDisplayString(withNullability: true) == 107 1 : (RxBlocEvent).toString() 108 1 : ? _eventAnnotation 109 : : null; 110 : 111 : /// Is the event stream type a BehaviorSubject 112 1 : bool get isBehavior => 113 3 : _computedRxBlocEventAnnotation.toString().contains('behaviour'); 114 : 115 : /// Use argument class when the event's parameters are more than 1 116 4 : bool get isUsingArgumentClass => parameters.length > 1; 117 : 118 : /// Provides the stream type based on the number of the parameters 119 : /// Example: 120 : /// if `fetchNews(int param)` then -> int 121 : /// if `fetchNews(String param)` then -> String 122 : /// if `fetchNews(int param1, int param2)` -> _FetchNewsEventArgs 123 1 : String get publishSubjectGenericType { 124 1 : if (isUsingArgumentClass) { 125 1 : return eventArgumentsClassName; 126 : } 127 2 : return parameters.isNotEmpty 128 : // The only parameter's type 129 4 : ? parameters.first.type.getDisplayString(withNullability: true) 130 : // Default type 131 : : 'void'; 132 : } 133 : 134 : /// Builds the stream body 135 : /// Example 1: 136 : /// _${EventMethodName}EventName.add(param) 137 : /// 138 : /// Example 2: 139 : /// _${EventMethodName}EventName.add(_MethodEventArgs(param1, param2)) 140 : /// 141 1 : Code buildBody() { 142 3 : var requiredParams = parameters.whereRequired().clone(); 143 3 : var optionalParams = parameters.whereOptional().clone(); 144 : 145 2 : if (requiredParams.isEmpty && optionalParams.isEmpty) { 146 : // Provide null if we don't have any parameters 147 1 : return _callStreamAddMethod(literalNull); 148 : } 149 : 150 : // Provide the first if it's just one required parameter 151 3 : if (optionalParams.isEmpty && requiredParams.length == 1) { 152 4 : return _callStreamAddMethod(refer(requiredParams.first.name)); 153 : } 154 : 155 : // Provide the first if it's just one optional parameter 156 3 : if (requiredParams.isEmpty && optionalParams.length == 1) { 157 4 : return _callStreamAddMethod(refer(optionalParams.first.name)); 158 : } 159 : 160 1 : return _callStreamAddMethod( 161 5 : refer('_${name.capitalize()}EventArgs').newInstance( 162 1 : _positionalArguments, 163 1 : _namedArguments, 164 : ), 165 : ); 166 : } 167 : 168 : /// Example: 169 : /// _${methodName}Event.add() 170 1 : Code _callStreamAddMethod(Expression argument) => 171 6 : refer('$eventFieldName.add').call([argument]).code; 172 : 173 : /// Provides the event's positional arguments as a [Map] of [Expression] 174 2 : List<Expression> get _positionalArguments => parameters 175 3 : .where((ParameterElement parameter) => parameter.isPositional) 176 1 : .map( 177 3 : (ParameterElement parameter) => refer(parameter.name), 178 : ) 179 1 : .toList(); 180 : 181 : /// Provides the event's name arguments as a [Map] of [Expression] 182 1 : Map<String, Expression> get _namedArguments { 183 2 : var params = parameters.clone(); 184 : 185 : // Only named are not positional parameters 186 1 : var namedArguments = <String, Expression>{}; 187 4 : for (var param in params.where((Parameter param) => param.named)) { 188 4 : namedArguments[param.name] = refer(param.name); 189 : } 190 : // For methods with more than 1 parameters provide the new param class 191 : return namedArguments; 192 : } 193 : } 194 : 195 : extension _ListParameterElementWhere on List<ParameterElement> { 196 2 : Iterable<ParameterElement> whereRequired() => where( 197 3 : (parameter) => !parameter.isNamed && !parameter.isOptionalPositional); 198 : 199 1 : Iterable<ParameterElement> whereOptional() => 200 4 : where((parameter) => parameter.isOptionalPositional || parameter.isNamed); 201 : } 202 : 203 : extension _ListParameterElementClone on Iterable<ParameterElement> { 204 2 : List<Parameter> clone({bool toThis = false}) => map( 205 2 : (ParameterElement parameter) => Parameter( 206 1 : (b) => b 207 1 : ..toThis = toThis 208 2 : ..required = parameter.isRequiredNamed 209 2 : ..defaultTo = parameter.defaultValueCode != null 210 2 : ? Code(parameter.defaultValueCode ?? '') 211 : : null 212 2 : ..named = parameter.isNamed 213 2 : ..name = parameter.name 214 1 : ..type = toThis 215 : ? null // We don't need the type in the constructor 216 2 : : refer(parameter.getTypeDisplayName()), 217 : ), 218 1 : ).toList(); 219 : } 220 : 221 : extension _ParameterElementToString on ParameterElement { 222 3 : String getTypeDisplayName() => type.getDisplayString(withNullability: true); 223 : }