generateForAnnotatedElement method
Stream<String>
generateForAnnotatedElement(
- Element factoryElement,
- 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.
Implementations should return null
when no content is generated. Empty
or whitespace-only String instances are also ignored.
Implementation
@override
Stream<String> generateForAnnotatedElement(
Element factoryElement,
ConstantReader annotation,
BuildStep buildStep,
) async* {
if (factoryElement is ClassElement) {
final target = annotation.read('target').typeValue;
final targetElement = target.element;
if (targetElement is! ClassElement) {
throw StateError('Factory target should be valid class element');
}
final hasAnnotatedConstructor = targetElement.constructors.any(
_constructorChecker.hasAnnotationOf,
);
final targetConstructor = targetElement.constructors.firstWhere(
(element) {
if (hasAnnotatedConstructor) {
return _constructorChecker.hasAnnotationOf(element);
}
return !element.isSynthetic &&
!element.isPrivate &&
element.isAccessibleIn(factoryElement.library);
},
);
final targetParameters = targetConstructor.parameters;
final parametersDocumentation = Map.fromEntries(
await Future.wait(
targetParameters.map(
(e) async => MapEntry(
e.name,
await getDocumentation(e, buildStep),
),
),
),
);
final hasValueProvider = checkValueProviderAssigned(factoryElement);
for (final targetParameter in targetParameters) {
final element = targetParameter.type.element;
if (element?.isAccessibleIn(factoryElement.library) != true) {
logWarning(
'${targetParameter.type.getDisplayString(withNullability: false)} needed by ${targetConstructor.displayName}\n'
'but not accessible in ${factoryElement.library.displayName} file.',
);
}
}
final defaultValues = Map.fromEntries(
targetParameters.map(
(targetParameter) => MapEntry(
targetParameter.name,
getDefaultValueCode(targetParameter),
),
),
);
bool checkDefaultValue(ParameterElement element) =>
defaultValues[element.name] != null;
final factoryImplementation = Class((it) {
it.name = '_\$${factoryElement.name}';
it.types.addAll(
factoryElement.typeParameters.map((type) => Reference('$type')));
it.extend = Reference('ObjectFactory<${targetElement.thisType}>');
it.abstract = true;
it.fields.addAll([
Field(
(it) => it
..name = 'isRoot'
..type = Reference('bool')
..modifier = FieldModifier.final$,
),
Field(
(it) => it
..name = 'context'
..type = Reference('FactoryContext')
..modifier = FieldModifier.final$,
),
Field(
(it) => it
..name = 'key'
..type = Reference('ContextKey')
..modifier = FieldModifier.final$,
),
]);
it.constructors.add(Constructor((it) {
it.optionalParameters.addAll([
Parameter((it) => it
..name = 'context'
..type = Reference('FactoryContext?')),
Parameter(
(it) => it
..name = 'key'
..defaultTo = Code('defaultKey')
..toThis = true,
),
]);
it.initializers.addAll([
Code('isRoot = context == null'),
Code('context = context ?? FactoryContext()'),
]);
}));
it.methods.addAll(targetParameters.map((targetParameter) {
return Method((it) {
final docs = parametersDocumentation[targetParameter.name];
if (docs?.isNotEmpty == true) {
it.docs.add(docs!);
}
it.name = 'get${targetParameter.name.capitalize()}';
it.returns = Reference('${targetParameter.type}');
it.requiredParameters.addAll([
Parameter((it) {
it.type = Reference('FactoryContext');
it.name = 'context';
}),
Parameter((it) {
it.type = Reference('ContextKey');
it.name = 'key';
})
]);
final hasDefaultValueCode =
defaultValues[targetParameter.name] != null;
final isOptional = checkOptionalRecursive(targetParameter);
final providerAssignment =
getProviderCallCode(targetParameter.type);
if (hasDefaultValueCode) {
it.body = Code('return ${defaultValues[targetParameter.name]};');
} else if (targetParameter.hasDefaultValue) {
it.body = Code('return ${targetParameter.defaultValueCode};');
} else if (hasValueProvider && providerAssignment != null) {
it.body = Code(providerAssignment);
} else if (isOptional) {
it.body = Code('return null;');
}
});
}));
final valueBuilders = targetParameters.map(
(e) => Parameter((it) {
it.name = e.name;
it.named = true;
it.type = Reference('ValueBuilder<${e.type}>?');
}),
);
it.methods.add(Method((it) {
it.name = 'create';
it.returns = Reference('${targetElement.thisType}');
it.optionalParameters.addAll(valueBuilders);
final fieldsSetters = targetParameters.map(
(e) => '_\$objectBuilder.${e.name} = '
'(${e.name} ?? get${e.name.capitalize()})(context, key + \'${e.name}\');',
);
final builderTypeParameters = targetElement.thisType.typeArguments;
final builderTypeParametersString = builderTypeParameters.isNotEmpty
? '<${builderTypeParameters.join(',')}>'
: '';
it.body = Code('''
final _\$objectBuilder = _${targetElement.thisType.plainName}Builder$builderTypeParametersString();
this.context.add(_\$objectBuilder.toReadOnly(), this.key);
${fieldsSetters.join('\n')}
{
final object = _\$objectBuilder.build();
this.context.clear();
return object;
}
''');
}));
it.methods.add(Method((it) {
it.name = 'batch';
it.returns = Reference('List<${targetElement.thisType}>');
it.requiredParameters.add(Parameter((it) {
it.name = 'length';
it.type = Reference('int');
}));
it.optionalParameters.addAll(valueBuilders);
it.body = Code('''
return List.generate(
length,
(index) => create(
${targetParameters.map((e) => '${e.name}: ${e.name}').join(',\n')}
),
);
''');
}));
});
final readonlyBulder = Class((it) {
final typeName = targetElement.thisType.plainName;
final genericTypes = targetElement.thisType.typeArguments;
it.name = '${typeName}ReadonlyBuilder';
it.types.addAll(genericTypes.map((type) => Reference('$type')));
it.extend = Reference(
'ObjectReadonlyBuilder<${targetElement.thisType}>',
);
it.fields.addAll(
targetParameters.map(
(e) => Field(
(it) {
final hasDefaultValue = checkDefaultValue(e);
final shouldBeNullable =
!hasDefaultValue && !e.type.toString().endsWith('?');
it
..type = Reference(
'ValueGetter<${e.type}${shouldBeNullable ? '?' : ''}>',
)
..name = 'get${e.name.capitalize()}'
..modifier = FieldModifier.final$;
},
),
),
);
it.constructors.add(Constructor((it) {
it.constant = true;
it.requiredParameters.addAll(
targetParameters.map(
(e) => Parameter(
(it) => it
..name = 'get${e.name.capitalize()}'
..toThis = true,
),
),
);
}));
});
final builder = Class((it) {
final typeName = targetElement.thisType.plainName;
final genericTypes = targetElement.thisType.typeArguments;
it.name = '_${typeName}Builder';
it.types.addAll(genericTypes.map((type) => Reference('$type')));
it.extend = Reference('ObjectBuilder<${targetElement.thisType}>');
it.fields.addAll(
targetParameters.map(
(targetParameter) => Field(
(it) {
final hasDefaultValue =
defaultValues[targetParameter.name] != null;
final shouldBeNullable = !hasDefaultValue &&
!targetParameter.type.toString().endsWith('?');
it
..name = targetParameter.name
..type = Reference(
'${targetParameter.type}${shouldBeNullable ? '?' : ''}');
final parameterDefaultAssignment =
defaultValues[targetParameter.name];
if (parameterDefaultAssignment?.isNotEmpty == true) {
it.assignment = Code(parameterDefaultAssignment!);
} else if (targetParameter.hasDefaultValue) {
it.assignment = Code(targetParameter.defaultValueCode!);
}
},
),
),
);
it.methods.add(
Method(
(it) {
it
..name = 'toReadOnly'
..returns = Reference(readonlyBulder.name);
it.body = Code('''
return ${readonlyBulder.name}(
${targetParameters.map((e) => '() => ${e.name}').join(',\n')}
);
''');
},
),
);
it.methods.add(
Method(
(it) {
final parameters = targetParameters.map((e) {
if (e.isNamed) {
return '${e.name}: ${e.name}';
}
return '${e.name}';
});
final constructorHasName =
targetConstructor.name.isNotEmpty == true;
final constructor =
'${targetElement.thisType}${constructorHasName ? '.' + targetConstructor.name : ''}';
it
..name = 'build'
..returns = Reference('${targetElement.thisType}')
..body = Code('''
try {
${targetParameters.map((targetParameter) {
final isOptional = checkOptionalRecursive(targetParameter);
return 'final ${targetParameter.name} = this.${targetParameter.name}${!isOptional ? '!' : ''};';
}).join('\n')}
return $constructor(
${parameters.join(',\n')}
);
} on Object {
throw InvalidBuilderStateException();
}
''');
},
),
);
});
final library = Library(
(b) => b.body.addAll(
[
factoryImplementation,
readonlyBulder,
builder,
],
),
);
final code = '${library.accept(DartEmitter.scoped())}';
yield _dartfmt.format(code);
}
}