From afb5e8157c687bc4c45733bf7258637fa2205e53 Mon Sep 17 00:00:00 2001 From: westito Date: Sat, 12 Feb 2022 19:36:54 +0100 Subject: [PATCH 1/8] Add custom parent class option to generated data classes --- drift/lib/src/dsl/table.dart | 10 +++- drift_dev/lib/src/analyzer/dart/parser.dart | 4 +- .../lib/src/analyzer/dart/table_parser.dart | 12 ++++- .../lib/src/analyzer/dart/view_parser.dart | 5 +- drift_dev/lib/src/analyzer/data_class.dart | 47 +++++++++++++++++++ .../analyzer/moor/create_table_reader.dart | 2 +- drift_dev/lib/src/model/base_entity.dart | 3 ++ drift_dev/lib/src/model/table.dart | 4 ++ drift_dev/lib/src/model/view.dart | 6 ++- drift_dev/lib/src/utils/names.dart | 16 ------- .../src/writer/tables/data_class_writer.dart | 4 +- 11 files changed, 87 insertions(+), 26 deletions(-) create mode 100644 drift_dev/lib/src/analyzer/data_class.dart delete mode 100644 drift_dev/lib/src/utils/names.dart diff --git a/drift/lib/src/dsl/table.dart b/drift/lib/src/dsl/table.dart index f17b5bcd..d3e7097c 100644 --- a/drift/lib/src/dsl/table.dart +++ b/drift/lib/src/dsl/table.dart @@ -184,9 +184,12 @@ class DataClassName { /// {@macro drift_custom_data_class} final String name; + /// The parent class of generated data class. Class must extends [DataClass]! + final Type? extending; + /// Customize the data class name for a given table. /// {@macro drift_custom_data_class} - const DataClassName(this.name); + const DataClassName(this.name, {this.extending}); } /// An annotation specifying an existing class to be used as a data class. @@ -239,6 +242,9 @@ class DriftView { /// {@macro drift_custom_data_class} final String? dataClassName; + /// The parent class of generated data class. Class must extends [DataClass]! + final Type? extending; + /// Customize view name and data class name - const DriftView({this.name, this.dataClassName}); + const DriftView({this.name, this.dataClassName, this.extending}); } diff --git a/drift_dev/lib/src/analyzer/dart/parser.dart b/drift_dev/lib/src/analyzer/dart/parser.dart index 29d90655..17d0f16c 100644 --- a/drift_dev/lib/src/analyzer/dart/parser.dart +++ b/drift_dev/lib/src/analyzer/dart/parser.dart @@ -5,10 +5,10 @@ import 'package:analyzer/dart/element/nullability_suffix.dart'; import 'package:analyzer/dart/element/type.dart'; import 'package:collection/collection.dart'; import 'package:drift_dev/moor_generator.dart'; +import 'package:drift_dev/src/analyzer/data_class.dart'; import 'package:drift_dev/src/analyzer/errors.dart'; import 'package:drift_dev/src/analyzer/runner/steps.dart'; import 'package:drift_dev/src/utils/exception.dart'; -import 'package:drift_dev/src/utils/names.dart'; import 'package:drift_dev/src/utils/type_utils.dart'; import 'package:meta/meta.dart'; import 'package:recase/recase.dart'; @@ -19,9 +19,9 @@ import '../custom_row_class.dart'; part 'column_parser.dart'; part 'table_parser.dart'; -part 'view_parser.dart'; part 'use_dao_parser.dart'; part 'use_moor_parser.dart'; +part 'view_parser.dart'; class MoorDartParser { final ParseDartStep step; diff --git a/drift_dev/lib/src/analyzer/dart/table_parser.dart b/drift_dev/lib/src/analyzer/dart/table_parser.dart index 4b640f43..8bf25328 100644 --- a/drift_dev/lib/src/analyzer/dart/table_parser.dart +++ b/drift_dev/lib/src/analyzer/dart/table_parser.dart @@ -21,6 +21,7 @@ class TableParser { sqlName: sqlName, dartTypeName: dataClassInfo.enforcedName, existingRowClass: dataClassInfo.existingClass, + customParentClass: dataClassInfo.extending, primaryKey: primaryKey, overrideWithoutRowId: await _overrideWithoutRowId(element), declaration: DartTableDeclaration(element, base.step.file), @@ -68,12 +69,14 @@ class TableParser { } String name; + String? customParentClass; FoundDartClass? existingClass; String? constructorInExistingClass; bool? generateInsertable; if (dataClassName != null) { name = dataClassName.getField('name')!.toStringValue()!; + customParentClass = parseCustomParentClass(dataClassName, element, base); } else { name = dataClassNameForClassName(element.name); } @@ -100,7 +103,7 @@ class TableParser { ? null : validateExistingClass(columns, existingClass, constructorInExistingClass!, generateInsertable!, base.step); - return _DataClassInformation(name, verified); + return _DataClassInformation(name, customParentClass, verified); } Future _parseTableName(ClassElement element) async { @@ -238,9 +241,14 @@ class TableParser { class _DataClassInformation { final String enforcedName; + final String? extending; final ExistingRowClass? existingClass; - _DataClassInformation(this.enforcedName, this.existingClass); + _DataClassInformation( + this.enforcedName, + this.extending, + this.existingClass, + ); } extension on Element { diff --git a/drift_dev/lib/src/analyzer/dart/view_parser.dart b/drift_dev/lib/src/analyzer/dart/view_parser.dart index 7fe40021..b3ce54d8 100644 --- a/drift_dev/lib/src/analyzer/dart/view_parser.dart +++ b/drift_dev/lib/src/analyzer/dart/view_parser.dart @@ -20,6 +20,7 @@ class ViewParser { name: name, dartTypeName: dataClassInfo.enforcedName, existingRowClass: dataClassInfo.existingClass, + customParentClass: dataClassInfo.extending, entityInfoName: '\$${element.name}View', viewQuery: query, ); @@ -36,6 +37,7 @@ class ViewParser { List columns, ClassElement element) { DartObject? useRowClass; String? dataClassName; + String? customParentClass; for (final annotation in element.metadata) { final computed = annotation.computeConstantValue(); @@ -43,6 +45,7 @@ class ViewParser { if (annotationClass == 'DriftView') { dataClassName = computed.getField('dataClassName')?.toStringValue(); + customParentClass = parseCustomParentClass(computed, element, base); } else if (annotationClass == 'UseRowClass') { useRowClass = computed; } @@ -84,7 +87,7 @@ class ViewParser { ? null : validateExistingClass(columns, existingClass, constructorInExistingClass!, generateInsertable!, base.step); - return _DataClassInformation(name, verified); + return _DataClassInformation(name, customParentClass, verified); } Future _parseViewName(ClassElement element) async { diff --git a/drift_dev/lib/src/analyzer/data_class.dart b/drift_dev/lib/src/analyzer/data_class.dart new file mode 100644 index 00000000..854cde00 --- /dev/null +++ b/drift_dev/lib/src/analyzer/data_class.dart @@ -0,0 +1,47 @@ +import 'package:analyzer/dart/constant/value.dart'; +import 'package:analyzer/dart/element/element.dart'; +import 'package:analyzer/dart/element/type.dart'; +import 'package:drift_dev/src/analyzer/custom_row_class.dart'; +import 'package:drift_dev/src/analyzer/dart/parser.dart'; +import 'package:drift_dev/src/analyzer/errors.dart'; + +String dataClassNameForClassName(String tableName) { + // This implementation is very primitive at the moment. The basic idea is + // that, very often, table names are formed from the plural of the entity + // they're storing (users, products, ...). We try to find the singular word + // from the table name. + + // todo we might want to implement some edge cases according to + // https://en.wikipedia.org/wiki/English_plurals + + if (tableName.endsWith('s')) { + return tableName.substring(0, tableName.length - 1); + } + + // Default behavior if the table name is not a valid plural. + return '${tableName}Data'; +} + +String? parseCustomParentClass( + DartObject dataClassName, ClassElement element, MoorDartParser base) { + final extending = dataClassName.getField('extending'); + if (extending != null && !extending.isNull) { + final extendingType = extending.toTypeValue(); + if (extendingType is InterfaceType) { + final dartClass = FoundDartClass( + extendingType.element, + extendingType.typeArguments, + ); + return dartClass.classElement.name; + } else { + base.step.reportError( + ErrorInDartCode( + message: 'Parameter `extending` in @DataClassName must be used with a' + ' class', + affectedElement: element, + ), + ); + } + } + return null; +} diff --git a/drift_dev/lib/src/analyzer/moor/create_table_reader.dart b/drift_dev/lib/src/analyzer/moor/create_table_reader.dart index 98e6d725..052e1786 100644 --- a/drift_dev/lib/src/analyzer/moor/create_table_reader.dart +++ b/drift_dev/lib/src/analyzer/moor/create_table_reader.dart @@ -1,11 +1,11 @@ import 'package:analyzer/dart/element/nullability_suffix.dart'; import 'package:analyzer/dart/element/type.dart'; import 'package:drift_dev/moor_generator.dart'; +import 'package:drift_dev/src/analyzer/data_class.dart'; import 'package:drift_dev/src/analyzer/errors.dart'; import 'package:drift_dev/src/analyzer/runner/steps.dart'; import 'package:drift_dev/src/analyzer/sql_queries/type_mapping.dart'; import 'package:drift_dev/src/backends/backend.dart'; -import 'package:drift_dev/src/utils/names.dart'; import 'package:drift_dev/src/utils/string_escaper.dart'; import 'package:drift_dev/src/utils/type_converter_hint.dart'; import 'package:drift_dev/src/utils/type_utils.dart'; diff --git a/drift_dev/lib/src/model/base_entity.dart b/drift_dev/lib/src/model/base_entity.dart index 0529cc52..fcdbc4da 100644 --- a/drift_dev/lib/src/model/base_entity.dart +++ b/drift_dev/lib/src/model/base_entity.dart @@ -49,6 +49,9 @@ abstract class MoorEntityWithResultSet extends MoorSchemaEntity { /// The existing class designed to hold a row, if there is any. ExistingRowClass? get existingRowClass; + /// Class that added to data class as implementation + String? get customParentClass; + /// The name of the Dart class storing the right column getters for this type. /// /// This class is equal to, or a superclass of, [entityInfoName]. diff --git a/drift_dev/lib/src/model/table.dart b/drift_dev/lib/src/model/table.dart index 699893a0..58bf9591 100644 --- a/drift_dev/lib/src/model/table.dart +++ b/drift_dev/lib/src/model/table.dart @@ -27,6 +27,9 @@ class MoorTable extends MoorEntityWithResultSet { @override final ExistingRowClass? existingRowClass; + @override + final String? customParentClass; + /// If [fromClass] is null, another source to use when determining the name /// of this table in generated Dart code. final String? _overriddenName; @@ -149,6 +152,7 @@ class MoorTable extends MoorEntityWithResultSet { this.overrideDontWriteConstraints, this.declaration, this.existingRowClass, + this.customParentClass, this.isStrict = false, }) : _overriddenName = overriddenName { _attachToConverters(); diff --git a/drift_dev/lib/src/model/view.dart b/drift_dev/lib/src/model/view.dart index 5dd39471..0e299d9e 100644 --- a/drift_dev/lib/src/model/view.dart +++ b/drift_dev/lib/src/model/view.dart @@ -1,7 +1,7 @@ +import 'package:drift_dev/src/analyzer/data_class.dart'; import 'package:drift_dev/src/analyzer/options.dart'; import 'package:drift_dev/src/analyzer/runner/file_graph.dart'; import 'package:drift_dev/src/analyzer/runner/results.dart'; -import 'package:drift_dev/src/utils/names.dart'; import 'package:recase/recase.dart'; import 'package:sqlparser/sqlparser.dart'; @@ -36,6 +36,9 @@ class MoorView extends MoorEntityWithResultSet { @override ExistingRowClass? existingRowClass; + @override + final String? customParentClass; + final ViewQueryInformation? viewQuery; MoorView({ @@ -44,6 +47,7 @@ class MoorView extends MoorEntityWithResultSet { required this.dartTypeName, required this.entityInfoName, this.existingRowClass, + this.customParentClass, this.viewQuery, }); diff --git a/drift_dev/lib/src/utils/names.dart b/drift_dev/lib/src/utils/names.dart deleted file mode 100644 index 7fa82c52..00000000 --- a/drift_dev/lib/src/utils/names.dart +++ /dev/null @@ -1,16 +0,0 @@ -String dataClassNameForClassName(String tableName) { - // This implementation is very primitive at the moment. The basic idea is - // that, very often, table names are formed from the plural of the entity - // they're storing (users, products, ...). We try to find the singular word - // from the table name. - - // todo we might want to implement some edge cases according to - // https://en.wikipedia.org/wiki/English_plurals - - if (tableName.endsWith('s')) { - return tableName.substring(0, tableName.length - 1); - } - - // Default behavior if the table name is not a valid plural. - return '${tableName}Data'; -} diff --git a/drift_dev/lib/src/writer/tables/data_class_writer.dart b/drift_dev/lib/src/writer/tables/data_class_writer.dart index bd3e0499..ba830521 100644 --- a/drift_dev/lib/src/writer/tables/data_class_writer.dart +++ b/drift_dev/lib/src/writer/tables/data_class_writer.dart @@ -24,7 +24,9 @@ class DataClassWriter { : 'driftRuntimeOptions'; void write() { - _buffer.write('class ${table.dartTypeName} extends DataClass '); + final parentClass = table.customParentClass ?? 'DataClass'; + _buffer.write('class ${table.dartTypeName} extends $parentClass '); + if (isInsertable) { // The data class is only an insertable if we can actually insert rows // into the target entity. From 4c2e74d5e0319718f00dc69956dbce3c0ee15488 Mon Sep 17 00:00:00 2001 From: westito Date: Sun, 13 Feb 2022 08:13:42 +0100 Subject: [PATCH 2/8] Add generics to parent class option --- drift_dev/lib/src/analyzer/data_class.dart | 14 ++++++++------ .../lib/src/writer/tables/data_class_writer.dart | 4 +++- 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/drift_dev/lib/src/analyzer/data_class.dart b/drift_dev/lib/src/analyzer/data_class.dart index 854cde00..3506f283 100644 --- a/drift_dev/lib/src/analyzer/data_class.dart +++ b/drift_dev/lib/src/analyzer/data_class.dart @@ -1,7 +1,6 @@ import 'package:analyzer/dart/constant/value.dart'; import 'package:analyzer/dart/element/element.dart'; import 'package:analyzer/dart/element/type.dart'; -import 'package:drift_dev/src/analyzer/custom_row_class.dart'; import 'package:drift_dev/src/analyzer/dart/parser.dart'; import 'package:drift_dev/src/analyzer/errors.dart'; @@ -28,11 +27,14 @@ String? parseCustomParentClass( if (extending != null && !extending.isNull) { final extendingType = extending.toTypeValue(); if (extendingType is InterfaceType) { - final dartClass = FoundDartClass( - extendingType.element, - extendingType.typeArguments, - ); - return dartClass.classElement.name; + final className = extendingType.element.name; + if (extendingType.typeArguments.length == 1) { + final genericType = extendingType.typeArguments[0].element?.name; + if (genericType == 'dynamic') { + return '$className'; + } + } + return className; } else { base.step.reportError( ErrorInDartCode( diff --git a/drift_dev/lib/src/writer/tables/data_class_writer.dart b/drift_dev/lib/src/writer/tables/data_class_writer.dart index ba830521..92a5d4fc 100644 --- a/drift_dev/lib/src/writer/tables/data_class_writer.dart +++ b/drift_dev/lib/src/writer/tables/data_class_writer.dart @@ -24,7 +24,9 @@ class DataClassWriter { : 'driftRuntimeOptions'; void write() { - final parentClass = table.customParentClass ?? 'DataClass'; + final customParent = table.customParentClass + ?.replaceFirst('', '<${table.dartTypeName}>'); + final parentClass = customParent ?? 'DataClass'; _buffer.write('class ${table.dartTypeName} extends $parentClass '); if (isInsertable) { From 46ca0462bf3c555c96723d14f6e547353ac9cb77 Mon Sep 17 00:00:00 2001 From: westito Date: Mon, 14 Feb 2022 10:03:46 +0100 Subject: [PATCH 3/8] Add type checks to parent class option --- .../lib/src/analyzer/dart/table_parser.dart | 3 +- .../lib/src/analyzer/dart/view_parser.dart | 17 +++++--- drift_dev/lib/src/analyzer/data_class.dart | 43 +++++++++++++++++-- .../src/writer/tables/data_class_writer.dart | 4 +- 4 files changed, 54 insertions(+), 13 deletions(-) diff --git a/drift_dev/lib/src/analyzer/dart/table_parser.dart b/drift_dev/lib/src/analyzer/dart/table_parser.dart index 8bf25328..cc803bc4 100644 --- a/drift_dev/lib/src/analyzer/dart/table_parser.dart +++ b/drift_dev/lib/src/analyzer/dart/table_parser.dart @@ -76,7 +76,8 @@ class TableParser { if (dataClassName != null) { name = dataClassName.getField('name')!.toStringValue()!; - customParentClass = parseCustomParentClass(dataClassName, element, base); + customParentClass = + parseCustomParentClass(name, dataClassName, element, base); } else { name = dataClassNameForClassName(element.name); } diff --git a/drift_dev/lib/src/analyzer/dart/view_parser.dart b/drift_dev/lib/src/analyzer/dart/view_parser.dart index b3ce54d8..c10a5315 100644 --- a/drift_dev/lib/src/analyzer/dart/view_parser.dart +++ b/drift_dev/lib/src/analyzer/dart/view_parser.dart @@ -36,7 +36,7 @@ class ViewParser { _DataClassInformation _readDataClassInformation( List columns, ClassElement element) { DartObject? useRowClass; - String? dataClassName; + DartObject? driftView; String? customParentClass; for (final annotation in element.metadata) { @@ -44,14 +44,13 @@ class ViewParser { final annotationClass = computed!.type!.element!.name; if (annotationClass == 'DriftView') { - dataClassName = computed.getField('dataClassName')?.toStringValue(); - customParentClass = parseCustomParentClass(computed, element, base); + driftView = computed; } else if (annotationClass == 'UseRowClass') { useRowClass = computed; } } - if (dataClassName != null && useRowClass != null) { + if (driftView != null && useRowClass != null) { base.step.reportError(ErrorInDartCode( message: "A table can't be annotated with both @DataClassName and " '@UseRowClass', @@ -63,7 +62,15 @@ class ViewParser { String? constructorInExistingClass; bool? generateInsertable; - var name = dataClassName ?? dataClassNameForClassName(element.name); + var name = dataClassNameForClassName(element.name); + + if (driftView != null) { + final dataClassName = + driftView.getField('dataClassName')?.toStringValue(); + name = dataClassName ?? dataClassNameForClassName(element.name); + customParentClass = + parseCustomParentClass(name, driftView, element, base); + } if (useRowClass != null) { final type = useRowClass.getField('type')!.toTypeValue(); diff --git a/drift_dev/lib/src/analyzer/data_class.dart b/drift_dev/lib/src/analyzer/data_class.dart index 3506f283..18abaa8d 100644 --- a/drift_dev/lib/src/analyzer/data_class.dart +++ b/drift_dev/lib/src/analyzer/data_class.dart @@ -3,6 +3,7 @@ import 'package:analyzer/dart/element/element.dart'; import 'package:analyzer/dart/element/type.dart'; import 'package:drift_dev/src/analyzer/dart/parser.dart'; import 'package:drift_dev/src/analyzer/errors.dart'; +import 'package:drift_dev/src/utils/type_utils.dart'; String dataClassNameForClassName(String tableName) { // This implementation is very primitive at the moment. The basic idea is @@ -21,19 +22,53 @@ String dataClassNameForClassName(String tableName) { return '${tableName}Data'; } -String? parseCustomParentClass( - DartObject dataClassName, ClassElement element, MoorDartParser base) { +String? parseCustomParentClass(String dartTypeName, DartObject dataClassName, + ClassElement element, MoorDartParser base) { final extending = dataClassName.getField('extending'); if (extending != null && !extending.isNull) { final extendingType = extending.toTypeValue(); if (extendingType is InterfaceType) { + final superType = extendingType.allSupertypes + .any((type) => isFromMoor(type) && type.element.name == 'DataClass'); + if (!superType) { + base.step.reportError( + ErrorInDartCode( + message: 'Parameter `extending` in @DataClassName must be subtype ' + 'of DataClass', + affectedElement: element, + ), + ); + return null; + } + + if (extendingType.typeArguments.length > 1) { + base.step.reportError( + ErrorInDartCode( + message: 'Parameter `extending` in @DataClassName must have zero or' + ' one type parameter', + affectedElement: element, + ), + ); + return null; + } + final className = extendingType.element.name; if (extendingType.typeArguments.length == 1) { final genericType = extendingType.typeArguments[0].element?.name; - if (genericType == 'dynamic') { - return '$className'; + if (genericType == 'Object') { + return '$className<$dartTypeName>'; + } else { + base.step.reportError( + ErrorInDartCode( + message: 'Parameter `extending` in @DataClassName can only have ' + '`Object` as type parameter: `YourType`', + affectedElement: element, + ), + ); + return null; } } + return className; } else { base.step.reportError( diff --git a/drift_dev/lib/src/writer/tables/data_class_writer.dart b/drift_dev/lib/src/writer/tables/data_class_writer.dart index 92a5d4fc..ba830521 100644 --- a/drift_dev/lib/src/writer/tables/data_class_writer.dart +++ b/drift_dev/lib/src/writer/tables/data_class_writer.dart @@ -24,9 +24,7 @@ class DataClassWriter { : 'driftRuntimeOptions'; void write() { - final customParent = table.customParentClass - ?.replaceFirst('', '<${table.dartTypeName}>'); - final parentClass = customParent ?? 'DataClass'; + final parentClass = table.customParentClass ?? 'DataClass'; _buffer.write('class ${table.dartTypeName} extends $parentClass '); if (isInsertable) { From bfc49218092acf12d49b98ca79a95da9a9ea29d7 Mon Sep 17 00:00:00 2001 From: westito Date: Mon, 14 Feb 2022 14:20:21 +0100 Subject: [PATCH 4/8] Allow dynamic as type parameter --- drift_dev/lib/src/analyzer/data_class.dart | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/drift_dev/lib/src/analyzer/data_class.dart b/drift_dev/lib/src/analyzer/data_class.dart index 18abaa8d..4530faff 100644 --- a/drift_dev/lib/src/analyzer/data_class.dart +++ b/drift_dev/lib/src/analyzer/data_class.dart @@ -55,13 +55,14 @@ String? parseCustomParentClass(String dartTypeName, DartObject dataClassName, final className = extendingType.element.name; if (extendingType.typeArguments.length == 1) { final genericType = extendingType.typeArguments[0].element?.name; - if (genericType == 'Object') { + if (genericType == 'Object' || genericType == 'dynamic') { return '$className<$dartTypeName>'; } else { base.step.reportError( ErrorInDartCode( - message: 'Parameter `extending` in @DataClassName can only have ' - '`Object` as type parameter: `YourType`', + message: 'Parameter `extending` in @DataClassName can only be ' + 'provided as `$className`, `$className` or ' + ' with omitted type parameter (`$className`)', affectedElement: element, ), ); From 3872f14d2ffa6e75a62eab97f849b4ae877d47d3 Mon Sep 17 00:00:00 2001 From: westito Date: Mon, 14 Feb 2022 14:24:22 +0100 Subject: [PATCH 5/8] Fix error message in parent class parse --- drift_dev/lib/src/analyzer/data_class.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drift_dev/lib/src/analyzer/data_class.dart b/drift_dev/lib/src/analyzer/data_class.dart index 4530faff..c1401d48 100644 --- a/drift_dev/lib/src/analyzer/data_class.dart +++ b/drift_dev/lib/src/analyzer/data_class.dart @@ -62,7 +62,7 @@ String? parseCustomParentClass(String dartTypeName, DartObject dataClassName, ErrorInDartCode( message: 'Parameter `extending` in @DataClassName can only be ' 'provided as `$className`, `$className` or ' - ' with omitted type parameter (`$className`)', + 'without declared type parameter (`$className`)', affectedElement: element, ), ); From e56260740dba1360efffef0000d648fe659296cb Mon Sep 17 00:00:00 2001 From: westito Date: Fri, 18 Feb 2022 10:12:35 +0100 Subject: [PATCH 6/8] Update drift_dev/lib/src/analyzer/data_class.dart Co-authored-by: Simon Binder --- drift_dev/lib/src/analyzer/data_class.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drift_dev/lib/src/analyzer/data_class.dart b/drift_dev/lib/src/analyzer/data_class.dart index c1401d48..f04644e6 100644 --- a/drift_dev/lib/src/analyzer/data_class.dart +++ b/drift_dev/lib/src/analyzer/data_class.dart @@ -74,8 +74,8 @@ String? parseCustomParentClass(String dartTypeName, DartObject dataClassName, } else { base.step.reportError( ErrorInDartCode( - message: 'Parameter `extending` in @DataClassName must be used with a' - ' class', + message: 'Parameter `extending` in @DataClassName must be used with ' + 'a class', affectedElement: element, ), ); From 9bcac38e987421ab06369d4afbc5bcd474e9e330 Mon Sep 17 00:00:00 2001 From: westito Date: Fri, 18 Feb 2022 10:50:03 +0100 Subject: [PATCH 7/8] Add tests for custom data class parent --- .gitignore | 1 + .../dart/custom_row_classes_test.dart | 176 ++++++++++++++++++ 2 files changed, 177 insertions(+) diff --git a/.gitignore b/.gitignore index 52f07f0d..78b82a71 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,4 @@ benchmark_results.json flutter_export_environment.sh # Local Netlify folder .netlify +.DS_Store diff --git a/drift_dev/test/analyzer/dart/custom_row_classes_test.dart b/drift_dev/test/analyzer/dart/custom_row_classes_test.dart index 614677b6..ebb4c6ea 100644 --- a/drift_dev/test/analyzer/dart/custom_row_classes_test.dart +++ b/drift_dev/test/analyzer/dart/custom_row_classes_test.dart @@ -148,6 +148,98 @@ class Cls extends HasBar { Cls(this.foo, int bar): super(bar); } +''', + 'a|lib/custom_parent_class_no_error.dart': ''' +import 'package:drift/drift.dart'; + +abstract class BaseModel extends DataClass { + abstract final String id; +} + +@DataClassName('Company', extending: BaseModel) +class Companies extends Table { + TextColumn get id => text()(); + TextColumn get name => text().named('name')(); +} +''', + 'a|lib/custom_parent_class_typed_no_error.dart': ''' +import 'package:drift/drift.dart'; + +abstract class BaseModel extends DataClass { + abstract final String id; +} + +@DataClassName('Company', extending: BaseModel) +class Companies extends Table { + TextColumn get id => text()(); + TextColumn get name => text().named('name')(); +} +''', + 'a|lib/custom_parent_class_no_super.dart': ''' +import 'package:drift/drift.dart'; + +abstract class BaseModel { + abstract final String id; +} + +@DataClassName('Company', extending: BaseModel) +class Companies extends Table { + TextColumn get id => text()(); + TextColumn get name => text().named('name')(); +} +''', + 'a|lib/custom_parent_class_wrong_super.dart': ''' +import 'package:drift/drift.dart'; + +class Test { +} + +abstract class BaseModel extends Test { + abstract final String id; +} + +@DataClassName('Company', extending: BaseModel) +class Companies extends Table { + TextColumn get id => text()(); + TextColumn get name => text().named('name')(); +} +''', + 'a|lib/custom_parent_class_typed_wrong_type_arg.dart': ''' +import 'package:drift/drift.dart'; + +abstract class BaseModel extends DataClass { + abstract final String id; +} + +@DataClassName('Company', extending: BaseModel) +class Companies extends Table { + TextColumn get id => text()(); + TextColumn get name => text().named('name')(); +} +''', + 'a|lib/custom_parent_class_two_type_argument.dart': ''' +import 'package:drift/drift.dart'; + +abstract class BaseModel extends DataClass { + abstract final String id; +} + +@DataClassName('Company', extending: BaseModel) +class Companies extends Table { + TextColumn get id => text()(); + TextColumn get name => text().named('name')(); +} +''', + 'a|lib/custom_parent_class_not_class.dart': ''' +import 'package:drift/drift.dart'; + +typedef NotClass = void Function(); + +@DataClassName('Company', extending: NotClass) +class Companies extends Table { + TextColumn get id => text()(); + TextColumn get name => text().named('name')(); +} ''', }); }); @@ -268,4 +360,88 @@ class Cls extends HasBar { final file = await state.analyze('package:a/insertable_valid.dart'); expect(file.errors.errors, isEmpty); }); + + group('custom data class parent', () { + test('check valid', () async { + final file = + await state.analyze('package:a/custom_parent_class_no_error.dart'); + expect(file.errors.errors, isEmpty); + }); + + test('check valid with type argument', () async { + final file = await state + .analyze('package:a/custom_parent_class_typed_no_error.dart'); + expect(file.errors.errors, isEmpty); + }); + + test('check extends DataClass (no super)', () async { + final file = + await state.analyze('package:a/custom_parent_class_no_super.dart'); + + expect( + file.errors.errors, + contains(isA().having( + (e) => e.message, + 'message', + contains('Parameter `extending` in ' + '@DataClassName must be subtype of DataClass'))), + ); + }); + + test('extends DataClass (wrong super)', () async { + final file = + await state.analyze('package:a/custom_parent_class_wrong_super.dart'); + + expect( + file.errors.errors, + contains(isA().having( + (e) => e.message, + 'message', + contains('Parameter `extending` in ' + '@DataClassName must be subtype of DataClass'))), + ); + }); + + test('wrong type argument in extending', () async { + final file = await state + .analyze('package:a/custom_parent_class_typed_wrong_type_arg.dart'); + + expect( + file.errors.errors, + contains(isA().having( + (e) => e.message, + 'message', + contains('Parameter `extending` in @DataClassName can only be ' + 'provided as'))), + ); + }); + + test('two type arguments in parent class', () async { + final file = await state + .analyze('package:a/custom_parent_class_two_type_argument.dart'); + + expect( + file.errors.errors, + contains(isA().having( + (e) => e.message, + 'message', + contains('Parameter `extending` in @DataClassName must have zero ' + 'or one type parameter'))), + ); + }); + + test('not a class in extending', () async { + final file = + await state.analyze('package:a/custom_parent_class_not_class.dart'); + + expect( + file.errors.errors, + contains(isA().having( + (e) => e.message, + 'message', + contains('Parameter `extending` in @DataClassName must be used ' + 'with a class'))), + ); + }); + }); } From 6184892217902461743fd72108a8a4635e6f2605 Mon Sep 17 00:00:00 2001 From: Simon Binder Date: Fri, 18 Feb 2022 15:09:51 +0100 Subject: [PATCH 8/8] Add more docs, changelog --- drift/CHANGELOG.md | 5 +++++ drift/lib/src/dsl/table.dart | 32 +++++++++++++++++++++++++++++++- drift/pubspec.yaml | 2 +- drift_dev/pubspec.yaml | 4 ++-- 4 files changed, 39 insertions(+), 4 deletions(-) diff --git a/drift/CHANGELOG.md b/drift/CHANGELOG.md index 61e4a3a0..9fb14546 100644 --- a/drift/CHANGELOG.md +++ b/drift/CHANGELOG.md @@ -1,3 +1,8 @@ +## 1.5.0-dev + +- Add `DataClassName.extending` to control the superclass of generated row + classes. + ## 1.4.0 - Most methods to compose statements are now available as an extension on diff --git a/drift/lib/src/dsl/table.dart b/drift/lib/src/dsl/table.dart index d3e7097c..e00e000a 100644 --- a/drift/lib/src/dsl/table.dart +++ b/drift/lib/src/dsl/table.dart @@ -184,7 +184,37 @@ class DataClassName { /// {@macro drift_custom_data_class} final String name; - /// The parent class of generated data class. Class must extends [DataClass]! + /// The parent type of the data class generated by drift. + /// + /// The [extending] type must refer to an interface type (usually just a + /// class name), and the parent class must extend [DataClass]. + /// + /// The extended class can optionally have a type parameter, which is + /// instantiated to the actual data class generated by drift. + /// + /// For example, + /// + /// ```dart + /// abstract class BaseModel extends DataClass { + /// abstract final String id; + /// } + /// + /// abstract class TypedBaseModel extends DataClass { + /// + /// } + /// + /// @DataClassName('Company', extending: BaseModel) + /// class Companies extends Table { + /// TextColumn get id => text()(); + /// TextColumn get name => text().named('name')(); + /// } + /// + /// // The actual generated class will extend `TypedBaseModel`. + /// @DataClassName('Employee', extending: TypedBaseModel) + /// class Employees extends Table { + /// TextColumn get id => text()(); + /// } + /// ``` final Type? extending; /// Customize the data class name for a given table. diff --git a/drift/pubspec.yaml b/drift/pubspec.yaml index ffe60ae4..f136a7f6 100644 --- a/drift/pubspec.yaml +++ b/drift/pubspec.yaml @@ -1,6 +1,6 @@ name: drift description: Drift is a reactive library to store relational data in Dart and Flutter applications. -version: 1.4.0 +version: 1.5.0-dev repository: https://github.com/simolus3/moor homepage: https://drift.simonbinder.eu/ issue_tracker: https://github.com/simolus3/moor/issues diff --git a/drift_dev/pubspec.yaml b/drift_dev/pubspec.yaml index 1ed59f32..546922d4 100644 --- a/drift_dev/pubspec.yaml +++ b/drift_dev/pubspec.yaml @@ -1,6 +1,6 @@ name: drift_dev description: Dev-dependency for users of drift. Contains a the generator and development tools. -version: 1.4.0 +version: 1.5.0-dev repository: https://github.com/simolus3/moor homepage: https://drift.simonbinder.eu/ issue_tracker: https://github.com/simolus3/moor/issues @@ -25,7 +25,7 @@ dependencies: io: ^1.0.3 # Drift-specific analysis and apis - drift: '>=1.4.0 <1.5.0' + drift: '>=1.5.0 <1.6.0' sqlite3: '>=0.1.6 <2.0.0' sqlparser: ^0.20.0