generateForAnnotatedElement method
- Element2 element,
- ConstantReader annotation,
- BuildStep buildStep
Implement to return source code to generate for element
.
This method is invoked based on finding elements annotated with an
instance of T
. The annotation
is provided as a ConstantReader
.
Supported return values include a single String or multiple String instances within an Iterable or Stream. It is also valid to return a Future of String, Iterable, or Stream. When multiple values are returned through an iterable or stream they will be deduplicated. Typically each value will be an independent unit of code and the deduplication prevents re-defining the same member multiple times. For example if multiple annotated elements may need a specific utility method available it can be output for each one, and the single deduplicated definition can be shared.
Implementations should return null
when no content is generated. Empty
or whitespace-only String instances are also ignored.
Implementation
@override
FutureOr<String> generateForAnnotatedElement(
Element2 element,
ConstantReader annotation,
BuildStep buildStep,
) async {
String? getParameterInitializationCode(String valueString) {
return '$valueString;';
}
final visitor = MainAppVisitor();
final methodsVisitor = AnnotatedFunctionVisitor();
element
..visitChildren2(visitor)
..visitChildren2(methodsVisitor);
final isDialog = annotation.peek('dialogs')?.boolValue ?? false;
final isBottomSheet = annotation.peek('bottomSheets')?.boolValue ?? false;
final className = visitor.className;
final classBuffer = StringBuffer();
var linkHandlerBaseClass = 'RouteLinkHandler';
var namesEnumClass = 'RouteNames';
var routesBaseClass = 'routes';
if (isDialog) {
linkHandlerBaseClass = 'DialogLinkHandler';
namesEnumClass = 'DialogNames';
routesBaseClass = 'dialogs';
} else if (isBottomSheet) {
linkHandlerBaseClass = 'BottomSheetLinkHandler';
namesEnumClass = 'BottomSheetNames';
routesBaseClass = 'bottomSheets';
}
final linkHandlersMap = {};
classBuffer.writeln(
'// ignore_for_file: unnecessary_parenthesis, unused_local_variable, prefer_final_locals, unnecessary_string_interpolations, join_return_with_assignment, unnecessary_raw_strings',
);
methodsVisitor.annotatedMethods.forEach((key, value) {
final paths = value
.peek('paths')
?.listValue
.map((e) => e.toStringValue())
// coverage:ignore-start
.toList() ??
[];
// coverage:ignore-end
final regexes = value.peek('regexes')?.listValue;
if (paths.isNotEmpty && regexes != null) {
throw Exception('Cant add paths and regexes to the same route $key');
}
final requiresState =
(methodsVisitor.annotatedMethodsData[key]?.formalParameters
// coverage:ignore-start
.where((element) => element.name3 == 'state') ??
[])
// coverage:ignore-end
.isNotEmpty;
if (regexes != null) {
return;
}
var handlerIndex = 0;
final query = value
.peek('query')
?.listValue
.map((e) => e.toStringValue())
// coverage:ignore-start
.toList() ??
[];
// coverage:ignore-end
final possibleFragments = value
.peek('possibleFragments')
?.listValue
// coverage:ignore-start
.map((e) => e.toStringValue()) ??
[];
// coverage:ignore-end
final customHandler =
value.peek('customHandler')?.typeValue.getDisplayString();
final queriesForPathValue = value.peek('queriesForPath')?.listValue;
final possibleFragmentsForPathValue =
value.peek('possibleFragmentsForPath')?.listValue;
for (int index = 0; index < paths.length; index++) {
final path = paths[index];
handlerIndex++;
var queryForPath = query;
var possibleFragmentsForPath = possibleFragments;
if (queriesForPathValue != null) {
queryForPath = queriesForPathValue[index]
.toListValue()
?.map((e) => e.toStringValue())
// coverage:ignore-start
.toList() ??
[];
// coverage:ignore-end
}
if (possibleFragmentsForPathValue != null) {
possibleFragmentsForPath = possibleFragmentsForPathValue[index]
.toListValue()
?.map((e) => e.toStringValue())
// coverage:ignore-start
.toList() ??
[];
// coverage:ignore-end
}
if (path != null) {
final codedPath = path.replaceAll(RegExp(':{.+?(?=})}'), '*');
final queryHandlersMap = {};
var resultRule = '';
if (possibleFragmentsForPath.isNotEmpty) {
for (final fragment in possibleFragmentsForPath) {
queryForPath.add('#=$fragment');
}
}
if (queryForPath.isNotEmpty) {
for (final element in queryForPath) {
if (element!.contains('=') && element.contains('|')) {
final splittedByKeyValues = element.split('=');
final key = splittedByKeyValues[0];
final values = splittedByKeyValues[1].split('|');
for (final element in values) {
resultRule += '$key=$element|';
}
} else {
resultRule += '$element|';
}
}
resultRule = resultRule.substring(0, resultRule.length - 1);
if (customHandler != null) {
queryHandlersMap[resultRule] = customHandler;
} else {
queryHandlersMap[resultRule] =
'${capitalize(key)}LinkHandler$handlerIndex';
}
}
final pathsMap = linkHandlersMap;
String newParser;
if (customHandler != null) {
newParser = customHandler;
} else {
newParser = '${capitalize(key)}LinkHandler$handlerIndex';
}
addLinkHandlerToMap(
codedPath,
pathsMap,
queryHandlersMap,
newParser,
);
}
if (path == null || customHandler != null) {
continue;
}
final uriPath = Uri.parse(path);
final segments = uriPath.pathSegments;
var parseParamsUrlString = '';
for (final element in segments) {
if (!element.contains(':{')) {
continue;
}
final paramName = element.replaceAll(RegExp('[:{}]'), '');
final paramInitialization = getParameterInitializationCode(
'segments[index]',
);
parseParamsUrlString += '''
if (pathSegmentPattern == '$element') {
pathParams['$paramName'] = $paramInitialization
}
''';
}
classBuffer
..writeln(
'class ${capitalize(key)}LinkHandler$handlerIndex extends $linkHandlerBaseClass {',
)
..writeln(
'''
@override
Future<UIRoute> parseLinkToRoute(String url) async {
final uriPath = Uri.parse(url);
final segments = uriPath.pathSegments;
final queryParams = uriPath.queryParametersAll;
final patternUriPath = Uri.parse('$path');
final patternSegments = patternUriPath.pathSegments;
Map<String, dynamic> queryParamsForView = {};
Map<String, dynamic> pathParams = {};
for (var index = 0; index < patternSegments.length; index++) {
final pathSegmentPattern = patternSegments[index];
$parseParamsUrlString
}
for (final param in queryParams.entries) {
final queryParam = param.key;
dynamic queryValue = param.value;
if (queryValue.length == 1) {
queryParamsForView[queryParam] = queryValue[0];
} else if (queryValue.length > 1) {
queryParamsForView[queryParam] = queryValue;
}
}
${requiresState ? 'final anchor = uriPath.fragment;' : ''}
final route = app.navigation.$routesBaseClass.$key(
pathParams: pathParams,
queryParams: queryParamsForView,
${requiresState ? 'state: anchor,' : ''}
);
return route;
}
}
''',
);
}
});
classBuffer
..writeln()
..writeln('enum $namesEnumClass {');
for (final method in methodsVisitor.allMethods) {
classBuffer.writeln('$method,');
}
classBuffer
..writeln('}')
..writeln()
..writeln('mixin ${className}Gen on RoutesBase {')
..writeln()
..writeln('@override')
..writeln('void initializeLinkHandlers() {')
..writeln('routeLinkHandlers.addAll({')
..writeln(generateLinksMap(linkHandlersMap))
..writeln('});')
..writeln('regexHandlers.addAll({')
..writeln(generateRegexMapper(methodsVisitor.annotatedMethods.values))
..writeln('});')
..writeln('}')
..writeln('}')
..writeln();
String printMessage = '';
printMessage += 'umvvm_generator:\nGenerated Navigation for app: ';
printMessage += '$className count: ${methodsVisitor.allMethods.length}';
log.info(printMessage);
return classBuffer.toString();
}