generateForAnnotatedElement method
- 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. 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
Future<String> generateForAnnotatedElement(
Element element,
ConstantReader annotation,
BuildStep buildStep,
) async {
if (element is! ClassElement) {
throw InvalidGenerationSourceError(
'@Db can only be applied to classes',
element: element,
);
}
final className = element.name;
final sqlDialectValue = annotation.peek('sqlDialect')?.objectValue;
// Determine SQL dialect
String sqlDialect = 'postgresql';
if (sqlDialectValue != null) {
final dialectName = sqlDialectValue.getField('_name')?.toStringValue();
if (dialectName != null) {
sqlDialect = dialectName;
}
}
// Extract entity types from annotation
final entitiesReader = annotation.peek('entities');
final entities = <_EntitySchemaInfo>[];
final manyToManyRelations = <_ManyToManyInfo>[];
final entityElements = <String, ClassElement>{};
final entityToTableName =
<String, String>{}; // Map entity name to table name
if (entitiesReader != null && !entitiesReader.isNull) {
final entityList = entitiesReader.listValue;
// First pass: collect all entity elements and their table names
for (final entityValue in entityList) {
final typeValue = entityValue.toTypeValue();
if (typeValue != null) {
final entityElement = typeValue.element;
if (entityElement is ClassElement && entityElement.name != null) {
entityElements[entityElement.name!] = entityElement;
// Extract table name from @Entity annotation
String tableName = _toSnakeCase(entityElement.name!);
for (final meta in entityElement.metadata.annotations) {
if (meta.element?.enclosingElement?.name == 'Entity') {
final source = meta.toSource();
final tableMatch = RegExp(
'tableName:\\s*[\'"]([\\w_]+)[\'"]',
).firstMatch(source);
if (tableMatch != null) {
tableName = tableMatch.group(1)!;
}
break;
}
}
entityToTableName[entityElement.name!] = tableName;
}
}
}
// Second pass: extract entity info and relationships
for (final entityValue in entityList) {
final typeValue = entityValue.toTypeValue();
if (typeValue != null) {
final entityElement = typeValue.element;
if (entityElement is ClassElement && entityElement.name != null) {
final tableName = entityToTableName[entityElement.name!]!;
// Extract ManyToMany relationships
final m2mRelations = _extractManyToManyRelations(
entityElement,
tableName,
entityElements,
entityToTableName,
sqlDialect,
);
manyToManyRelations.addAll(m2mRelations);
// Generate full entity schema info
final schemaInfo = _extractEntitySchemaInfo(
entityElement,
tableName,
entityToTableName,
);
entities.add(schemaInfo);
}
}
}
}
// Deduplicate junction tables (keep only owning side)
final junctionTables = _deduplicateJunctionTables(
manyToManyRelations,
entityToTableName,
);
// Save junction schemas to .dorm/schemas/ as JSON files
final inputPath = buildStep.inputId.path;
final packageRoot = inputPath.split('lib/').first;
await _saveJunctionSchemas(packageRoot, junctionTables);
return _generateSchemaCode(
className: className!,
entities: entities,
junctionTables: junctionTables,
);
}