generateForAnnotatedElement method
dynamic
generateForAnnotatedElement(
- Element 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.
Implementations should return null
when no content is generated. Empty
or whitespace-only String instances are also ignored.
Implementation
@override
dynamic generateForAnnotatedElement(
Element element, ConstantReader annotation, BuildStep buildStep) async {
final generateForDir = annotation
.read('generateForDir')
.listValue
.map((e) => e.toStringValue());
final usesNullSafety = annotation.read('usesNullSafety').boolValue;
final isMicroPackage = annotation.read('_isMicroPackage').boolValue;
final usesConstructorCallback =
annotation.read('usesConstructorCallback').boolValue;
final throwOnMissingDependencies =
annotation.read('throwOnMissingDependencies').boolValue;
final targetFile = element.source?.uri;
final preferRelativeImports =
annotation.read("preferRelativeImports").boolValue;
final includeMicroPackages =
annotation.read("includeMicroPackages").boolValue;
final rootDir = annotation.peek('rootDir')?.stringValue;
final dirPattern = generateForDir.length > 1
? '{${generateForDir.join(',')}}'
: '${generateForDir.first}';
final injectableConfigFiles = Glob("$dirPattern/**.injectable.json");
final jsonData = <Map>[];
await for (final id in buildStep.findAssets(injectableConfigFiles)) {
final json = jsonDecode(await buildStep.readAsString(id));
jsonData.addAll([...json]);
}
final deps = <DependencyConfig>[];
for (final json in jsonData) {
deps.add(DependencyConfig.fromJson(json));
}
final initializerName = annotation.read('initializerName').stringValue;
final asExtension = annotation.read('asExtension').boolValue;
final typeResolver =
ImportableTypeResolverImpl(await buildStep.resolver.libraries.toList());
final ignoredTypes =
annotation.read('ignoreUnregisteredTypes').listValue.map(
(e) => typeResolver.resolveType(e.toTypeValue()!),
);
final microPackageModulesBefore = _getMicroPackageModules(
annotation.peek('externalPackageModulesBefore'),
typeResolver,
);
// remove after deprecation period
if (microPackageModulesBefore.isEmpty) {
microPackageModulesBefore.addAll(
_getMicroPackageModulesMapped(
annotation.peek('externalPackageModules'),
typeResolver,
),
);
}
final microPackageModulesAfter = _getMicroPackageModules(
annotation.peek('externalPackageModulesAfter'),
typeResolver,
);
final microPackagesModules =
microPackageModulesBefore.union(microPackageModulesAfter);
if (!isMicroPackage && includeMicroPackages) {
final glob = Glob('**.module.dart', recursive: true);
final filesStream = glob.list(root: rootDir);
await for (final match in filesStream) {
final commentAnnotation = File(match.path).readAsLinesSync().first;
if (commentAnnotation.contains('@GeneratedMicroModule')) {
final segments = commentAnnotation.split(';');
if (segments.length == 3) {
final externalModule = ExternalModuleConfig(
ImportableType(
name: segments[1],
import: segments[2],
),
);
if (!microPackagesModules
.any((e) => externalModule.module == e.module)) {
microPackageModulesBefore.add(externalModule);
}
}
}
}
}
final ignoreTypesInPackages = annotation
.read('ignoreUnregisteredTypesInPackages')
.listValue
.map((e) => e.toStringValue())
.where((e) => e != null)
.cast<String>()
.toList();
// we want to ignore unregistered types in microPackages
// because the micro module should handle them
for (final pckModule in microPackagesModules) {
final packageName =
Uri.parse(pckModule.module.import!).pathSegments.first;
ignoreTypesInPackages.add(packageName);
}
_reportMissingDependencies(
deps,
ignoredTypes,
ignoreTypesInPackages,
targetFile,
throwOnMissingDependencies,
);
_validateDuplicateDependencies(deps);
/// don't allow registering of the same dependency with both async and sync factories
final groupedByType = deps.groupListsBy((d) => (d.type, d.instanceName));
for (final entry in groupedByType.entries) {
final first = entry.value.first;
final isAsync = first.isAsync && !first.preResolve;
throwIf(
entry.value.any((e) => (e.isAsync && !e.preResolve) != isAsync),
'Dependencies of type [${entry.key.$1}] must either all be async or all be sync\n',
);
}
final generator = LibraryGenerator(
dependencies: List.of(deps),
targetFile: preferRelativeImports ? targetFile : null,
initializerName: initializerName,
asExtension: asExtension,
microPackageName:
isMicroPackage ? buildStep.inputId.package.pascalCase : null,
microPackagesModulesBefore: microPackageModulesBefore,
microPackagesModulesAfter: microPackageModulesAfter,
usesConstructorCallback: usesConstructorCallback,
);
final generatedLib = generator.generate();
final emitter = DartEmitter(
allocator: Allocator.simplePrefixing(),
orderDirectives: true,
useNullSafetySyntax: usesNullSafety,
);
final output =
DartFormatter().format(generatedLib.accept(emitter).toString());
if (isMicroPackage) {
final outputId = buildStep.inputId.changeExtension('.module.dart');
return buildStep.writeAsString(
outputId,
[
'//@GeneratedMicroModule;${capitalize(buildStep.inputId.package.pascalCase)}PackageModule;${outputId.uri}',
defaultFileHeader,
output,
].join('\n'),
);
}
return output;
}