mirror of https://github.com/AMT-Cheif/drift.git
Option to generate mutable data classes (#551)
This commit is contained in:
parent
896d62d76c
commit
f6ab5f64a8
|
@ -62,6 +62,8 @@ At the moment, moor supports these options:
|
||||||
If you're using this flag, please open an issue and explain how the new inference isn't working for you, thanks!
|
If you're using this flag, please open an issue and explain how the new inference isn't working for you, thanks!
|
||||||
* `data_class_to_companions` (defaults to `true`): Controls whether moor will write the `toCompanion` method in generated
|
* `data_class_to_companions` (defaults to `true`): Controls whether moor will write the `toCompanion` method in generated
|
||||||
data classes.
|
data classes.
|
||||||
|
* `mutable_classes` (defaults to `false`): The fields generated in generated data, companion and result set classes are final
|
||||||
|
by default. You can make them mutable by setting `mutable_classes: true`.
|
||||||
|
|
||||||
## Available extensions
|
## Available extensions
|
||||||
|
|
||||||
|
|
|
@ -68,6 +68,9 @@ class MoorOptions {
|
||||||
@JsonKey(name: 'data_class_to_companions', defaultValue: true)
|
@JsonKey(name: 'data_class_to_companions', defaultValue: true)
|
||||||
final bool dataClassToCompanions;
|
final bool dataClassToCompanions;
|
||||||
|
|
||||||
|
@JsonKey(name: 'mutable_classes', defaultValue: false)
|
||||||
|
final bool generateMutableClasses;
|
||||||
|
|
||||||
/// Whether the [module] has been enabled in this configuration.
|
/// Whether the [module] has been enabled in this configuration.
|
||||||
bool hasModule(SqlModule module) => modules.contains(module);
|
bool hasModule(SqlModule module) => modules.contains(module);
|
||||||
|
|
||||||
|
@ -82,6 +85,7 @@ class MoorOptions {
|
||||||
this.legacyTypeInference = false,
|
this.legacyTypeInference = false,
|
||||||
this.eagerlyLoadDartAst = false,
|
this.eagerlyLoadDartAst = false,
|
||||||
this.dataClassToCompanions = true,
|
this.dataClassToCompanions = true,
|
||||||
|
this.generateMutableClasses = false,
|
||||||
this.modules = const [],
|
this.modules = const [],
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -19,7 +19,8 @@ MoorOptions _$MoorOptionsFromJson(Map<String, dynamic> json) {
|
||||||
'legacy_type_inference',
|
'legacy_type_inference',
|
||||||
'sqlite_modules',
|
'sqlite_modules',
|
||||||
'eagerly_load_dart_ast',
|
'eagerly_load_dart_ast',
|
||||||
'data_class_to_companions'
|
'data_class_to_companions',
|
||||||
|
'mutable_classes'
|
||||||
]);
|
]);
|
||||||
final val = MoorOptions(
|
final val = MoorOptions(
|
||||||
generateFromJsonStringConstructor: $checkedConvert(
|
generateFromJsonStringConstructor: $checkedConvert(
|
||||||
|
@ -54,6 +55,8 @@ MoorOptions _$MoorOptionsFromJson(Map<String, dynamic> json) {
|
||||||
dataClassToCompanions:
|
dataClassToCompanions:
|
||||||
$checkedConvert(json, 'data_class_to_companions', (v) => v as bool) ??
|
$checkedConvert(json, 'data_class_to_companions', (v) => v as bool) ??
|
||||||
true,
|
true,
|
||||||
|
generateMutableClasses:
|
||||||
|
$checkedConvert(json, 'mutable_classes', (v) => v as bool) ?? false,
|
||||||
modules: $checkedConvert(
|
modules: $checkedConvert(
|
||||||
json,
|
json,
|
||||||
'sqlite_modules',
|
'sqlite_modules',
|
||||||
|
@ -76,6 +79,7 @@ MoorOptions _$MoorOptionsFromJson(Map<String, dynamic> json) {
|
||||||
'legacyTypeInference': 'legacy_type_inference',
|
'legacyTypeInference': 'legacy_type_inference',
|
||||||
'eagerlyLoadDartAst': 'eagerly_load_dart_ast',
|
'eagerlyLoadDartAst': 'eagerly_load_dart_ast',
|
||||||
'dataClassToCompanions': 'data_class_to_companions',
|
'dataClassToCompanions': 'data_class_to_companions',
|
||||||
|
'generateMutableClasses': 'mutable_classes',
|
||||||
'modules': 'sqlite_modules'
|
'modules': 'sqlite_modules'
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,11 +14,14 @@ class ResultSetWriter {
|
||||||
final into = scope.leaf();
|
final into = scope.leaf();
|
||||||
|
|
||||||
into.write('class $className {\n');
|
into.write('class $className {\n');
|
||||||
|
final modifier = scope.options.fieldModifier;
|
||||||
|
|
||||||
// write fields
|
// write fields
|
||||||
for (final column in query.resultSet.columns) {
|
for (final column in query.resultSet.columns) {
|
||||||
final name = query.resultSet.dartNameFor(column);
|
final name = query.resultSet.dartNameFor(column);
|
||||||
final runtimeType = column.dartType;
|
final runtimeType = column.dartType;
|
||||||
into.write('final $runtimeType $name\n;');
|
|
||||||
|
into.write('$modifier $runtimeType $name\n;');
|
||||||
|
|
||||||
fieldNames.add(name);
|
fieldNames.add(name);
|
||||||
}
|
}
|
||||||
|
@ -27,7 +30,7 @@ class ResultSetWriter {
|
||||||
final typeName = nested.table.dartTypeName;
|
final typeName = nested.table.dartTypeName;
|
||||||
final fieldName = nested.dartFieldName;
|
final fieldName = nested.dartFieldName;
|
||||||
|
|
||||||
into.write('final $typeName $fieldName;\n');
|
into.write('$modifier $typeName $fieldName;\n');
|
||||||
|
|
||||||
fieldNames.add(fieldName);
|
fieldNames.add(fieldName);
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,8 +19,9 @@ class DataClassWriter {
|
||||||
|
|
||||||
// write individual fields
|
// write individual fields
|
||||||
for (final column in table.columns) {
|
for (final column in table.columns) {
|
||||||
_buffer
|
final modifier = scope.options.fieldModifier;
|
||||||
.write('final ${column.dartTypeName} ${column.dartGetterName}; \n');
|
_buffer.write(
|
||||||
|
'$modifier ${column.dartTypeName} ${column.dartGetterName}; \n');
|
||||||
}
|
}
|
||||||
|
|
||||||
// write constructor with named optional fields
|
// write constructor with named optional fields
|
||||||
|
|
|
@ -30,7 +30,8 @@ class UpdateCompanionWriter {
|
||||||
|
|
||||||
void _writeFields() {
|
void _writeFields() {
|
||||||
for (final column in table.columns) {
|
for (final column in table.columns) {
|
||||||
_buffer.write('final Value<${column.dartTypeName}>'
|
final modifier = scope.options.fieldModifier;
|
||||||
|
_buffer.write('$modifier Value<${column.dartTypeName}>'
|
||||||
' ${column.dartGetterName};\n');
|
' ${column.dartGetterName};\n');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -121,3 +121,7 @@ class DartScope {
|
||||||
return other._id >= _id;
|
return other._id >= _id;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension WriterUtilsForOptions on MoorOptions {
|
||||||
|
String get fieldModifier => generateMutableClasses ? '' : 'final';
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,108 @@
|
||||||
|
import 'package:analyzer/dart/analysis/features.dart';
|
||||||
|
import 'package:analyzer/dart/analysis/utilities.dart';
|
||||||
|
import 'package:analyzer/dart/ast/ast.dart';
|
||||||
|
import 'package:analyzer/file_system/memory_file_system.dart';
|
||||||
|
import 'package:build/build.dart';
|
||||||
|
import 'package:build_test/build_test.dart';
|
||||||
|
import 'package:moor_generator/src/backends/build/moor_builder.dart';
|
||||||
|
import 'package:test/test.dart';
|
||||||
|
|
||||||
|
const _testInput = r'''
|
||||||
|
import 'package:moor/moor.dart';
|
||||||
|
|
||||||
|
part 'main.moor.dart';
|
||||||
|
|
||||||
|
class Users extends Table {
|
||||||
|
IntColumn get id => integer().autoIncrement()();
|
||||||
|
TextColumn get name => text()();
|
||||||
|
}
|
||||||
|
|
||||||
|
@UseMoor(
|
||||||
|
tables: [Users],
|
||||||
|
queries: {
|
||||||
|
'someQuery': 'SELECT 1 AS foo, 2 AS bar;',
|
||||||
|
},
|
||||||
|
)
|
||||||
|
class Database extends _$Database {}
|
||||||
|
''';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
test('generates mutable classes if needed', () async {
|
||||||
|
await testBuilder(
|
||||||
|
MoorPartBuilder(const BuilderOptions({'mutable_classes': true})),
|
||||||
|
const {'a|lib/main.dart': _testInput},
|
||||||
|
reader: await PackageAssetReader.currentIsolate(),
|
||||||
|
outputs: const {
|
||||||
|
'a|lib/main.moor.dart': _GeneratesWithoutFinalFields(
|
||||||
|
{'User', 'UsersCompanion', 'SomeQueryResult'},
|
||||||
|
),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}, tags: 'analyzer');
|
||||||
|
}
|
||||||
|
|
||||||
|
class _GeneratesWithoutFinalFields extends Matcher {
|
||||||
|
final Set<String> expectedWithoutFinals;
|
||||||
|
|
||||||
|
const _GeneratesWithoutFinalFields(this.expectedWithoutFinals);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Description describe(Description description) {
|
||||||
|
return description.add('generates classes $expectedWithoutFinals without '
|
||||||
|
'final fields.');
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool matches(dynamic desc, Map matchState) {
|
||||||
|
// Parse the file, assure we don't have final fields in data classes.
|
||||||
|
final resourceProvider = MemoryResourceProvider();
|
||||||
|
if (desc is List<int>) {
|
||||||
|
resourceProvider.newFileWithBytes('/foo.dart', desc);
|
||||||
|
} else if (desc is String) {
|
||||||
|
resourceProvider.newFile('/foo.dart', desc);
|
||||||
|
} else {
|
||||||
|
desc['desc'] = 'Neither a List<int> or String - cannot be parsed';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
final parsed = parseFile(
|
||||||
|
path: '/foo.dart',
|
||||||
|
featureSet: FeatureSet.forTesting(),
|
||||||
|
resourceProvider: resourceProvider,
|
||||||
|
).unit;
|
||||||
|
|
||||||
|
final remaining = expectedWithoutFinals.toSet();
|
||||||
|
|
||||||
|
final definedClasses = parsed.declarations.whereType<ClassDeclaration>();
|
||||||
|
for (final definedClass in definedClasses) {
|
||||||
|
if (expectedWithoutFinals.contains(definedClass.name.name)) {
|
||||||
|
final fields = definedClass.members.whereType<FieldDeclaration>();
|
||||||
|
|
||||||
|
for (final field in fields) {
|
||||||
|
if (field.fields.isFinal) {
|
||||||
|
matchState['desc'] =
|
||||||
|
'Field ${field.fields.variables.first.name.name} in '
|
||||||
|
'${definedClass.name.name} is final.';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
remaining.remove(definedClass.name.name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Also ensure that all expected classes were generated.
|
||||||
|
if (remaining.isNotEmpty) {
|
||||||
|
matchState['desc'] = 'Did not generate $remaining classes';
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Description describeMismatch(dynamic item, Description mismatchDescription,
|
||||||
|
Map matchState, bool verbose) {
|
||||||
|
return mismatchDescription.add(matchState['desc'] as String);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue