Skip to content

Commit deec9da

Browse files
Generator: migrate to analyzer element2 API #152
1 parent 0796914 commit deec9da

File tree

2 files changed

+77
-71
lines changed

2 files changed

+77
-71
lines changed

generator/lib/src/entity_resolver.dart

Lines changed: 74 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import 'dart:async';
22
import 'dart:convert';
33

44
import 'package:analyzer/dart/constant/value.dart';
5-
import 'package:analyzer/dart/element/element.dart';
5+
import 'package:analyzer/dart/element/element2.dart';
66
import 'package:analyzer/dart/element/nullability_suffix.dart';
77
import 'package:analyzer/dart/element/type.dart';
88
import 'package:build/build.dart';
@@ -53,10 +53,10 @@ class EntityResolver extends Builder {
5353
}
5454

5555
ModelEntity generateForAnnotatedElement(
56-
Element classElement, ConstantReader annotation) {
57-
if (classElement is! ClassElement) {
56+
Element2 classElement, ConstantReader annotation) {
57+
if (classElement is! ClassElement2) {
5858
throw InvalidGenerationSourceError(
59-
"Entity '${classElement.name}': annotated element must be a class.");
59+
"Entity '${classElement.displayName}': annotated element must be a class.");
6060
}
6161

6262
// process basic entity (note that allModels.createEntity is not used, as the entity will be merged)
@@ -65,8 +65,8 @@ class EntityResolver extends Builder {
6565
final entity = ModelEntity.create(
6666
IdUid(0, entityUid.isNull ? 0 : entityUid.intValue),
6767
entityRealClass.isNull
68-
? classElement.name
69-
: entityRealClass.typeValue.element!.name!,
68+
? classElement.displayName
69+
: entityRealClass.typeValue.element3!.displayName,
7070
null,
7171
uidRequest: !entityUid.isNull && entityUid.intValue == 0);
7272

@@ -85,43 +85,45 @@ class EntityResolver extends Builder {
8585

8686
log.info(entity);
8787

88-
entity.constructorParams = constructorParams(findConstructor(classElement));
88+
entity.constructorParams =
89+
constructorParams(classElement.unnamedConstructor2);
8990

9091
// Make sure all stored fields are writable when reading object from DB.
9192
// Let's filter read-only fields, i.e those that:
9293
// * don't have a setter, and
9394
// * don't have a corresponding argument in the constructor.
94-
// Note: `.correspondingSetter == null` is also true for `final` fields.
95+
// Note: the corresponding setter is also null for final fields.
9596
final readOnlyFields = <String>{};
96-
for (var f in classElement.accessors) {
97-
if (f.isGetter &&
98-
f.correspondingSetter == null &&
97+
for (var f in classElement.getters2) {
98+
if (f.correspondingSetter2 == null &&
9999
!entity.constructorParams
100-
.any((String param) => param.startsWith('${f.name} '))) {
101-
readOnlyFields.add(f.name);
100+
.any((String param) => param.startsWith('${f.displayName} '))) {
101+
readOnlyFields.add(f.displayName);
102102
}
103103
}
104104

105105
// read all suitable annotated properties
106-
for (var f in classElement.fields) {
106+
for (var f in classElement.fields2) {
107107
// The field might be implicitly defined by a getter, aka it is synthetic
108108
// and does not exist in code. So always resolve the actual non-synthetic
109109
// element that exists in code (here a getter) as only it will have any
110110
// annotations.
111-
final annotated = f.nonSynthetic;
111+
final annotated = f.nonSynthetic2;
112112

113113
if (_transientChecker.hasAnnotationOfExact(annotated)) {
114-
log.info(" Skipping property '${f.name}': annotated with @Transient.");
114+
log.info(
115+
" Skipping property '${f.displayName}': annotated with @Transient.");
115116
continue;
116117
}
117118

118-
if (readOnlyFields.contains(f.name) && !isRelationField(f)) {
119-
log.info(" Skipping property '${f.name}': is read-only/getter.");
119+
if (readOnlyFields.contains(f.name3) && !isRelationField(f)) {
120+
log.info(
121+
" Skipping property '${f.displayName}': is read-only/getter.");
120122
continue;
121123
}
122124

123125
if (f.isPrivate) {
124-
log.info(" Skipping property '${f.name}': is private.");
126+
log.info(" Skipping property '${f.displayName}': is private.");
125127
continue;
126128
}
127129

@@ -153,10 +155,10 @@ class EntityResolver extends Builder {
153155
if (isToManyRelationField(f)) {
154156
isToManyRel = true;
155157
} else {
156-
fieldType = detectObjectBoxType(f, classElement.name);
158+
fieldType = detectObjectBoxType(f, classElement.displayName);
157159
if (fieldType == null) {
158160
log.warning(
159-
" Skipping property '${f.name}': type '${f.type}' not supported,"
161+
" Skipping property '${f.displayName}': type '${f.type}' not supported,"
160162
" consider creating a relation for @Entity types (https://docs.objectbox.io/relations),"
161163
" or replace with getter/setter converting to a supported type (https://docs.objectbox.io/advanced/custom-types).");
162164
continue;
@@ -167,12 +169,15 @@ class EntityResolver extends Builder {
167169
String? relTargetName;
168170
if (isRelationField(f)) {
169171
if (f.type is! ParameterizedType) {
170-
log.severe(" Skipping property '${f.name}': invalid relation type, "
172+
log.severe(
173+
" Skipping property '${f.displayName}': invalid relation type, "
171174
"use a type like ToOne<TargetEntity> or ToMany<TargetEntity>.");
172175
continue;
173176
}
174-
relTargetName =
175-
(f.type as ParameterizedType).typeArguments[0].element!.name;
177+
relTargetName = (f.type as ParameterizedType)
178+
.typeArguments[0]
179+
.element3!
180+
.displayName;
176181
}
177182

178183
final backlinkAnnotations =
@@ -181,13 +186,15 @@ class EntityResolver extends Builder {
181186
// Handles ToMany based on other ToOne or ToMany relation (backlink)
182187
if (!isToManyRel) {
183188
log.severe(
184-
" Skipping property '${f.name}': @Backlink() may only be used with ToMany.");
189+
" Skipping property '${f.displayName}': @Backlink() may only be used with ToMany.");
185190
continue;
186191
}
187192
final backlinkField =
188193
backlinkAnnotations.first.getField('to')!.toStringValue()!;
189194
final backlink = ModelBacklink(
190-
name: f.name, srcEntity: relTargetName!, srcField: backlinkField);
195+
name: f.displayName,
196+
srcEntity: relTargetName!,
197+
srcField: backlinkField);
191198
entity.backlinks.add(backlink);
192199
log.info(' $backlink');
193200
} else if (isToManyRel) {
@@ -207,7 +214,7 @@ class EntityResolver extends Builder {
207214
});
208215

209216
// create relation
210-
final rel = ModelRelation.create(IdUid(0, propUid ?? 0), f.name,
217+
final rel = ModelRelation.create(IdUid(0, propUid ?? 0), f.displayName,
211218
targetName: relTargetName,
212219
uidRequest: propUid != null && propUid == 0,
213220
externalName: externalName,
@@ -220,7 +227,7 @@ class EntityResolver extends Builder {
220227
// Handles regular properties
221228
// create property (do not use readEntity.createProperty in order to avoid generating new ids)
222229
final prop = ModelProperty.create(
223-
IdUid(0, propUid ?? 0), f.name, fieldType,
230+
IdUid(0, propUid ?? 0), f.displayName, fieldType,
224231
flags: flags,
225232
entity: entity,
226233
uidRequest: propUid != null && propUid == 0);
@@ -245,7 +252,7 @@ class EntityResolver extends Builder {
245252
// errors, so no need to integrate with regular index processing.
246253
if (fieldType != OBXPropertyType.FloatVector) {
247254
throw InvalidGenerationSourceError(
248-
"'${classElement.name}.${f.name}': @HnswIndex is only supported for float vector properties.",
255+
"'${classElement.displayName}.${f.displayName}': @HnswIndex is only supported for float vector properties.",
249256
element: f);
250257
}
251258
// Create an index
@@ -266,7 +273,7 @@ class EntityResolver extends Builder {
266273

267274
// for code generation
268275
prop.dartFieldType =
269-
f.type.element!.name! + (isNullable(f.type) ? '?' : '');
276+
f.type.element3!.displayName + (isNullable(f.type) ? '?' : '');
270277
entity.properties.add(prop);
271278
}
272279
}
@@ -277,13 +284,13 @@ class EntityResolver extends Builder {
277284
// for `setId()` won't compile. The only exception is when user uses
278285
// self-assigned IDs, then a different setter will be generated - one that
279286
// checks the ID being set is already the same, otherwise it must throw.
280-
final idField = classElement.fields
281-
.singleWhere((FieldElement f) => f.name == entity.idProperty.name);
282-
if (idField.setter == null) {
287+
final idField = classElement.fields2.singleWhere(
288+
(FieldElement2 f) => f.displayName == entity.idProperty.name);
289+
if (idField.setter2 == null) {
283290
if (!entity.idProperty.hasFlag(OBXPropertyFlags.ID_SELF_ASSIGNABLE)) {
284291
throw InvalidGenerationSourceError(
285-
"@Id field '${idField.name}' must be writable:"
286-
" ObjectBox uses it to set the assigned ID after inserting a new object,"
292+
"@Id field '${idField.displayName}' must be writable,"
293+
" ObjectBox uses it to set the assigned ID after inserting a new object:"
287294
" provide a setter or remove the 'final' keyword."
288295
" If your code needs to assign IDs itself,"
289296
" see https://docs.objectbox.io/advanced/object-ids#self-assigned-object-ids.",
@@ -309,8 +316,8 @@ class EntityResolver extends Builder {
309316
/// For fields that do not have a [Property.type] declared in their [Property]
310317
/// annotation tries to determine the ObjectBox database type based on the
311318
/// Dart type. May return null if no supported type is detected.
312-
int? detectObjectBoxType(FieldElement f, String className) {
313-
final dartType = f.type;
319+
int? detectObjectBoxType(FieldElement2 field, String classDisplayName) {
320+
final dartType = field.type;
314321

315322
if (dartType.isDartCoreInt) {
316323
// Dart: 8 bytes
@@ -342,32 +349,36 @@ class EntityResolver extends Builder {
342349
// List<String>
343350
return OBXPropertyType.StringVector;
344351
}
345-
} else if (['Int8List', 'Uint8List'].contains(dartType.element!.name)) {
352+
} else if (['Int8List', 'Uint8List']
353+
.contains(dartType.element3!.displayName)) {
346354
return OBXPropertyType.ByteVector;
347-
} else if (['Int16List', 'Uint16List'].contains(dartType.element!.name)) {
355+
} else if (['Int16List', 'Uint16List']
356+
.contains(dartType.element3!.displayName)) {
348357
return OBXPropertyType.ShortVector;
349-
} else if (['Int32List', 'Uint32List'].contains(dartType.element!.name)) {
358+
} else if (['Int32List', 'Uint32List']
359+
.contains(dartType.element3!.displayName)) {
350360
return OBXPropertyType.IntVector;
351-
} else if (['Int64List', 'Uint64List'].contains(dartType.element!.name)) {
361+
} else if (['Int64List', 'Uint64List']
362+
.contains(dartType.element3!.displayName)) {
352363
return OBXPropertyType.LongVector;
353-
} else if (dartType.element!.name == 'Float32List') {
364+
} else if (dartType.element3!.displayName == 'Float32List') {
354365
return OBXPropertyType.FloatVector;
355-
} else if (dartType.element!.name == 'Float64List') {
366+
} else if (dartType.element3!.displayName == 'Float64List') {
356367
return OBXPropertyType.DoubleVector;
357-
} else if (dartType.element!.name == 'DateTime') {
368+
} else if (dartType.element3!.displayName == 'DateTime') {
358369
log.warning(
359-
" DateTime property '${f.name}' in entity '$className' is stored and read using millisecond precision. "
370+
" DateTime property '${field.displayName}' in entity '$classDisplayName' is stored and read using millisecond precision. "
360371
'To silence this warning, add an explicit type using @Property(type: PropertyType.date) or @Property(type: PropertyType.dateNano) annotation.');
361372
return OBXPropertyType.Date;
362-
} else if (isToOneRelationField(f)) {
373+
} else if (isToOneRelationField(field)) {
363374
return OBXPropertyType.Relation;
364375
}
365376

366377
// No supported Dart type recognized.
367378
return null;
368379
}
369380

370-
void processIdProperty(ModelEntity entity, ClassElement classElement) {
381+
void processIdProperty(ModelEntity entity, ClassElement2 classElement) {
371382
// check properties explicitly annotated with @Id()
372383
final annotated =
373384
entity.properties.where((p) => p.hasFlag(OBXPropertyFlags.ID));
@@ -405,8 +416,8 @@ class EntityResolver extends Builder {
405416
idProperty.flags &= ~OBXPropertyFlags.UNSIGNED;
406417
}
407418

408-
void processAnnotationIndexUnique(FieldElement f, Element annotatedElement,
409-
int? fieldType, Element elementBare, ModelProperty prop) {
419+
void processAnnotationIndexUnique(FieldElement2 f, Element2 annotatedElement,
420+
int? fieldType, Element2 elementBare, ModelProperty prop) {
410421
IndexType? indexType;
411422

412423
final indexAnnotation =
@@ -427,13 +438,13 @@ class EntityResolver extends Builder {
427438
fieldType == OBXPropertyType.DoubleVector ||
428439
fieldType == OBXPropertyType.StringVector) {
429440
throw InvalidGenerationSourceError(
430-
"Entity '${elementBare.name}': @Index/@Unique is not supported for type '${f.type}' of field '${f.name}'.",
441+
"Entity '${elementBare.displayName}': @Index/@Unique is not supported for type '${f.type}' of field '${f.displayName}'.",
431442
element: f);
432443
}
433444

434445
if (prop.hasFlag(OBXPropertyFlags.ID)) {
435446
throw InvalidGenerationSourceError(
436-
"Entity '${elementBare.name}': @Index/@Unique is not supported for @Id field '${f.name}'."
447+
"Entity '${elementBare.displayName}': @Index/@Unique is not supported for @Id field '${f.displayName}'."
437448
" IDs are unique by definition and automatically indexed.",
438449
element: f);
439450
}
@@ -459,7 +470,7 @@ class EntityResolver extends Builder {
459470
if (!supportsHashIndex &&
460471
(indexType == IndexType.hash || indexType == IndexType.hash64)) {
461472
throw InvalidGenerationSourceError(
462-
"Entity '${elementBare.name}': a hash index is not supported for type '${f.type}' of field '${f.name}'",
473+
"Entity '${elementBare.displayName}': a hash index is not supported for type '${f.type}' of field '${f.displayName}'",
463474
element: f);
464475
}
465476

@@ -489,7 +500,7 @@ class EntityResolver extends Builder {
489500
}
490501

491502
void ensureSingleUniqueReplace(
492-
ModelEntity entity, ClassElement classElement) {
503+
ModelEntity entity, ClassElement2 classElement) {
493504
final uniqueReplaceProps = entity.properties
494505
.where((p) => p.hasFlag(OBXPropertyFlags.UNIQUE_ON_CONFLICT_REPLACE));
495506
if (uniqueReplaceProps.length > 1) {
@@ -500,7 +511,7 @@ class EntityResolver extends Builder {
500511
}
501512

502513
void ifSyncEnsureAllUniqueAreReplace(
503-
ModelEntity entity, ClassElement classElement) {
514+
ModelEntity entity, ClassElement2 classElement) {
504515
if (!entity.hasFlag(OBXEntityFlags.SYNC_ENABLED)) return;
505516
final uniqueButNotReplaceProps = entity.properties.where((p) {
506517
return p.hasFlag(OBXPropertyFlags.UNIQUE) &&
@@ -541,28 +552,23 @@ class EntityResolver extends Builder {
541552
return typeArgs.length == 1 ? typeArgs[0] : null;
542553
}
543554

544-
bool isRelationField(FieldElement f) =>
555+
bool isRelationField(FieldElement2 f) =>
545556
isToOneRelationField(f) || isToManyRelationField(f);
546557

547-
bool isToOneRelationField(FieldElement f) => f.type.element!.name == 'ToOne';
558+
bool isToOneRelationField(FieldElement2 f) =>
559+
f.type.element3!.name3 == 'ToOne';
548560

549-
bool isToManyRelationField(FieldElement f) =>
550-
f.type.element!.name == 'ToMany';
561+
bool isToManyRelationField(FieldElement2 f) =>
562+
f.type.element3!.name3 == 'ToMany';
551563

552564
bool isNullable(DartType type) =>
553565
type.nullabilitySuffix == NullabilitySuffix.star ||
554566
type.nullabilitySuffix == NullabilitySuffix.question;
555567

556-
// Find an unnamed constructor we can use to initialize
557-
ConstructorElement? findConstructor(ClassElement entity) {
558-
final index = entity.constructors.indexWhere((c) => c.name.isEmpty);
559-
return index >= 0 ? entity.constructors[index] : null;
560-
}
561-
562-
List<String> constructorParams(ConstructorElement? constructor) {
568+
List<String> constructorParams(ConstructorElement2? constructor) {
563569
if (constructor == null) return List.empty();
564-
return constructor.parameters.map((param) {
565-
var info = StringBuffer(param.name);
570+
return constructor.formalParameters.map((param) {
571+
var info = StringBuffer(param.displayName);
566572
if (param.isRequiredPositional) info.write(' positional');
567573
if (param.isOptionalPositional) info.write(' optional');
568574
if (param.isRequiredNamed) info.write(' required-named');
@@ -617,7 +623,7 @@ class EntityResolver extends Builder {
617623
}
618624

619625
extension _TypeCheckerExtensions on TypeChecker {
620-
void runIfMatches(Element element, void Function(DartObject) fn) {
626+
void runIfMatches(Element2 element, void Function(DartObject) fn) {
621627
final annotations = annotationsOfExact(element);
622628
if (annotations.isNotEmpty) fn(annotations.first);
623629
}

generator/pubspec.yaml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,13 @@ environment:
1010

1111
dependencies:
1212
objectbox: 4.3.0
13-
analyzer: '>=6.5.0 <8.0.0' # dart_style 2.3.7 requires 6.5.0
14-
build: ^2.5.0 # TODO Tighten further as Dart 3.7.0 is required by build_test anyhow?
13+
analyzer: 7.4.0 # TODO Allow version 8
14+
build: 3.0.0 # source_gen 3 requires 3.0.0 # TODO Allow any 3 version
1515
collection: ^1.18.0 # Would require 1.19.1, but Flutter 3.24.0 has 1.18.0 pinned
1616
dart_style: '>=2.3.7 <4.0.0' # require 2.3.7 for languageVersion in DartFormatter constructor
1717
glob: ^2.1.3
1818
path: ^1.9.1
19-
source_gen: ">=1.5.0 <3.0.0"
19+
source_gen: 3.0.0 # Require 3.0.0 to use new analyzer 7.4.0 element2 APIs # TODO Allow any 3 version
2020
pubspec_parse: ^1.4.0
2121
yaml: ^3.1.3
2222
http: ^1.3.0

0 commit comments

Comments
 (0)