Skip to content

Commit ba25c58

Browse files
Generator: consistent helpful logs, add docs links, add code locations.
1 parent 638a3a3 commit ba25c58

File tree

2 files changed

+59
-46
lines changed

2 files changed

+59
-46
lines changed

generator/lib/src/entity_resolver.dart

Lines changed: 58 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -51,10 +51,10 @@ class EntityResolver extends Builder {
5151
}
5252

5353
ModelEntity generateForAnnotatedElement(
54-
Element element, ConstantReader annotation) {
55-
if (element is! ClassElement) {
54+
Element classElement, ConstantReader annotation) {
55+
if (classElement is! ClassElement) {
5656
throw InvalidGenerationSourceError(
57-
"entity ${element.name}: annotated element isn't a class");
57+
"Entity '${classElement.name}': annotated element must be a class.");
5858
}
5959

6060
// process basic entity (note that allModels.createEntity is not used, as the entity will be merged)
@@ -63,23 +63,23 @@ class EntityResolver extends Builder {
6363
final entity = ModelEntity.create(
6464
IdUid(0, entityUid.isNull ? 0 : entityUid.intValue),
6565
entityRealClass.isNull
66-
? element.name
66+
? classElement.name
6767
: entityRealClass.typeValue.element!.name!,
6868
null,
6969
uidRequest: !entityUid.isNull && entityUid.intValue == 0);
7070

71-
if (_syncChecker.hasAnnotationOfExact(element)) {
71+
if (_syncChecker.hasAnnotationOfExact(classElement)) {
7272
entity.flags |= OBXEntityFlags.SYNC_ENABLED;
7373
}
7474

7575
log.info(entity);
7676

77-
entity.constructorParams = constructorParams(findConstructor(element));
78-
entity.nullSafetyEnabled = nullSafetyEnabled(element);
77+
entity.constructorParams = constructorParams(findConstructor(classElement));
78+
entity.nullSafetyEnabled = nullSafetyEnabled(classElement);
7979
if (!entity.nullSafetyEnabled) {
8080
log.warning(
81-
"Entity ${entity.name} is in a library/application that doesn't use null-safety"
82-
' - consider increasing your SDK version to Flutter 2.0/Dart 2.12');
81+
"Entity '${entity.name}' is in a package that doesn't use null-safety"
82+
' - consider increasing your SDK version to Flutter 2.0/Dart 2.12.');
8383
}
8484

8585
// Make sure all stored fields are writable when reading object from DB.
@@ -88,7 +88,7 @@ class EntityResolver extends Builder {
8888
// * don't have a corresponding argument in the constructor.
8989
// Note: `.correspondingSetter == null` is also true for `final` fields.
9090
final readOnlyFields = <String>{};
91-
for (var f in element.accessors) {
91+
for (var f in classElement.accessors) {
9292
if (f.isGetter &&
9393
f.correspondingSetter == null &&
9494
!entity.constructorParams
@@ -98,19 +98,19 @@ class EntityResolver extends Builder {
9898
}
9999

100100
// read all suitable annotated properties
101-
for (var f in element.fields) {
101+
for (var f in classElement.fields) {
102102
if (_transientChecker.hasAnnotationOfExact(f)) {
103-
log.info(' skipping property ${f.name} (annotated with @Transient)');
103+
log.info(" Skipping property '${f.name}': annotated with @Transient.");
104104
continue;
105105
}
106106

107107
if (readOnlyFields.contains(f.name) && !isRelationField(f)) {
108-
log.info(' skipping read-only/getter ${f.name}');
108+
log.info(" Skipping property '${f.name}': is read-only/getter.");
109109
continue;
110110
}
111111

112112
if (f.isPrivate) {
113-
log.info(' skipping private field ${f.name}');
113+
log.info(" Skipping property '${f.name}': is private.");
114114
continue;
115115
}
116116

@@ -160,15 +160,15 @@ class EntityResolver extends Builder {
160160
} else if (dartType.element!.name == 'DateTime') {
161161
fieldType = OBXPropertyType.Date;
162162
log.warning(
163-
" DateTime property '${f.name}' in entity '${element.name}' is stored and read using millisecond precision. "
163+
" DateTime property '${f.name}' in entity '${classElement.name}' is stored and read using millisecond precision. "
164164
'To silence this warning, add an explicit type using @Property(type: PropertyType.date) or @Property(type: PropertyType.dateNano) annotation.');
165165
} else if (isToOneRelationField(f)) {
166166
fieldType = OBXPropertyType.Relation;
167167
} else if (isToManyRelationField(f)) {
168168
isToManyRel = true;
169169
} else {
170170
log.warning(
171-
" skipping property '${f.name}': type '$dartType' not supported,"
171+
" Skipping property '${f.name}': type '$dartType' not supported,"
172172
" consider creating a relation for @Entity types (https://docs.objectbox.io/relations),"
173173
" or replace with getter/setter converting to a supported type (https://docs.objectbox.io/advanced/custom-types).");
174174
continue;
@@ -178,8 +178,8 @@ class EntityResolver extends Builder {
178178
String? relTargetName;
179179
if (isRelationField(f)) {
180180
if (f.type is! ParameterizedType) {
181-
log.severe(
182-
" invalid relation property '${f.name}' in entity '${element.name}' - must use ToOne/ToMany<TargetEntity>");
181+
log.severe(" Skipping property '${f.name}': invalid relation type, "
182+
"use a type like ToOne<TargetEntity> or ToMany<TargetEntity>.");
183183
continue;
184184
}
185185
relTargetName =
@@ -190,7 +190,7 @@ class EntityResolver extends Builder {
190190
if (backlinkAnnotations.isNotEmpty) {
191191
if (!isToManyRel) {
192192
log.severe(
193-
' invalid use of @Backlink() annotation - may only be used on a ToMany<> field');
193+
" Skipping property '${f.name}': @Backlink() may only be used with ToMany.");
194194
continue;
195195
}
196196
final backlinkField =
@@ -226,7 +226,7 @@ class EntityResolver extends Builder {
226226
}
227227

228228
// Index and unique annotation.
229-
processAnnotationIndexUnique(f, fieldType, element, prop);
229+
processAnnotationIndexUnique(f, fieldType, classElement, prop);
230230

231231
// for code generation
232232
prop.dartFieldType =
@@ -235,63 +235,65 @@ class EntityResolver extends Builder {
235235
}
236236
}
237237

238-
processIdProperty(entity);
238+
processIdProperty(entity, classElement);
239239

240240
// We need to check that the ID field is writable. Otherwise, generated code
241241
// for `setId()` won't compile. The only exception is when user uses
242242
// self-assigned IDs, then a different setter will be generated - one that
243243
// checks the ID being set is already the same, otherwise it must throw.
244-
final idField = element.fields
244+
final idField = classElement.fields
245245
.singleWhere((FieldElement f) => f.name == entity.idProperty.name);
246246
if (idField.setter == null) {
247247
if (!entity.idProperty.hasFlag(OBXPropertyFlags.ID_SELF_ASSIGNABLE)) {
248248
throw InvalidGenerationSourceError(
249-
"Entity ${entity.name} has an ID field '${idField.name}' that is "
250-
'not assignable (that usually means it is declared final). '
251-
"This won't work because ObjectBox needs to be able to assign "
252-
'an ID after inserting a new object (if the given ID was zero). '
253-
'If you want to assign IDs manually instead, you can annotate the '
254-
"field '${idField.name}' with `@Id(assignable: true)`. Otherwise "
255-
'please provide a setter or remove the `final` keyword.');
249+
"@Id field '${idField.name}' must be writable:"
250+
" ObjectBox uses it to set the assigned ID after inserting a new object,"
251+
" provide a setter or remove the 'final' keyword."
252+
" If your code needs to assign IDs itself,"
253+
" see https://docs.objectbox.io/advanced/object-ids#self-assigned-object-ids.",
254+
element: idField);
256255
} else {
257256
// We need to get the information to code generator (code_chunks.dart).
258257
entity.idProperty.fieldIsReadOnly = true;
259258
}
260259
}
261260

262261
// Verify there is at most 1 unique property with REPLACE strategy.
263-
ensureSingleUniqueReplace(entity);
262+
ensureSingleUniqueReplace(entity, classElement);
264263
// If sync enabled, verify all unique properties use REPLACE strategy.
265-
ifSyncEnsureAllUniqueAreReplace(entity);
264+
ifSyncEnsureAllUniqueAreReplace(entity, classElement);
266265

267266
entity.properties.forEach((p) => log.info(' $p'));
268267

269268
return entity;
270269
}
271270

272-
void processIdProperty(ModelEntity entity) {
271+
void processIdProperty(ModelEntity entity, ClassElement classElement) {
273272
// check properties explicitly annotated with @Id()
274273
final annotated =
275274
entity.properties.where((p) => p.hasFlag(OBXPropertyFlags.ID));
276275
if (annotated.length > 1) {
276+
final names = annotated.map((e) => e.name).join(", ");
277277
throw InvalidGenerationSourceError(
278-
'entity ${entity.name}: multiple fields annotated with Id(), there may only be one');
278+
"Entity '${entity.name}': multiple fields ($names) annotated with @Id(), there may only be one.",
279+
element: classElement);
279280
}
280281

281282
if (annotated.length == 1) {
282283
if (annotated.first.type != OBXPropertyType.Long) {
283284
throw InvalidGenerationSourceError(
284-
"entity ${entity.name}: Id() annotated property has invalid type, expected 'int'");
285+
"Entity '${entity.name}': @Id() property must be 'int'."
286+
" If you need to use other types, see https://docs.objectbox.io/entity-annotations#object-ids-id.",
287+
element: classElement);
285288
}
286289
} else {
287290
// if there are no annotated props, try to find one by name & type
288291
final candidates = entity.properties.where((p) =>
289292
p.name.toLowerCase() == 'id' && p.type == OBXPropertyType.Long);
290293
if (candidates.length != 1) {
291294
throw InvalidGenerationSourceError(
292-
'entity ${entity.name}: ID property not found - either define'
293-
' an integer field named ID/id/... (case insensitive) or add'
294-
' @Id annotation to any integer field');
295+
"Entity '${entity.name}': no @Id() property found, add an int field annotated with @Id().",
296+
element: classElement);
295297
}
296298
candidates.first.flags |= OBXPropertyFlags.ID;
297299
}
@@ -310,19 +312,22 @@ class EntityResolver extends Builder {
310312

311313
final indexAnnotation = _indexChecker.firstAnnotationOfExact(f);
312314
final uniqueAnnotation = _uniqueChecker.firstAnnotationOfExact(f);
313-
if (indexAnnotation == null && uniqueAnnotation == null) return null;
315+
if (indexAnnotation == null && uniqueAnnotation == null) return;
314316

315317
// Throw if property type does not support any index.
316318
if (fieldType == OBXPropertyType.Float ||
317319
fieldType == OBXPropertyType.Double ||
318320
fieldType == OBXPropertyType.ByteVector) {
319321
throw InvalidGenerationSourceError(
320-
"entity ${elementBare.name}: @Index/@Unique is not supported for type '${f.type}' of field '${f.name}'");
322+
"Entity '${elementBare.name}': @Index/@Unique is not supported for type '${f.type}' of field '${f.name}'.",
323+
element: f);
321324
}
322325

323326
if (prop.hasFlag(OBXPropertyFlags.ID)) {
324327
throw InvalidGenerationSourceError(
325-
'entity ${elementBare.name}: @Index/@Unique is not supported for ID field ${f.name}. IDs are unique by definition and automatically indexed');
328+
"Entity '${elementBare.name}': @Index/@Unique is not supported for @Id field '${f.name}'."
329+
" IDs are unique by definition and automatically indexed.",
330+
element: f);
326331
}
327332

328333
// If available use index type from annotation.
@@ -345,7 +350,8 @@ class EntityResolver extends Builder {
345350
if (!supportsHashIndex &&
346351
(indexType == IndexType.hash || indexType == IndexType.hash64)) {
347352
throw InvalidGenerationSourceError(
348-
"entity ${elementBare.name}: a hash index is not supported for type '${f.type}' of field '${f.name}'");
353+
"Entity '${elementBare.name}': a hash index is not supported for type '${f.type}' of field '${f.name}'",
354+
element: f);
349355
}
350356

351357
if (uniqueAnnotation != null && !uniqueAnnotation.isNull) {
@@ -371,28 +377,34 @@ class EntityResolver extends Builder {
371377
break;
372378
default:
373379
throw InvalidGenerationSourceError(
374-
'entity ${elementBare.name}: invalid index type: $indexType');
380+
"Entity '${elementBare.name}': index type $indexType not supported.",
381+
element: f);
375382
}
376383
}
377384

378-
void ensureSingleUniqueReplace(ModelEntity entity) {
385+
void ensureSingleUniqueReplace(
386+
ModelEntity entity, ClassElement classElement) {
379387
final uniqueReplaceProps = entity.properties
380388
.where((p) => p.hasFlag(OBXPropertyFlags.UNIQUE_ON_CONFLICT_REPLACE));
381389
if (uniqueReplaceProps.length > 1) {
382390
throw InvalidGenerationSourceError(
383-
"ConflictStrategy.replace can only be used on a single property, but found multiple in '${entity.name}':\n ${uniqueReplaceProps.join('\n ')}");
391+
"ConflictStrategy.replace can only be used on a single property, but found multiple in '${entity.name}':\n ${uniqueReplaceProps.join('\n ')}",
392+
element: classElement);
384393
}
385394
}
386395

387-
void ifSyncEnsureAllUniqueAreReplace(ModelEntity entity) {
396+
void ifSyncEnsureAllUniqueAreReplace(
397+
ModelEntity entity, ClassElement classElement) {
388398
if (!entity.hasFlag(OBXEntityFlags.SYNC_ENABLED)) return;
389399
final uniqueButNotReplaceProps = entity.properties.where((p) {
390400
return p.hasFlag(OBXPropertyFlags.UNIQUE) &&
391401
!p.hasFlag(OBXPropertyFlags.UNIQUE_ON_CONFLICT_REPLACE);
392402
});
393403
if (uniqueButNotReplaceProps.isNotEmpty) {
394404
throw InvalidGenerationSourceError(
395-
"Synced entities must use @Unique(onConflict: ConflictStrategy.replace) on all unique properties, but found others in '${entity.name}':\n ${uniqueButNotReplaceProps.join('\n ')}");
405+
"Synced entities must use @Unique(onConflict: ConflictStrategy.replace) on all unique properties,"
406+
" but found others in '${entity.name}':\n ${uniqueButNotReplaceProps.join('\n ')}",
407+
element: classElement);
396408
}
397409
}
398410

objectbox/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
## latest
22

3+
* Generator messages should be more helpful, provide code location when possible and link to docs.
34

45
## 1.6.1 (2022-08-22)
56

0 commit comments

Comments
 (0)