Merge pull request #1932 from ValentinVignal/const-constructor

Add a `const` constructor to non mutable classes
This commit is contained in:
Simon Binder 2022-07-20 21:31:49 +02:00 committed by GitHub
commit 3564d725a9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 182 additions and 51 deletions

2
.gitignore vendored
View File

@ -16,3 +16,5 @@ flutter_export_environment.sh
.DS_Store
docs/**/*.g.dart
*/build/

View File

@ -10,7 +10,7 @@ part of 'main.dart';
class TodoCategory extends DataClass implements Insertable<TodoCategory> {
final int id;
final String name;
TodoCategory({required this.id, required this.name});
const TodoCategory({required this.id, required this.name});
factory TodoCategory.fromData(Map<String, dynamic> data, {String? prefix}) {
final effectivePrefix = prefix ?? '';
return TodoCategory(
@ -189,7 +189,7 @@ class TodoItem extends DataClass implements Insertable<TodoItem> {
final String? content;
final int categoryId;
final String? generatedText;
TodoItem(
const TodoItem(
{required this.id,
required this.title,
this.content,
@ -474,7 +474,8 @@ class $TodoItemsTable extends TodoItems
class TodoCategoryItemCountData extends DataClass {
final String name;
final int itemCount;
TodoCategoryItemCountData({required this.name, required this.itemCount});
const TodoCategoryItemCountData(
{required this.name, required this.itemCount});
factory TodoCategoryItemCountData.fromData(Map<String, dynamic> data,
{String? prefix}) {
final effectivePrefix = prefix ?? '';
@ -582,7 +583,8 @@ class $TodoCategoryItemCountView
class TodoItemWithCategoryNameViewData extends DataClass {
final int id;
final String title;
TodoItemWithCategoryNameViewData({required this.id, required this.title});
const TodoItemWithCategoryNameViewData(
{required this.id, required this.title});
factory TodoItemWithCategoryNameViewData.fromData(Map<String, dynamic> data,
{String? prefix}) {
final effectivePrefix = prefix ?? '';

View File

@ -189,7 +189,7 @@ void main() {
test('updates stream queries', () async {
await db.batch((b) {
b.insert(db.todosTable, TodoEntry(id: 3, content: 'content'));
b.insert(db.todosTable, const TodoEntry(id: 3, content: 'content'));
b.update(db.users, const UsersCompanion(name: Value('new user name')));
b.replace(

View File

@ -77,7 +77,7 @@ void main() {
});
test('generated data classes can be converted to companions', () {
final entry = Category(
const entry = Category(
id: 3,
description: 'description',
priority: CategoryPriority.low,
@ -97,7 +97,7 @@ void main() {
});
test('data classes can be converted to companions with null to absent', () {
final entry = PureDefault(txt: null);
const entry = PureDefault(txt: null);
expect(entry.toCompanion(false),
const PureDefaultsCompanion(txt: Value(null)));

View File

@ -33,14 +33,18 @@ void main() {
.go();
verify(executor.runDelete(
'DELETE FROM users WHERE NOT is_awesome OR id < ?;', [100]));
'DELETE FROM users WHERE NOT is_awesome OR id < ?;', const [100]));
});
test('to delete an entity via a dataclasss', () async {
await db.delete(db.sharedTodos).delete(SharedTodo(todo: 3, user: 2));
await db
.delete(db.sharedTodos)
.delete(const SharedTodo(todo: 3, user: 2));
verify(executor.runDelete(
'DELETE FROM shared_todos WHERE todo = ? AND user = ?;', [3, 2]));
'DELETE FROM shared_todos WHERE todo = ? AND user = ?;',
const [3, 2],
));
});
});
@ -75,19 +79,19 @@ void main() {
test('delete()', () async {
await db.users.delete().go();
verify(executor.runDelete('DELETE FROM users;', []));
verify(executor.runDelete('DELETE FROM users;', const []));
});
test('deleteOne()', () async {
await db.users.deleteOne(const UsersCompanion(id: Value(3)));
verify(executor.runDelete('DELETE FROM users WHERE id = ?;', [3]));
verify(executor.runDelete('DELETE FROM users WHERE id = ?;', const [3]));
});
test('deleteWhere', () async {
await db.users.deleteWhere((tbl) => tbl.id.isSmallerThanValue(3));
verify(executor.runDelete('DELETE FROM users WHERE id < ?;', [3]));
verify(executor.runDelete('DELETE FROM users WHERE id < ?;', const [3]));
});
});
}

View File

@ -55,7 +55,7 @@ void main() {
test('generates insert or replace statements', () async {
await db.into(db.todosTable).insert(
TodoEntry(
const TodoEntry(
id: 113,
content: 'Done',
),
@ -452,7 +452,7 @@ void main() {
CategoriesCompanion.insert(description: 'description'));
expect(
row,
Category(
const Category(
id: 1,
description: 'description',
descriptionInUpperCase: 'DESCRIPTION',
@ -503,7 +503,7 @@ void main() {
CategoriesCompanion.insert(description: 'description'));
expect(
row,
Category(
const Category(
id: 1,
description: 'description',
descriptionInUpperCase: 'DESCRIPTION',

View File

@ -72,7 +72,7 @@ void main() {
expect(
row.readTable(categories),
Category(
const Category(
id: 3,
description: 'description',
priority: CategoryPriority.high,
@ -107,7 +107,7 @@ void main() {
expect(() => row.readTable(db.categories), throwsArgumentError);
expect(
row.readTable(db.todosTable),
TodoEntry(
const TodoEntry(
id: 5,
title: 'title',
content: 'content',
@ -224,7 +224,7 @@ void main() {
expect(
result.readTable(categories),
equals(
Category(
const Category(
id: 3,
description: 'Description',
descriptionInUpperCase: 'DESCRIPTION',
@ -274,7 +274,7 @@ void main() {
expect(
result.readTable(categories),
equals(
Category(
const Category(
id: 3,
description: 'Description',
descriptionInUpperCase: 'DESCRIPTION',
@ -330,7 +330,7 @@ void main() {
expect(result.readTableOrNull(todos), isNull);
expect(
result.readTable(categories),
Category(
const Category(
id: 3,
description: 'desc',
descriptionInUpperCase: 'DESC',

View File

@ -14,7 +14,7 @@ final _dataOfTodoEntry = {
'category': 3
};
final _todoEntry = TodoEntry(
const _todoEntry = TodoEntry(
id: 10,
title: 'A todo title',
content: 'Content',
@ -123,7 +123,7 @@ void main() {
'category': null,
}
];
final resolved = TodoEntry(
const resolved = TodoEntry(
id: 10,
title: null,
content: 'Content',
@ -193,7 +193,7 @@ void main() {
expect(
category,
Category(
const Category(
id: 1,
description: 'description',
descriptionInUpperCase: 'DESCRIPTION',

View File

@ -52,7 +52,7 @@ void main() {
group('generates replace statements', () {
test('regular', () async {
await db.update(db.todosTable).replace(TodoEntry(
await db.update(db.todosTable).replace(const TodoEntry(
id: 3,
title: 'Title',
content: 'Updated content',

View File

@ -43,7 +43,7 @@ void main() {
final user = db.sharedTodos.mapFromCompanion(companion);
expect(
user,
SharedTodo(todo: 3, user: 4),
const SharedTodo(todo: 3, user: 4),
);
});
@ -58,7 +58,7 @@ void main() {
final todo = db.todosTable.mapFromRowOrNull(QueryRow(rowData, db));
expect(
todo,
TodoEntry(
const TodoEntry(
id: 1,
title: 'some title',
content: 'do this',

View File

@ -12,7 +12,7 @@ class Config extends DataClass implements Insertable<Config> {
final String? configValue;
final SyncType? syncState;
final SyncType? syncStateImplicit;
Config(
const Config(
{required this.configKey,
this.configValue,
this.syncState,
@ -296,7 +296,7 @@ class ConfigTable extends Table with TableInfo<ConfigTable, Config> {
class WithDefault extends DataClass implements Insertable<WithDefault> {
final String? a;
final int? b;
WithDefault({this.a, this.b});
const WithDefault({this.a, this.b});
factory WithDefault.fromData(Map<String, dynamic> data, {String? prefix}) {
final effectivePrefix = prefix ?? '';
return WithDefault(
@ -572,7 +572,7 @@ class WithConstraint extends DataClass implements Insertable<WithConstraint> {
final String? a;
final int b;
final double? c;
WithConstraint({this.a, required this.b, this.c});
const WithConstraint({this.a, required this.b, this.c});
factory WithConstraint.fromData(Map<String, dynamic> data, {String? prefix}) {
final effectivePrefix = prefix ?? '';
return WithConstraint(
@ -792,7 +792,7 @@ class MytableData extends DataClass implements Insertable<MytableData> {
final String? sometext;
final bool? isInserting;
final DateTime? somedate;
MytableData(
const MytableData(
{required this.someid, this.sometext, this.isInserting, this.somedate});
factory MytableData.fromData(Map<String, dynamic> data, {String? prefix}) {
final effectivePrefix = prefix ?? '';
@ -1059,7 +1059,7 @@ class EMail extends DataClass implements Insertable<EMail> {
final String sender;
final String title;
final String body;
EMail({required this.sender, required this.title, required this.body});
const EMail({required this.sender, required this.title, required this.body});
factory EMail.fromData(Map<String, dynamic> data, {String? prefix}) {
final effectivePrefix = prefix ?? '';
return EMail(
@ -1278,7 +1278,7 @@ class Email extends Table
class WeirdData extends DataClass implements Insertable<WeirdData> {
final int sqlClass;
final String textColumn;
WeirdData({required this.sqlClass, required this.textColumn});
const WeirdData({required this.sqlClass, required this.textColumn});
factory WeirdData.fromData(Map<String, dynamic> data, {String? prefix}) {
final effectivePrefix = prefix ?? '';
return WeirdData(
@ -1465,7 +1465,7 @@ class MyViewData extends DataClass {
final String? configValue;
final SyncType? syncState;
final SyncType? syncStateImplicit;
MyViewData(
const MyViewData(
{required this.configKey,
this.configValue,
this.syncState,

View File

@ -12,7 +12,7 @@ class Category extends DataClass implements Insertable<Category> {
final String description;
final CategoryPriority priority;
final String descriptionInUpperCase;
Category(
const Category(
{required this.id,
required this.description,
required this.priority,
@ -269,7 +269,7 @@ class TodoEntry extends DataClass implements Insertable<TodoEntry> {
final String content;
final DateTime? targetDate;
final int? category;
TodoEntry(
const TodoEntry(
{required this.id,
this.title,
required this.content,
@ -576,7 +576,7 @@ class User extends DataClass implements Insertable<User> {
final bool isAwesome;
final Uint8List profilePicture;
final DateTime creationTime;
User(
const User(
{required this.id,
required this.name,
required this.isAwesome,
@ -873,7 +873,7 @@ class $UsersTable extends Users with TableInfo<$UsersTable, User> {
class SharedTodo extends DataClass implements Insertable<SharedTodo> {
final int todo;
final int user;
SharedTodo({required this.todo, required this.user});
const SharedTodo({required this.todo, required this.user});
factory SharedTodo.fromData(Map<String, dynamic> data, {String? prefix}) {
final effectivePrefix = prefix ?? '';
return SharedTodo(
@ -1242,7 +1242,7 @@ class $TableWithoutPKTable extends TableWithoutPK
class PureDefault extends DataClass implements Insertable<PureDefault> {
final MyCustomObject? txt;
PureDefault({this.txt});
const PureDefault({this.txt});
factory PureDefault.fromData(Map<String, dynamic> data, {String? prefix}) {
final effectivePrefix = prefix ?? '';
return PureDefault(
@ -1397,7 +1397,7 @@ class $PureDefaultsTable extends PureDefaults
class CategoryTodoCountViewData extends DataClass {
final String description;
final int itemCount;
CategoryTodoCountViewData(
const CategoryTodoCountViewData(
{required this.description, required this.itemCount});
factory CategoryTodoCountViewData.fromData(Map<String, dynamic> data,
{String? prefix}) {
@ -1507,7 +1507,7 @@ class $CategoryTodoCountViewView
class TodoWithCategoryViewData extends DataClass {
final String? title;
final String description;
TodoWithCategoryViewData({this.title, required this.description});
const TodoWithCategoryViewData({this.title, required this.description});
factory TodoWithCategoryViewData.fromData(Map<String, dynamic> data,
{String? prefix}) {
final effectivePrefix = prefix ?? '';

View File

@ -69,7 +69,7 @@ void main() {
test('can be used in a query stream', () async {
final stream = db.readView().watch();
final entry = Config(
const entry = Config(
configKey: 'another_key',
configValue: 'value',
syncState: SyncType.synchronized,
@ -141,7 +141,7 @@ void main() {
expect(result, hasLength(1));
expect(
result.single,
Config(
const Config(
configKey: 'key2',
configValue: 'val',
syncState: SyncType.locallyCreated,

View File

@ -124,7 +124,7 @@ void main() {
verify(
mock.runSelect('SELECT * FROM config WHERE config_key = ?1', ['key']));
expect(parsed, Config(configKey: 'key', configValue: 'value'));
expect(parsed, const Config(configKey: 'key', configValue: 'value'));
});
test('applies default parameter expressions when not set', () async {
@ -167,7 +167,7 @@ void main() {
row: QueryRow(row, db),
a: 'text for a',
b: 42,
c: WithConstraint(a: 'text', b: 1337, c: 18.7),
c: const WithConstraint(a: 'text', b: 1337, c: 18.7),
),
);
});
@ -216,7 +216,7 @@ void main() {
final entry = await db.readConfig('key').getSingle();
expect(
entry,
Config(
const Config(
configKey: 'key',
configValue: 'value',
syncState: SyncType.locallyUpdated,

View File

@ -104,7 +104,7 @@ void main() {
expect(
entry,
Category(
const Category(
id: 1,
description: 'Description',
priority: CategoryPriority.low,

View File

@ -14,7 +14,12 @@ void main() {
db.select(db.myView).watch(),
emitsInOrder([
isEmpty,
[MyViewData(configKey: 'another', syncState: SyncType.synchronized)]
[
const MyViewData(
configKey: 'another',
syncState: SyncType.synchronized,
),
]
]),
);

View File

@ -40,7 +40,7 @@ void main() {
final result = await db.todoWithCategoryView.select().getSingle();
expect(
result,
TodoWithCategoryViewData(
const TodoWithCategoryViewData(
description: 'category description', title: 'title'));
});
}

View File

@ -171,7 +171,10 @@ void _runTests(FutureOr<DriftIsolate> Function() spawner, bool terminateIsolate,
await expectLater(stream, emits(null));
await database.into(database.todosTable).insert(initialCompanion);
await expectLater(stream, emits(TodoEntry(id: 1, content: 'my content')));
await expectLater(
stream,
emits(const TodoEntry(id: 1, content: 'my content')),
);
});
test('can start transactions', () async {

View File

@ -54,6 +54,10 @@ class DataClassWriter {
}
// write constructor with named optional fields
if (!scope.options.generateMutableClasses) {
_buffer.write('const ');
}
_buffer
..write(table.dartTypeName)
..write('({')

View File

@ -2,7 +2,7 @@ import 'package:drift_dev/src/model/model.dart';
import 'package:test/test.dart';
void main() {
test('removes leaading numbers', () {
test('removes leading numbers', () {
expect(dartNameForSqlColumn('foo'), 'foo');
expect(dartNameForSqlColumn('123a'), 'a');
});

View File

@ -0,0 +1,111 @@
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:drift_dev/src/backends/build/drift_builder.dart';
import 'package:pub_semver/pub_semver.dart';
import 'package:test/test.dart';
const _testInput = r'''
import 'package:drift/drift.dart';
part 'main.moor.dart';
class Users extends Table {
IntColumn get id => integer().autoIncrement()();
TextColumn get name => text()();
}
@DriftDatabase(
tables: [Users],
)
class Database extends _$Database {}
''';
void main() {
test(
'generates const constructor for data classes can companion classes',
() async {
await testBuilder(
DriftPartBuilder(const BuilderOptions({})),
const {'a|lib/main.dart': _testInput},
reader: await PackageAssetReader.currentIsolate(),
outputs: const {
'a|lib/main.moor.dart': _GeneratesConstDataClasses(
{'User', 'UsersCompanion'},
),
},
);
},
tags: 'analyzer',
);
}
class _GeneratesConstDataClasses extends Matcher {
final Set<String> expectedWithConstConstructor;
const _GeneratesConstDataClasses(this.expectedWithConstConstructor);
@override
Description describe(Description description) {
return description.add('generates classes $expectedWithConstConstructor '
'const constructor.');
}
@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.fromEnableFlags2(
sdkLanguageVersion: Version(2, 12, 0),
flags: const [],
),
resourceProvider: resourceProvider,
throwIfDiagnostics: true,
).unit;
final remaining = expectedWithConstConstructor.toSet();
final definedClasses = parsed.declarations.whereType<ClassDeclaration>();
for (final definedClass in definedClasses) {
if (expectedWithConstConstructor.contains(definedClass.name.name)) {
final constructor = definedClass.getConstructor(null);
if (constructor?.constKeyword == null) {
matchState['desc'] = 'Constructor ${definedClass.name.name} is not '
'const.';
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?) ?? 'Had syntax errors');
}
}