diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1698050a..e53c638d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -54,9 +54,9 @@ jobs: run: dartfmt -n --set-exit-if-changed . - name: Run tests - if: working-directory: floor_generator - run: pub run test_cov + # TODO #504 remove --no-sound-null-safety once all dependencies have been migrated + run: pub run test_cov --no-sound-null-safety - name: Upload coverage to Codecov uses: codecov/codecov-action@v1 @@ -86,7 +86,7 @@ jobs: working-directory: floor - name: Run generator - run: flutter packages pub run build_runner build + run: flutter packages pub run build_runner build --delete-conflicting-outputs working-directory: floor - name: Analyze diff --git a/CHANGELOG.md b/CHANGELOG.md index 54cc4604..588e1948 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,17 @@ # Changelog +# 1.0.0-nullsafety.1 + +### Changes + +* Migrate to stable Dart 2.12 + +# 1.0.0-nullsafety.0 + +### 🚀 Features + +* Make floor null safe + # 0.19.1 ### Changes @@ -84,7 +96,8 @@ ### ⚠️ Breaking Changes -**You need to migrate the explicit usages of `OnConflictStrategy` and `ForeignKeyAction` from snake case to camel case.** +**You need to migrate the explicit usages of `OnConflictStrategy` and `ForeignKeyAction` from snake case to camel +case.** * Apply camel case to constants diff --git a/README.md b/README.md index 4a9792f4..9247dd2d 100644 --- a/README.md +++ b/README.md @@ -36,11 +36,11 @@ The third dependency is `build_runner` which has to be included as a dev depende dependencies: flutter: sdk: flutter - floor: ^0.19.1 + floor: ^1.0.0-nullsafety dev_dependencies: - floor_generator: ^0.19.1 - build_runner: ^1.11.1 + floor_generator: ^1.0.0-nullsafety + build_runner: ^1.11.5 ``` ### 2. Create an Entity @@ -86,9 +86,9 @@ import 'package:floor/floor.dart'; abstract class PersonDao { @Query('SELECT * FROM Person') Future> findAllPersons(); - + @Query('SELECT * FROM Person WHERE id = :id') - Stream findPersonById(int id); + Stream findPersonById(int id); @insert Future insertPerson(Person person); diff --git a/docs/changelog.md b/docs/changelog.md index 0e10be46..7def4e32 100644 --- a/docs/changelog.md +++ b/docs/changelog.md @@ -1,5 +1,17 @@ # Changelog +## 1.0.0-nullsafety.1 + +### Changes + +* Migrate to stable Dart 2.12 + +## 1.0.0-nullsafety.0 + +### 🚀 Features + +* Make floor null safe + ## 0.19.1 ### Changes diff --git a/docs/daos.md b/docs/daos.md index 14c71b00..992400ed 100644 --- a/docs/daos.md +++ b/docs/daos.md @@ -10,7 +10,7 @@ abstract class PersonDao { Future> findAllPersons(); @Query('SELECT * FROM Person WHERE id = :id') - Stream findPersonById(int id); + Stream findPersonById(int id); @insert Future insertPerson(Person person); @@ -19,18 +19,22 @@ abstract class PersonDao { ## Queries Method signatures turn into query methods by adding the `@Query()` annotation with the query in parenthesis to them. -Be patient about the correctness of your SQL statements. -They are only partly validated while generating the code. +Be mindful about the correctness of your SQL statements as they are only partly validated while generating the code. These queries have to return either a `Future` or a `Stream` of an entity or `void`. Returning `Future` comes in handy whenever you want to delete the full content of a table, for instance. Some query method examples can be seen in the following. +A function returning a single item will return `null` when no matching row is found. +Thereby, the function is required to return a nullable type. +For example `Person?`. +This way, we leave the handling of an absent row up to you and don't attempt to guess intention. + ```dart @Query('SELECT * FROM Person WHERE id = :id') -Future findPersonById(int id); +Future findPersonById(int id); @Query('SELECT * FROM Person WHERE id = :id AND name = :name') -Future findPersonByIdAndName(int id, String name); +Future findPersonByIdAndName(int id, String name); @Query('SELECT * FROM Person') Future> findAllPersons(); // select multiple items @@ -116,12 +120,23 @@ Future deletePersons(List persons); As already mentioned, queries cannot only return values once when called but also continuous streams of query results. The returned streams keep you in sync with the changes happening in the database tables. This feature plays well with the `StreamBuilder` widget which accepts a stream of values and rebuilds itself whenever there is a new emission. - These methods return broadcast streams and thus, can have multiple listeners. + +A function returning a stream of single items will emit `null` when no matching row is found. +Thereby, it's necessary to make the function return a stream of a nullable type. +For example `Stream`. +In case you're not interested in `null`s, you can simply use `Stream.where((value) => value != null)` to get rid of them. + ```dart // definition -@Query('SELECT * FROM Person') -Stream> findAllPersonsAsStream(); +@dao +abstract class PersonDao { + @Query('SELECT * FROM Person WHERE id = :id') + Stream findPersonByIdAsStream(int id); + + @Query('SELECT * FROM Person') + Stream> findAllPersonsAsStream(); +} // usage StreamBuilder>( @@ -138,7 +153,6 @@ StreamBuilder>( - It is now possible to return a `Stream` if the function queries a database view. But it will fire on **any** `@update`, `@insert`, `@delete` events in the whole database, which can get quite taxing on the runtime. Please add it only if you know what you are doing! This is mostly due to the complexity of detecting which entities are involved in a database view. - - Functions returning a stream of single items such as `Stream` do not emit when there is no query result. ## Transactions Whenever you want to perform some operations in a transaction you have to add the `@transaction` annotation to the method. @@ -161,7 +175,7 @@ Bear in mind that only abstract classes allow method signatures without an imple @dao abstract class PersonDao extends AbstractDao { @Query('SELECT * FROM Person WHERE id = :id') - Future findPersonById(int id); + Future findPersonById(int id); } abstract class AbstractDao { diff --git a/docs/entities.md b/docs/entities.md index 4e312d40..596bd08a 100644 --- a/docs/entities.md +++ b/docs/entities.md @@ -16,7 +16,9 @@ The value can be automatically generated by SQLite when `autoGenerate` is enable For more information about primary keys and especially compound primary keys, refer to the [Primary Keys](#primary-keys) section. `@ColumnInfo` enables custom mapping of single table columns. -With the annotation, it's possible to give columns a custom name and define if the column is able to store `null`. +With the annotation it's possible to give columns a custom name. +If you want a table's column to be nullable, mark the entity's field as nullable. +More information can be found in the [Null Safety](null-safety.md) section. !!! attention - Floor automatically uses the **first** constructor defined in the entity class for creating in-memory objects from database rows. @@ -28,7 +30,7 @@ class Person { @PrimaryKey(autoGenerate: true) final int id; - @ColumnInfo(name: 'custom_name', nullable: false) + @ColumnInfo(name: 'custom_name') final String name; Person(this.id, this.name); @@ -105,7 +107,7 @@ class Person { @primaryKey final int id; - @ColumnInfo(name: 'custom_name', nullable: false) + @ColumnInfo(name: 'custom_name') final String name; Person(this.id, this.name); @@ -147,7 +149,7 @@ class BaseObject { @PrimaryKey() final int id; - @ColumnInfo(name: 'create_time', nullable: false) + @ColumnInfo(name: 'create_time') final String createTime; @ColumnInfo(name: 'update_time') diff --git a/docs/getting-started.md b/docs/getting-started.md index 172ad898..f5b3a00d 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -13,11 +13,11 @@ The third dependency is `build_runner` which has to be included as a dev depende dependencies: flutter: sdk: flutter - floor: ^0.18.0 + floor: ^1.0.0-nullsafety dev_dependencies: - floor_generator: ^0.18.0 - build_runner: ^1.10.3 + floor_generator: ^1.0.0-nullsafety + build_runner: ^1.11.5 ``` ## 2. Create an Entity @@ -65,7 +65,7 @@ abstract class PersonDao { Future> findAllPersons(); @Query('SELECT * FROM Person WHERE id = :id') - Stream findPersonById(int id); + Stream findPersonById(int id); @insert Future insertPerson(Person person); diff --git a/docs/migrations.md b/docs/migrations.md index bb15792e..8736cdb4 100644 --- a/docs/migrations.md +++ b/docs/migrations.md @@ -15,7 +15,7 @@ class Person { @PrimaryKey(autoGenerate: true) final int id; - @ColumnInfo(name: 'custom_name', nullable: false) + @ColumnInfo(name: 'custom_name') final String name; final String nickname; diff --git a/docs/null-safety.md b/docs/null-safety.md new file mode 100644 index 00000000..3c3ac815 --- /dev/null +++ b/docs/null-safety.md @@ -0,0 +1,5 @@ +# Null Safety + +Floor infers nullability of database columns directly from entity fields, as mentioned in the [Entities](entities.md) section. +When not explicitly making a field nullable by applying `?` to its type, a column cannot hold `NULL`. +For more information regarding `null`s as query results, see the [Queries](daos.md#queries) and [Streams](daos.md#streams) section. diff --git a/docs/testing.md b/docs/testing.md index 54b046e1..99ed389c 100644 --- a/docs/testing.md +++ b/docs/testing.md @@ -16,8 +16,8 @@ import 'entity/person.dart'; void main() { group('database tests', () { - TestDatabase database; - PersonDao personDao; + late TestDatabase database; + late PersonDao personDao; setUp(() async { database = await $FloorTestDatabase diff --git a/docs/type-converters.md b/docs/type-converters.md index f20aa453..b2ebd3ad 100644 --- a/docs/type-converters.md +++ b/docs/type-converters.md @@ -19,14 +19,12 @@ The implementation and usage of the mentioned `DateTime` to `int` converter is d class DateTimeConverter extends TypeConverter { @override DateTime decode(int databaseValue) { - return databaseValue == null - ? null - : DateTime.fromMillisecondsSinceEpoch(databaseValue); + return DateTime.fromMillisecondsSinceEpoch(databaseValue); } @override int encode(DateTime value) { - return value == null ? null : value.millisecondsSinceEpoch; + return value.millisecondsSinceEpoch; } } ``` diff --git a/example/ios/Flutter/Debug.xcconfig b/example/ios/Flutter/Debug.xcconfig index e8efba11..b2f5fae9 100644 --- a/example/ios/Flutter/Debug.xcconfig +++ b/example/ios/Flutter/Debug.xcconfig @@ -1,2 +1,3 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" #include "Generated.xcconfig" diff --git a/example/ios/Flutter/Release.xcconfig b/example/ios/Flutter/Release.xcconfig index 399e9340..88c29144 100644 --- a/example/ios/Flutter/Release.xcconfig +++ b/example/ios/Flutter/Release.xcconfig @@ -1,2 +1,3 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" #include "Generated.xcconfig" diff --git a/example/ios/Runner.xcodeproj/project.pbxproj b/example/ios/Runner.xcodeproj/project.pbxproj index 19880765..d4fc6969 100644 --- a/example/ios/Runner.xcodeproj/project.pbxproj +++ b/example/ios/Runner.xcodeproj/project.pbxproj @@ -310,7 +310,6 @@ /* Begin XCBuildConfiguration section */ 249021D3217E4FDB00AE95B9 /* Profile */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; @@ -382,7 +381,6 @@ }; 97C147031CF9000F007C117D /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; @@ -436,7 +434,6 @@ }; 97C147041CF9000F007C117D /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; diff --git a/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata index 1d526a16..919434a6 100644 --- a/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata +++ b/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -2,6 +2,6 @@ + location = "self:"> diff --git a/example/lib/database.g.dart b/example/lib/database.g.dart index b7aaac83..be7fa612 100644 --- a/example/lib/database.g.dart +++ b/example/lib/database.g.dart @@ -22,11 +22,11 @@ class $FloorFlutterDatabase { class _$FlutterDatabaseBuilder { _$FlutterDatabaseBuilder(this.name); - final String name; + final String? name; final List _migrations = []; - Callback _callback; + Callback? _callback; /// Adds migrations to the builder. _$FlutterDatabaseBuilder addMigrations(List migrations) { @@ -43,7 +43,7 @@ class _$FlutterDatabaseBuilder { /// Creates the database and initializes it. Future build() async { final path = name != null - ? await sqfliteDatabaseFactory.getDatabasePath(name) + ? await sqfliteDatabaseFactory.getDatabasePath(name!) : ':memory:'; final database = _$FlutterDatabase(); database.database = await database.open( @@ -56,14 +56,14 @@ class _$FlutterDatabaseBuilder { } class _$FlutterDatabase extends FlutterDatabase { - _$FlutterDatabase([StreamController listener]) { + _$FlutterDatabase([StreamController? listener]) { changeListener = listener ?? StreamController.broadcast(); } - TaskDao _taskDaoInstance; + TaskDao? _taskDaoInstance; Future open(String path, List migrations, - [Callback callback]) async { + [Callback? callback]) async { final databaseOptions = sqflite.OpenDatabaseOptions( version: 1, onConfigure: (database) async { @@ -80,7 +80,7 @@ class _$FlutterDatabase extends FlutterDatabase { }, onCreate: (database, version) async { await database.execute( - 'CREATE TABLE IF NOT EXISTS `Task` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `message` TEXT)'); + 'CREATE TABLE IF NOT EXISTS `Task` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `message` TEXT NOT NULL)'); await callback?.onCreate?.call(database, version); }, @@ -101,21 +101,21 @@ class _$TaskDao extends TaskDao { database, 'Task', (Task item) => - {'id': item.id, 'message': item.message}, + {'id': item.id, 'message': item.message}, changeListener), _taskUpdateAdapter = UpdateAdapter( database, 'Task', ['id'], (Task item) => - {'id': item.id, 'message': item.message}, + {'id': item.id, 'message': item.message}, changeListener), _taskDeletionAdapter = DeletionAdapter( database, 'Task', ['id'], (Task item) => - {'id': item.id, 'message': item.message}, + {'id': item.id, 'message': item.message}, changeListener); final sqflite.DatabaseExecutor database; @@ -131,18 +131,18 @@ class _$TaskDao extends TaskDao { final DeletionAdapter _taskDeletionAdapter; @override - Future findTaskById(int id) async { + Future findTaskById(int id) async { return _queryAdapter.query('SELECT * FROM task WHERE id = ?', - arguments: [id], - mapper: (Map row) => - Task(row['id'] as int, row['message'] as String)); + arguments: [id], + mapper: (Map row) => + Task(row['id'] as int?, row['message'] as String)); } @override Future> findAllTasks() async { return _queryAdapter.queryList('SELECT * FROM task', - mapper: (Map row) => - Task(row['id'] as int, row['message'] as String)); + mapper: (Map row) => + Task(row['id'] as int?, row['message'] as String)); } @override @@ -150,8 +150,8 @@ class _$TaskDao extends TaskDao { return _queryAdapter.queryListStream('SELECT * FROM task', queryableName: 'Task', isView: false, - mapper: (Map row) => - Task(row['id'] as int, row['message'] as String)); + mapper: (Map row) => + Task(row['id'] as int?, row['message'] as String)); } @override diff --git a/example/lib/main.dart b/example/lib/main.dart index a789981f..dcb0793c 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -37,9 +37,9 @@ class TasksWidget extends StatelessWidget { final TaskDao dao; const TasksWidget({ - Key key, - @required this.title, - @required this.dao, + Key? key, + required this.title, + required this.dao, }) : super(key: key); @override @@ -62,8 +62,8 @@ class TasksListView extends StatelessWidget { final TaskDao dao; const TasksListView({ - Key key, - @required this.dao, + Key? key, + required this.dao, }) : super(key: key); @override @@ -74,7 +74,7 @@ class TasksListView extends StatelessWidget { builder: (_, snapshot) { if (!snapshot.hasData) return Container(); - final tasks = snapshot.data; + final tasks = snapshot.requireData; return ListView.builder( itemCount: tasks.length, @@ -96,9 +96,9 @@ class TaskListCell extends StatelessWidget { final TaskDao dao; const TaskListCell({ - Key key, - @required this.task, - @required this.dao, + Key? key, + required this.task, + required this.dao, }) : super(key: key); @override @@ -117,8 +117,9 @@ class TaskListCell extends StatelessWidget { onDismissed: (_) async { await dao.deleteTask(task); - Scaffold.of(context).hideCurrentSnackBar(); - Scaffold.of(context).showSnackBar( + final scaffoldMessengerState = ScaffoldMessenger.of(context); + scaffoldMessengerState.hideCurrentSnackBar(); + scaffoldMessengerState.showSnackBar( const SnackBar(content: Text('Removed task')), ); }, @@ -131,9 +132,9 @@ class TasksTextField extends StatelessWidget { final TaskDao dao; TasksTextField({ - Key key, - @required this.dao, - }) : _textEditingController = TextEditingController(), + Key? key, + required this.dao, + }) : _textEditingController = TextEditingController(), super(key: key); @override @@ -159,8 +160,7 @@ class TasksTextField extends StatelessWidget { ), Padding( padding: const EdgeInsets.only(right: 16), - child: OutlineButton( - textColor: Colors.blueGrey, + child: OutlinedButton( child: const Text('Save'), onPressed: () async { await _persistMessage(); diff --git a/example/lib/task.dart b/example/lib/task.dart index 9d1dea6e..b570c8f9 100644 --- a/example/lib/task.dart +++ b/example/lib/task.dart @@ -3,7 +3,7 @@ import 'package:floor/floor.dart'; @entity class Task { @PrimaryKey(autoGenerate: true) - final int id; + final int? id; final String message; diff --git a/example/lib/task_dao.dart b/example/lib/task_dao.dart index 07c91bfc..b1ce8e6d 100644 --- a/example/lib/task_dao.dart +++ b/example/lib/task_dao.dart @@ -4,7 +4,7 @@ import 'package:floor/floor.dart'; @dao abstract class TaskDao { @Query('SELECT * FROM task WHERE id = :id') - Future findTaskById(int id); + Future findTaskById(int id); @Query('SELECT * FROM task') Future> findAllTasks(); diff --git a/example/pubspec.lock b/example/pubspec.lock index bd120b78..58e1651a 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -7,77 +7,77 @@ packages: name: _fe_analyzer_shared url: "https://pub.dartlang.org" source: hosted - version: "14.0.0" + version: "17.0.0" analyzer: dependency: transitive description: name: analyzer url: "https://pub.dartlang.org" source: hosted - version: "0.41.2" + version: "1.1.0" args: dependency: transitive description: name: args url: "https://pub.dartlang.org" source: hosted - version: "1.6.0" + version: "2.0.0" async: dependency: transitive description: name: async url: "https://pub.dartlang.org" source: hosted - version: "2.5.0-nullsafety.1" + version: "2.5.0" boolean_selector: dependency: transitive description: name: boolean_selector url: "https://pub.dartlang.org" source: hosted - version: "2.1.0-nullsafety.1" + version: "2.1.0" build: dependency: transitive description: name: build url: "https://pub.dartlang.org" source: hosted - version: "1.6.2" + version: "1.6.3" build_config: dependency: transitive description: name: build_config url: "https://pub.dartlang.org" source: hosted - version: "0.4.5" + version: "0.4.6" build_daemon: dependency: transitive description: name: build_daemon url: "https://pub.dartlang.org" source: hosted - version: "2.1.4" + version: "2.1.8" build_resolvers: dependency: transitive description: name: build_resolvers url: "https://pub.dartlang.org" source: hosted - version: "1.5.3" + version: "1.5.4" build_runner: dependency: "direct dev" description: name: build_runner url: "https://pub.dartlang.org" source: hosted - version: "1.11.1" + version: "1.11.5" build_runner_core: dependency: transitive description: name: build_runner_core url: "https://pub.dartlang.org" source: hosted - version: "6.1.7" + version: "6.1.10" built_collection: dependency: transitive description: @@ -98,35 +98,35 @@ packages: name: characters url: "https://pub.dartlang.org" source: hosted - version: "1.1.0-nullsafety.3" + version: "1.1.0" charcode: dependency: transitive description: name: charcode url: "https://pub.dartlang.org" source: hosted - version: "1.2.0-nullsafety.1" + version: "1.2.0" checked_yaml: dependency: transitive description: name: checked_yaml url: "https://pub.dartlang.org" source: hosted - version: "1.0.2" + version: "1.0.4" cli_util: dependency: transitive description: name: cli_util url: "https://pub.dartlang.org" source: hosted - version: "0.2.0" + version: "0.3.0" clock: dependency: transitive description: name: clock url: "https://pub.dartlang.org" source: hosted - version: "1.1.0-nullsafety.1" + version: "1.1.0" code_builder: dependency: transitive description: @@ -140,49 +140,49 @@ packages: name: collection url: "https://pub.dartlang.org" source: hosted - version: "1.15.0-nullsafety.3" + version: "1.15.0" convert: dependency: transitive description: name: convert url: "https://pub.dartlang.org" source: hosted - version: "2.1.1" + version: "3.0.0" crypto: dependency: transitive description: name: crypto url: "https://pub.dartlang.org" source: hosted - version: "2.1.5" + version: "3.0.0" dart_style: dependency: transitive description: name: dart_style url: "https://pub.dartlang.org" source: hosted - version: "1.3.12" - dartx: - dependency: transitive - description: - name: dartx - url: "https://pub.dartlang.org" - source: hosted - version: "0.5.0" + version: "1.3.14" fake_async: dependency: transitive description: name: fake_async url: "https://pub.dartlang.org" source: hosted - version: "1.2.0-nullsafety.1" + version: "1.2.0" ffi: dependency: transitive description: name: ffi url: "https://pub.dartlang.org" source: hosted - version: "0.1.3" + version: "1.0.0" + file: + dependency: transitive + description: + name: file + url: "https://pub.dartlang.org" + source: hosted + version: "6.1.0" fixnum: dependency: transitive description: @@ -196,21 +196,21 @@ packages: path: "../floor" relative: true source: path - version: "0.19.0" + version: "1.0.0-nullsafety.1" floor_annotation: dependency: transitive description: path: "../floor_annotation" relative: true source: path - version: "0.12.0" + version: "1.0.0-nullsafety.1" floor_generator: dependency: "direct dev" description: path: "../floor_generator" relative: true source: path - version: "0.19.0" + version: "1.0.0-nullsafety.1" flutter: dependency: "direct main" description: flutter @@ -227,7 +227,7 @@ packages: name: glob url: "https://pub.dartlang.org" source: hosted - version: "1.2.0" + version: "2.0.0" graphs: dependency: transitive description: @@ -248,28 +248,28 @@ packages: name: http_parser url: "https://pub.dartlang.org" source: hosted - version: "3.1.4" + version: "4.0.0" io: dependency: transitive description: name: io url: "https://pub.dartlang.org" source: hosted - version: "0.3.4" + version: "0.3.5" js: dependency: transitive description: name: js url: "https://pub.dartlang.org" source: hosted - version: "0.6.2" + version: "0.6.3" json_annotation: dependency: transitive description: name: json_annotation url: "https://pub.dartlang.org" source: hosted - version: "3.1.0" + version: "3.1.1" logging: dependency: transitive description: @@ -283,98 +283,84 @@ packages: name: matcher url: "https://pub.dartlang.org" source: hosted - version: "0.12.10-nullsafety.1" + version: "0.12.10" meta: dependency: transitive description: name: meta url: "https://pub.dartlang.org" source: hosted - version: "1.3.0-nullsafety.3" + version: "1.3.0" mime: dependency: transitive description: name: mime url: "https://pub.dartlang.org" source: hosted - version: "0.9.7" - node_interop: - dependency: transitive - description: - name: node_interop - url: "https://pub.dartlang.org" - source: hosted - version: "1.1.1" - node_io: - dependency: transitive - description: - name: node_io - url: "https://pub.dartlang.org" - source: hosted - version: "1.1.1" + version: "1.0.0" package_config: dependency: transitive description: name: package_config url: "https://pub.dartlang.org" source: hosted - version: "1.9.3" + version: "2.0.0" path: dependency: transitive description: name: path url: "https://pub.dartlang.org" source: hosted - version: "1.8.0-nullsafety.1" + version: "1.8.0" pedantic: dependency: transitive description: name: pedantic url: "https://pub.dartlang.org" source: hosted - version: "1.9.2" + version: "1.11.0" pool: dependency: transitive description: name: pool url: "https://pub.dartlang.org" source: hosted - version: "1.4.0" + version: "1.5.0" pub_semver: dependency: transitive description: name: pub_semver url: "https://pub.dartlang.org" source: hosted - version: "1.4.4" + version: "2.0.0" pubspec_parse: dependency: transitive description: name: pubspec_parse url: "https://pub.dartlang.org" source: hosted - version: "0.1.5" + version: "0.1.8" quiver: dependency: transitive description: name: quiver url: "https://pub.dartlang.org" source: hosted - version: "2.1.3" + version: "2.1.5" shelf: dependency: transitive description: name: shelf url: "https://pub.dartlang.org" source: hosted - version: "0.7.9" + version: "1.0.0" shelf_web_socket: dependency: transitive description: name: shelf_web_socket url: "https://pub.dartlang.org" source: hosted - version: "0.2.3" + version: "0.2.4+1" sky_engine: dependency: transitive description: flutter @@ -393,133 +379,126 @@ packages: name: source_span url: "https://pub.dartlang.org" source: hosted - version: "1.8.0-nullsafety.2" + version: "1.8.0" sqflite: dependency: transitive description: name: sqflite url: "https://pub.dartlang.org" source: hosted - version: "1.3.2+3" + version: "2.0.0+2" sqflite_common: dependency: transitive description: name: sqflite_common url: "https://pub.dartlang.org" source: hosted - version: "1.0.3+1" + version: "2.0.0+1" sqflite_common_ffi: dependency: transitive description: name: sqflite_common_ffi url: "https://pub.dartlang.org" source: hosted - version: "1.1.1" + version: "2.0.0" sqlite3: dependency: transitive description: name: sqlite3 url: "https://pub.dartlang.org" source: hosted - version: "0.1.6" + version: "1.0.0" stack_trace: dependency: transitive description: name: stack_trace url: "https://pub.dartlang.org" source: hosted - version: "1.10.0-nullsafety.1" + version: "1.10.0" stream_channel: dependency: transitive description: name: stream_channel url: "https://pub.dartlang.org" source: hosted - version: "2.1.0-nullsafety.1" + version: "2.1.0" stream_transform: dependency: transitive description: name: stream_transform url: "https://pub.dartlang.org" source: hosted - version: "1.2.0" + version: "2.0.0" string_scanner: dependency: transitive description: name: string_scanner url: "https://pub.dartlang.org" source: hosted - version: "1.1.0-nullsafety.1" + version: "1.1.0" synchronized: dependency: transitive description: name: synchronized url: "https://pub.dartlang.org" source: hosted - version: "2.2.0+2" + version: "3.0.0" term_glyph: dependency: transitive description: name: term_glyph url: "https://pub.dartlang.org" source: hosted - version: "1.2.0-nullsafety.1" + version: "1.2.0" test_api: dependency: transitive description: name: test_api url: "https://pub.dartlang.org" source: hosted - version: "0.2.19-nullsafety.2" - time: - dependency: transitive - description: - name: time - url: "https://pub.dartlang.org" - source: hosted - version: "1.3.0" + version: "0.2.19" timing: dependency: transitive description: name: timing url: "https://pub.dartlang.org" source: hosted - version: "0.1.1+2" + version: "0.1.1+3" typed_data: dependency: transitive description: name: typed_data url: "https://pub.dartlang.org" source: hosted - version: "1.3.0-nullsafety.3" + version: "1.3.0" vector_math: dependency: transitive description: name: vector_math url: "https://pub.dartlang.org" source: hosted - version: "2.1.0-nullsafety.3" + version: "2.1.0" watcher: dependency: transitive description: name: watcher url: "https://pub.dartlang.org" source: hosted - version: "0.9.7+15" + version: "1.0.0" web_socket_channel: dependency: transitive description: name: web_socket_channel url: "https://pub.dartlang.org" source: hosted - version: "1.1.0" + version: "1.2.0" yaml: dependency: transitive description: name: yaml url: "https://pub.dartlang.org" source: hosted - version: "2.2.1" + version: "3.1.0" sdks: - dart: ">=2.10.2 <2.11.0" - flutter: ">=1.22.2 <2.0.0" + dart: ">=2.12.0 <3.0.0" + flutter: ">=1.24.0-10" diff --git a/example/pubspec.yaml b/example/pubspec.yaml index 047cc85c..abecf8d2 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -2,22 +2,23 @@ name: example description: > The typesafe, reactive and lightweight SQLite abstraction for your Flutter applications. This is an example on how to use the library. -version: 0.1.0 +version: 1.0.0-nullsafety.1 homepage: https://github.com/vitusortner/floor author: vitusortner +publish_to: none environment: - sdk: '>=2.6.0 <3.0.0' + sdk: '>=2.12.0 <3.0.0' dependencies: - flutter: - sdk: flutter floor: path: ../floor/ + flutter: + sdk: flutter dev_dependencies: - flutter_test: - sdk: flutter + build_runner: ^1.11.5 floor_generator: path: ../floor_generator/ - build_runner: ^1.10.3 + flutter_test: + sdk: flutter diff --git a/example/test/main_test.dart b/example/test/main_test.dart index 8e85f9bc..836ea537 100644 --- a/example/test/main_test.dart +++ b/example/test/main_test.dart @@ -5,8 +5,8 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; void main() { - FlutterDatabase database; - TaskDao taskDao; + late FlutterDatabase database; + late TaskDao taskDao; setUp(() async { database = await $FloorFlutterDatabase.inMemoryDatabaseBuilder().build(); @@ -21,7 +21,7 @@ void main() { await tester.runAsync(() async { await tester.pumpWidget(FloorApp(taskDao)); final textFieldFinder = find.byType(TextField); - final raisedButtonFinder = find.byType(RaisedButton); + final raisedButtonFinder = find.byType(OutlinedButton); await tester.enterText(textFieldFinder, 'Hello world!'); await tester.tap(raisedButtonFinder); @@ -35,14 +35,14 @@ void main() { await tester.runAsync(() async { await tester.pumpWidget(FloorApp(taskDao)); final textFieldFinder = find.byType(TextField); - final raisedButtonFinder = find.byType(RaisedButton); + final raisedButtonFinder = find.byType(OutlinedButton); await tester.enterText(textFieldFinder, 'Hello world!'); await tester.tap(raisedButtonFinder); await Future.delayed(const Duration(milliseconds: 100)); await tester.pump(); - final text = tester.widget(textFieldFinder).controller.text; + final text = tester.widget(textFieldFinder).controller!.text; expect(text, isEmpty); }); }); @@ -51,7 +51,7 @@ void main() { await tester.runAsync(() async { await tester.pumpWidget(FloorApp(taskDao)); final textFieldFinder = find.byType(TextField); - final raisedButtonFinder = find.byType(RaisedButton); + final raisedButtonFinder = find.byType(OutlinedButton); final listViewFinder = find.byType(ListView); final textFinder = find.byType(Text); diff --git a/floor/CHANGELOG.md b/floor/CHANGELOG.md index 54cc4604..4a542db4 100644 --- a/floor/CHANGELOG.md +++ b/floor/CHANGELOG.md @@ -1,5 +1,17 @@ # Changelog +# 1.0.0-nullsafety.1 + +### Changes + +* Migrate to stable Dart 2.12 + +# 1.0.0-nullsafety.0 + +### 🚀 Features + +* Make floor null safe + # 0.19.1 ### Changes @@ -84,7 +96,8 @@ ### ⚠️ Breaking Changes -**You need to migrate the explicit usages of `OnConflictStrategy` and `ForeignKeyAction` from snake case to camel case.** +**You need to migrate the explicit usages of `OnConflictStrategy` and `ForeignKeyAction` from snake case to camel +case.** * Apply camel case to constants diff --git a/floor/README.md b/floor/README.md index 75385299..454555cf 100644 --- a/floor/README.md +++ b/floor/README.md @@ -36,11 +36,11 @@ The third dependency is `build_runner` which has to be included as a dev depende dependencies: flutter: sdk: flutter - floor: ^0.19.1 + floor: ^1.0.0-nullsafety dev_dependencies: - floor_generator: ^0.19.1 - build_runner: ^1.11.1 + floor_generator: ^1.0.0-nullsafety + build_runner: ^1.11.5 ``` ### 2. Create an Entity @@ -60,9 +60,9 @@ import 'package:floor/floor.dart'; class Person { @primaryKey final int id; - + final String name; - + Person(this.id, this.name); } ``` @@ -86,10 +86,10 @@ import 'package:floor/floor.dart'; abstract class PersonDao { @Query('SELECT * FROM Person') Future> findAllPersons(); - + @Query('SELECT * FROM Person WHERE id = :id') - Stream findPersonById(int id); - + Stream findPersonById(int id); + @insert Future insertPerson(Person person); } diff --git a/floor/lib/src/adapter/deletion_adapter.dart b/floor/lib/src/adapter/deletion_adapter.dart index 66cfc914..1396fcaf 100644 --- a/floor/lib/src/adapter/deletion_adapter.dart +++ b/floor/lib/src/adapter/deletion_adapter.dart @@ -7,21 +7,17 @@ class DeletionAdapter { final DatabaseExecutor _database; final String _entityName; final List _primaryKeyColumnNames; - final Map Function(T) _valueMapper; - final StreamController _changeListener; + final Map Function(T) _valueMapper; + final StreamController? _changeListener; DeletionAdapter( final DatabaseExecutor database, final String entityName, final List primaryKeyColumnName, - final Map Function(T) valueMapper, [ - final StreamController changeListener, - ]) : assert(database != null), - assert(entityName != null), - assert(entityName.isNotEmpty), - assert(primaryKeyColumnName != null), + final Map Function(T) valueMapper, [ + final StreamController? changeListener, + ]) : assert(entityName.isNotEmpty), assert(primaryKeyColumnName.isNotEmpty), - assert(valueMapper != null), _database = database, _entityName = entityName, _primaryKeyColumnNames = primaryKeyColumnName, @@ -55,9 +51,7 @@ class DeletionAdapter { _valueMapper(item), ), ); - if (_changeListener != null && result != 0) { - _changeListener.add(_entityName); - } + if (result != 0) _changeListener?.add(_entityName); return result; } @@ -74,9 +68,7 @@ class DeletionAdapter { ); } final result = (await batch.commit(noResult: false)).cast(); - if (_changeListener != null && result.isNotEmpty) { - _changeListener.add(_entityName); - } + if (result.isNotEmpty) _changeListener?.add(_entityName); return result.isNotEmpty ? result.reduce((sum, element) => sum + element) : 0; diff --git a/floor/lib/src/adapter/insertion_adapter.dart b/floor/lib/src/adapter/insertion_adapter.dart index afdc52aa..2f584d06 100644 --- a/floor/lib/src/adapter/insertion_adapter.dart +++ b/floor/lib/src/adapter/insertion_adapter.dart @@ -7,18 +7,15 @@ import 'package:sqflite/sqlite_api.dart'; class InsertionAdapter { final DatabaseExecutor _database; final String _entityName; - final Map Function(T) _valueMapper; - final StreamController _changeListener; + final Map Function(T) _valueMapper; + final StreamController? _changeListener; InsertionAdapter( final DatabaseExecutor database, final String entityName, - final Map Function(T) valueMapper, [ - final StreamController changeListener, - ]) : assert(database != null), - assert(entityName != null), - assert(entityName.isNotEmpty), - assert(valueMapper != null), + final Map Function(T) valueMapper, [ + final StreamController? changeListener, + ]) : assert(entityName.isNotEmpty), _database = database, _entityName = entityName, _valueMapper = valueMapper, @@ -45,9 +42,7 @@ class InsertionAdapter { ); } await batch.commit(noResult: true); - if (_changeListener != null) { - _changeListener.add(_entityName); - } + _changeListener?.add(_entityName); } Future insertAndReturnId( @@ -71,9 +66,7 @@ class InsertionAdapter { ); } final result = (await batch.commit(noResult: false)).cast(); - if (_changeListener != null && result.isNotEmpty) { - _changeListener.add(_entityName); - } + if (result.isNotEmpty) _changeListener?.add(_entityName); return result; } @@ -86,9 +79,7 @@ class InsertionAdapter { _valueMapper(item), conflictAlgorithm: onConflictStrategy.asSqfliteConflictAlgorithm(), ); - if (_changeListener != null && result != null) { - _changeListener.add(_entityName); - } + if (result != 0) _changeListener?.add(_entityName); return result; } } diff --git a/floor/lib/src/adapter/query_adapter.dart b/floor/lib/src/adapter/query_adapter.dart index bf38ecfa..d2bbdb11 100644 --- a/floor/lib/src/adapter/query_adapter.dart +++ b/floor/lib/src/adapter/query_adapter.dart @@ -1,25 +1,23 @@ import 'dart:async'; -import 'package:meta/meta.dart'; import 'package:sqflite/sqflite.dart'; /// This class knows how to execute database queries. class QueryAdapter { final DatabaseExecutor _database; - final StreamController _changeListener; + final StreamController? _changeListener; QueryAdapter( final DatabaseExecutor database, [ - final StreamController changeListener, - ]) : assert(database != null), - _database = database, + final StreamController? changeListener, + ]) : _database = database, _changeListener = changeListener; /// Executes a SQLite query that may return a single value. - Future query( + Future query( final String sql, { - final List arguments, - @required final T Function(Map) mapper, + final List? arguments, + required final T Function(Map) mapper, }) async { final rows = await _database.rawQuery(sql, arguments); @@ -35,8 +33,8 @@ class QueryAdapter { /// Executes a SQLite query that may return multiple values. Future> queryList( final String sql, { - final List arguments, - @required final T Function(Map) mapper, + final List? arguments, + required final T Function(Map) mapper, }) async { final rows = await _database.rawQuery(sql, arguments); return rows.map((row) => mapper(row)).toList(); @@ -44,7 +42,7 @@ class QueryAdapter { Future queryNoReturn( final String sql, { - final List arguments, + final List? arguments, }) async { // TODO #94 differentiate between different query kinds (select, update, delete, insert) // this enables to notify the observers @@ -52,28 +50,29 @@ class QueryAdapter { await _database.rawQuery(sql, arguments); } - /// Executes a SQLite query that returns a stream of single query results. - Stream queryStream( + /// Executes a SQLite query that returns a stream of single query results + /// or `null`. + Stream queryStream( final String sql, { - final List arguments, - @required final String queryableName, - @required final bool isView, - @required final T Function(Map) mapper, + final List? arguments, + required final String queryableName, + required final bool isView, + required final T Function(Map) mapper, }) { - assert(_changeListener != null); - - final controller = StreamController.broadcast(); + // ignore: close_sinks + final changeListener = ArgumentError.checkNotNull(_changeListener); + final controller = StreamController.broadcast(); Future executeQueryAndNotifyController() async { final result = await query(sql, arguments: arguments, mapper: mapper); - if (result != null) controller.add(result); + controller.add(result); } controller.onListen = () async => executeQueryAndNotifyController(); // listen on all updates if the stream is on a view, only listen to the // name of the table if the stream is on a entity. - final subscription = _changeListener.stream + final subscription = changeListener.stream .where((updatedTable) => updatedTable == queryableName || isView) .listen( (_) async => executeQueryAndNotifyController(), @@ -88,13 +87,13 @@ class QueryAdapter { /// Executes a SQLite query that returns a stream of multiple query results. Stream> queryListStream( final String sql, { - final List arguments, - @required final String queryableName, - @required final bool isView, - @required final T Function(Map) mapper, + final List? arguments, + required final String queryableName, + required final bool isView, + required final T Function(Map) mapper, }) { - assert(_changeListener != null); - + // ignore: close_sinks + final changeListener = ArgumentError.checkNotNull(_changeListener); final controller = StreamController>.broadcast(); Future executeQueryAndNotifyController() async { @@ -105,7 +104,7 @@ class QueryAdapter { controller.onListen = () async => executeQueryAndNotifyController(); // Views listen on all events, Entities only on events that changed the same entity. - final subscription = _changeListener.stream + final subscription = changeListener.stream .where((updatedTable) => isView || updatedTable == queryableName) .listen( (_) async => executeQueryAndNotifyController(), diff --git a/floor/lib/src/adapter/update_adapter.dart b/floor/lib/src/adapter/update_adapter.dart index 7dfa8439..d83bd022 100644 --- a/floor/lib/src/adapter/update_adapter.dart +++ b/floor/lib/src/adapter/update_adapter.dart @@ -9,21 +9,17 @@ class UpdateAdapter { final DatabaseExecutor _database; final String _entityName; final List _primaryKeyColumnName; - final Map Function(T) _valueMapper; - final StreamController _changeListener; + final Map Function(T) _valueMapper; + final StreamController? _changeListener; UpdateAdapter( final DatabaseExecutor database, final String entityName, final List primaryKeyColumnName, - final Map Function(T) valueMapper, [ - final StreamController changeListener, - ]) : assert(database != null), - assert(entityName != null), - assert(entityName.isNotEmpty), - assert(primaryKeyColumnName != null), + final Map Function(T) valueMapper, [ + final StreamController? changeListener, + ]) : assert(entityName.isNotEmpty), assert(primaryKeyColumnName.isNotEmpty), - assert(valueMapper != null), _database = database, _entityName = entityName, _valueMapper = valueMapper, @@ -76,9 +72,7 @@ class UpdateAdapter { ), conflictAlgorithm: onConflictStrategy.asSqfliteConflictAlgorithm(), ); - if (_changeListener != null && result != 0) { - _changeListener.add(_entityName); - } + if (result != 0) _changeListener?.add(_entityName); return result; } @@ -102,9 +96,7 @@ class UpdateAdapter { ); } final result = (await batch.commit(noResult: false)).cast(); - if (_changeListener != null && result.isNotEmpty) { - _changeListener.add(_entityName); - } + if (result.isNotEmpty) _changeListener?.add(_entityName); return result.isNotEmpty ? result.reduce((sum, element) => sum + element) : 0; diff --git a/floor/lib/src/callback.dart b/floor/lib/src/callback.dart index 90739f8e..b481e32b 100644 --- a/floor/lib/src/callback.dart +++ b/floor/lib/src/callback.dart @@ -8,17 +8,17 @@ class Callback { final FutureOr Function( Database database, int version, - ) onCreate; + )? onCreate; /// Fired when the [database] has successfully been opened. - final FutureOr Function(Database database) onOpen; + final FutureOr Function(Database database)? onOpen; /// Fired when the [database] has finished upgrading from [startVersion] to [endVersion]. final FutureOr Function( Database database, int startVersion, int endVersion, - ) onUpgrade; + )? onUpgrade; /// Constructor. const Callback({this.onCreate, this.onOpen, this.onUpgrade}); diff --git a/floor/lib/src/database.dart b/floor/lib/src/database.dart index 357f31bf..d8898ac4 100644 --- a/floor/lib/src/database.dart +++ b/floor/lib/src/database.dart @@ -8,19 +8,18 @@ abstract class FloorDatabase { /// [StreamController] that is responsible for notifying listeners about changes /// in specific tables. It acts as an event bus. @protected - StreamController changeListener; + late final StreamController changeListener; /// Use this whenever you need direct access to the sqflite database. - sqflite.DatabaseExecutor database; + late final sqflite.DatabaseExecutor database; /// Closes the database. Future close() async { - await changeListener?.close(); + await changeListener.close(); - final immutableDatabase = database; - if (immutableDatabase is sqflite.Database && - (immutableDatabase?.isOpen ?? false)) { - await immutableDatabase.close(); + final database = this.database; + if (database is sqflite.Database && database.isOpen) { + await database.close(); } } } diff --git a/floor/lib/src/extension/on_conflict_strategy_extensions.dart b/floor/lib/src/extension/on_conflict_strategy_extensions.dart index 2fe1bd21..aff8afa0 100644 --- a/floor/lib/src/extension/on_conflict_strategy_extensions.dart +++ b/floor/lib/src/extension/on_conflict_strategy_extensions.dart @@ -13,7 +13,6 @@ extension OnConflictStrategyExtensions on OnConflictStrategy { case OnConflictStrategy.ignore: return ConflictAlgorithm.ignore; case OnConflictStrategy.abort: - default: return ConflictAlgorithm.abort; } } diff --git a/floor/lib/src/migration.dart b/floor/lib/src/migration.dart index 92ac914f..25d06da5 100644 --- a/floor/lib/src/migration.dart +++ b/floor/lib/src/migration.dart @@ -18,10 +18,7 @@ class Migration { /// [migrate] will be called by the database and performs the actual /// migration. Migration(this.startVersion, this.endVersion, this.migrate) - : assert(startVersion != null), - assert(endVersion != null), - assert(migrate != null), - assert(startVersion > 0), + : assert(startVersion > 0), assert(startVersion < endVersion); @override diff --git a/floor/lib/src/sqflite_database_factory.dart b/floor/lib/src/sqflite_database_factory.dart index aa6193e1..e59c6043 100644 --- a/floor/lib/src/sqflite_database_factory.dart +++ b/floor/lib/src/sqflite_database_factory.dart @@ -5,7 +5,8 @@ import 'package:sqflite/sqflite.dart'; import 'package:sqflite/sqlite_api.dart'; import 'package:sqflite_common_ffi/sqflite_ffi.dart'; -final sqfliteDatabaseFactory = () { +// infers factory as nullable without explicit type definition +final DatabaseFactory sqfliteDatabaseFactory = () { if (Platform.isAndroid || Platform.isIOS) { return databaseFactory; } else if (Platform.isLinux || Platform.isMacOS || Platform.isWindows) { @@ -20,6 +21,7 @@ final sqfliteDatabaseFactory = () { extension DatabaseFactoryExtension on DatabaseFactory { Future getDatabasePath(final String name) async { - return join(await this.getDatabasesPath(), name); + final databasesPath = await this.getDatabasesPath(); + return join(databasesPath, name); } } diff --git a/floor/lib/src/util/primary_key_helper.dart b/floor/lib/src/util/primary_key_helper.dart index b22a8b6f..04c2d7fa 100644 --- a/floor/lib/src/util/primary_key_helper.dart +++ b/floor/lib/src/util/primary_key_helper.dart @@ -6,10 +6,19 @@ class PrimaryKeyHelper { } /// Obtains the primary key values - static List getPrimaryKeyValues( + static List getPrimaryKeyValues( final List primaryKeys, - final Map values, + final Map values, ) { - return primaryKeys.map((key) => values[key]).toList(); + return primaryKeys.mapNotNull((key) => values[key]).toList(); + } +} + +extension on Iterable { + Iterable mapNotNull(R? Function(T element) transform) sync* { + for (final element in this) { + final transformed = transform(element); + if (transformed != null) yield transformed; + } } } diff --git a/floor/pubspec.lock b/floor/pubspec.lock index 676d0dfd..6ab74f40 100644 --- a/floor/pubspec.lock +++ b/floor/pubspec.lock @@ -7,126 +7,126 @@ packages: name: _fe_analyzer_shared url: "https://pub.dartlang.org" source: hosted - version: "14.0.0" + version: "17.0.0" analyzer: dependency: transitive description: name: analyzer url: "https://pub.dartlang.org" source: hosted - version: "0.41.2" + version: "1.1.0" args: dependency: transitive description: name: args url: "https://pub.dartlang.org" source: hosted - version: "1.6.0" + version: "2.0.0" async: dependency: transitive description: name: async url: "https://pub.dartlang.org" source: hosted - version: "2.5.0-nullsafety.1" + version: "2.5.0" boolean_selector: dependency: transitive description: name: boolean_selector url: "https://pub.dartlang.org" source: hosted - version: "2.1.0-nullsafety.1" + version: "2.1.0" build: dependency: transitive description: name: build url: "https://pub.dartlang.org" source: hosted - version: "1.6.2" + version: "1.6.3" build_config: dependency: transitive description: name: build_config url: "https://pub.dartlang.org" source: hosted - version: "0.4.5" + version: "0.4.6" build_daemon: dependency: transitive description: name: build_daemon url: "https://pub.dartlang.org" source: hosted - version: "2.1.4" + version: "2.1.8" build_resolvers: dependency: transitive description: name: build_resolvers url: "https://pub.dartlang.org" source: hosted - version: "1.5.3" + version: "1.5.4" build_runner: dependency: "direct dev" description: name: build_runner url: "https://pub.dartlang.org" source: hosted - version: "1.11.1" + version: "1.11.5" build_runner_core: dependency: transitive description: name: build_runner_core url: "https://pub.dartlang.org" source: hosted - version: "6.1.7" + version: "6.1.10" built_collection: dependency: transitive description: name: built_collection url: "https://pub.dartlang.org" source: hosted - version: "4.3.2" + version: "5.0.0" built_value: dependency: transitive description: name: built_value url: "https://pub.dartlang.org" source: hosted - version: "7.1.0" + version: "8.0.0" characters: dependency: transitive description: name: characters url: "https://pub.dartlang.org" source: hosted - version: "1.1.0-nullsafety.3" + version: "1.1.0" charcode: dependency: transitive description: name: charcode url: "https://pub.dartlang.org" source: hosted - version: "1.2.0-nullsafety.1" + version: "1.2.0" checked_yaml: dependency: transitive description: name: checked_yaml url: "https://pub.dartlang.org" source: hosted - version: "1.0.2" + version: "1.0.4" cli_util: dependency: transitive description: name: cli_util url: "https://pub.dartlang.org" source: hosted - version: "0.2.0" + version: "0.3.0" clock: dependency: transitive description: name: clock url: "https://pub.dartlang.org" source: hosted - version: "1.1.0-nullsafety.1" + version: "1.1.0" code_builder: dependency: transitive description: @@ -140,70 +140,70 @@ packages: name: collection url: "https://pub.dartlang.org" source: hosted - version: "1.15.0-nullsafety.3" + version: "1.15.0" convert: dependency: transitive description: name: convert url: "https://pub.dartlang.org" source: hosted - version: "2.1.1" + version: "3.0.0" crypto: dependency: transitive description: name: crypto url: "https://pub.dartlang.org" source: hosted - version: "2.1.5" + version: "3.0.0" dart_style: dependency: transitive description: name: dart_style url: "https://pub.dartlang.org" source: hosted - version: "1.3.12" - dartx: - dependency: transitive - description: - name: dartx - url: "https://pub.dartlang.org" - source: hosted - version: "0.5.0" + version: "1.3.14" fake_async: dependency: transitive description: name: fake_async url: "https://pub.dartlang.org" source: hosted - version: "1.2.0-nullsafety.1" + version: "1.2.0" ffi: dependency: transitive description: name: ffi url: "https://pub.dartlang.org" source: hosted - version: "0.1.3" + version: "1.0.0" + file: + dependency: transitive + description: + name: file + url: "https://pub.dartlang.org" + source: hosted + version: "6.1.0" fixnum: dependency: transitive description: name: fixnum url: "https://pub.dartlang.org" source: hosted - version: "0.10.11" + version: "1.0.0" floor_annotation: dependency: "direct main" description: path: "../floor_annotation" relative: true source: path - version: "0.12.0" + version: "1.0.0-nullsafety.1" floor_generator: dependency: "direct dev" description: path: "../floor_generator" relative: true source: path - version: "0.19.0" + version: "1.0.0-nullsafety.1" flutter: dependency: "direct main" description: flutter @@ -220,7 +220,7 @@ packages: name: glob url: "https://pub.dartlang.org" source: hosted - version: "1.2.0" + version: "2.0.0" graphs: dependency: transitive description: @@ -241,140 +241,119 @@ packages: name: http_parser url: "https://pub.dartlang.org" source: hosted - version: "3.1.4" + version: "4.0.0" io: dependency: transitive description: name: io url: "https://pub.dartlang.org" source: hosted - version: "0.3.4" + version: "0.3.5" js: dependency: transitive description: name: js url: "https://pub.dartlang.org" source: hosted - version: "0.6.2" + version: "0.6.3" json_annotation: dependency: transitive description: name: json_annotation url: "https://pub.dartlang.org" source: hosted - version: "3.1.0" + version: "4.0.0" logging: dependency: transitive description: name: logging url: "https://pub.dartlang.org" source: hosted - version: "0.11.4" + version: "1.0.0" matcher: dependency: "direct dev" description: name: matcher url: "https://pub.dartlang.org" source: hosted - version: "0.12.10-nullsafety.1" + version: "0.12.10" meta: dependency: "direct main" description: name: meta url: "https://pub.dartlang.org" source: hosted - version: "1.3.0-nullsafety.3" + version: "1.3.0" mime: dependency: transitive description: name: mime url: "https://pub.dartlang.org" source: hosted - version: "0.9.7" + version: "1.0.0" mockito: dependency: "direct dev" description: name: mockito url: "https://pub.dartlang.org" source: hosted - version: "4.1.4" - node_interop: - dependency: transitive - description: - name: node_interop - url: "https://pub.dartlang.org" - source: hosted - version: "1.1.1" - node_io: - dependency: transitive - description: - name: node_io - url: "https://pub.dartlang.org" - source: hosted - version: "1.1.1" + version: "5.0.0" package_config: dependency: transitive description: name: package_config url: "https://pub.dartlang.org" source: hosted - version: "1.9.3" + version: "2.0.0" path: dependency: "direct main" description: name: path url: "https://pub.dartlang.org" source: hosted - version: "1.8.0-nullsafety.1" + version: "1.8.0" pedantic: dependency: transitive description: name: pedantic url: "https://pub.dartlang.org" source: hosted - version: "1.9.2" + version: "1.11.0" pool: dependency: transitive description: name: pool url: "https://pub.dartlang.org" source: hosted - version: "1.4.0" + version: "1.5.0" pub_semver: dependency: transitive description: name: pub_semver url: "https://pub.dartlang.org" source: hosted - version: "1.4.4" + version: "2.0.0" pubspec_parse: dependency: transitive description: name: pubspec_parse url: "https://pub.dartlang.org" source: hosted - version: "0.1.5" - quiver: - dependency: transitive - description: - name: quiver - url: "https://pub.dartlang.org" - source: hosted - version: "2.1.3" + version: "0.1.8" shelf: dependency: transitive description: name: shelf url: "https://pub.dartlang.org" source: hosted - version: "0.7.9" + version: "1.0.0" shelf_web_socket: dependency: transitive description: name: shelf_web_socket url: "https://pub.dartlang.org" source: hosted - version: "0.2.3" + version: "0.2.4+1" sky_engine: dependency: transitive description: flutter @@ -393,133 +372,126 @@ packages: name: source_span url: "https://pub.dartlang.org" source: hosted - version: "1.8.0-nullsafety.2" + version: "1.8.0" sqflite: dependency: "direct main" description: name: sqflite url: "https://pub.dartlang.org" source: hosted - version: "1.3.2+3" + version: "2.0.0+2" sqflite_common: dependency: transitive description: name: sqflite_common url: "https://pub.dartlang.org" source: hosted - version: "1.0.3+1" + version: "2.0.0+1" sqflite_common_ffi: dependency: "direct main" description: name: sqflite_common_ffi url: "https://pub.dartlang.org" source: hosted - version: "1.1.1" + version: "2.0.0" sqlite3: dependency: transitive description: name: sqlite3 url: "https://pub.dartlang.org" source: hosted - version: "0.1.6" + version: "1.0.0" stack_trace: dependency: transitive description: name: stack_trace url: "https://pub.dartlang.org" source: hosted - version: "1.10.0-nullsafety.1" + version: "1.10.0" stream_channel: dependency: transitive description: name: stream_channel url: "https://pub.dartlang.org" source: hosted - version: "2.1.0-nullsafety.1" + version: "2.1.0" stream_transform: dependency: transitive description: name: stream_transform url: "https://pub.dartlang.org" source: hosted - version: "1.2.0" + version: "2.0.0" string_scanner: dependency: transitive description: name: string_scanner url: "https://pub.dartlang.org" source: hosted - version: "1.1.0-nullsafety.1" + version: "1.1.0" synchronized: dependency: transitive description: name: synchronized url: "https://pub.dartlang.org" source: hosted - version: "2.2.0+2" + version: "3.0.0" term_glyph: dependency: transitive description: name: term_glyph url: "https://pub.dartlang.org" source: hosted - version: "1.2.0-nullsafety.1" + version: "1.2.0" test_api: dependency: transitive description: name: test_api url: "https://pub.dartlang.org" source: hosted - version: "0.2.19-nullsafety.2" - time: - dependency: transitive - description: - name: time - url: "https://pub.dartlang.org" - source: hosted - version: "1.3.0" + version: "0.2.19" timing: dependency: transitive description: name: timing url: "https://pub.dartlang.org" source: hosted - version: "0.1.1+2" + version: "0.1.1+3" typed_data: dependency: transitive description: name: typed_data url: "https://pub.dartlang.org" source: hosted - version: "1.3.0-nullsafety.3" + version: "1.3.0" vector_math: dependency: transitive description: name: vector_math url: "https://pub.dartlang.org" source: hosted - version: "2.1.0-nullsafety.3" + version: "2.1.0" watcher: dependency: transitive description: name: watcher url: "https://pub.dartlang.org" source: hosted - version: "0.9.7+15" + version: "1.0.0" web_socket_channel: dependency: transitive description: name: web_socket_channel url: "https://pub.dartlang.org" source: hosted - version: "1.1.0" + version: "1.2.0" yaml: dependency: transitive description: name: yaml url: "https://pub.dartlang.org" source: hosted - version: "2.2.1" + version: "3.1.0" sdks: - dart: ">=2.10.2 <2.11.0" - flutter: ">=1.22.2 <2.0.0" + dart: ">=2.12.0 <3.0.0" + flutter: ">=1.24.0-10" diff --git a/floor/pubspec.yaml b/floor/pubspec.yaml index 957e0d25..9334ff70 100644 --- a/floor/pubspec.yaml +++ b/floor/pubspec.yaml @@ -2,29 +2,30 @@ name: floor description: > The typesafe, reactive, and lightweight SQLite abstraction for your Flutter applications. This library is the runtime dependency. -version: 0.19.1 +version: 1.0.0-nullsafety.1 homepage: https://github.com/vitusortner/floor author: Vitus Ortner +publish_to: none environment: - sdk: '>=2.6.0 <3.0.0' + sdk: '>=2.12.0 <3.0.0' dependencies: floor_annotation: path: ../floor_annotation/ flutter: sdk: flutter - meta: ^1.2.4 - path: ^1.7.0 - sqflite: ^1.3.2+3 - sqflite_common_ffi: ^1.1.1 + meta: ^1.3.0 + path: ^1.8.0 + sqflite: ^2.0.0+2 + sqflite_common_ffi: ^2.0.0 dev_dependencies: - build_runner: ^1.11.1 - collection: ^1.14.13 + build_runner: ^1.11.5 + collection: ^1.15.0 floor_generator: path: ../floor_generator/ flutter_test: sdk: flutter - matcher: ^0.12.9 - mockito: ^4.1.4 + matcher: ^0.12.10 + mockito: ^5.0.0 diff --git a/floor/test/adapter/deletion_adapter_test.dart b/floor/test/adapter/deletion_adapter_test.dart index a8d8cc66..306bf968 100644 --- a/floor/test/adapter/deletion_adapter_test.dart +++ b/floor/test/adapter/deletion_adapter_test.dart @@ -7,12 +7,11 @@ import '../test_util/person.dart'; void main() { final mockDatabaseExecutor = MockDatabaseExecutor(); - final mockDatabaseBatch = MockDatabaseBatch(); + final mockDatabaseBatch = MockBatch(); const entityName = 'person'; const primaryKeyColumnName = 'id'; - final valueMapper = (Person person) => - {'id': person.id, 'name': person.name}; + final valueMapper = (Person person) => {'id': person.id, 'name': person.name}; final underTest = DeletionAdapter( mockDatabaseExecutor, @@ -28,13 +27,18 @@ void main() { group('delete without return', () { test('delete item', () async { final person = Person(1, 'Simon'); + when(mockDatabaseExecutor.delete( + entityName, + where: '$primaryKeyColumnName = ?', + whereArgs: [person.id], + )).thenAnswer((_) => Future(() => 1)); await underTest.delete(person); verify(mockDatabaseExecutor.delete( entityName, where: '$primaryKeyColumnName = ?', - whereArgs: [person.id], + whereArgs: [person.id], )); }); @@ -53,12 +57,12 @@ void main() { mockDatabaseBatch.delete( entityName, where: '$primaryKeyColumnName = ?', - whereArgs: [person1.id], + whereArgs: [person1.id], ), mockDatabaseBatch.delete( entityName, where: '$primaryKeyColumnName = ?', - whereArgs: [person2.id], + whereArgs: [person2.id], ), mockDatabaseBatch.commit(noResult: false), ]); @@ -77,7 +81,7 @@ void main() { when(mockDatabaseExecutor.delete( entityName, where: '$primaryKeyColumnName = ?', - whereArgs: [person.id], + whereArgs: [person.id], )).thenAnswer((_) => Future(() => 1)); final actual = await underTest.deleteAndReturnChangedRows(person); @@ -85,7 +89,7 @@ void main() { verify(mockDatabaseExecutor.delete( entityName, where: '$primaryKeyColumnName = ?', - whereArgs: [person.id], + whereArgs: [person.id], )); expect(actual, equals(1)); }); @@ -105,12 +109,12 @@ void main() { mockDatabaseBatch.delete( entityName, where: '$primaryKeyColumnName = ?', - whereArgs: [person1.id], + whereArgs: [person1.id], ), mockDatabaseBatch.delete( entityName, where: '$primaryKeyColumnName = ?', - whereArgs: [person2.id], + whereArgs: [person2.id], ), mockDatabaseBatch.commit(noResult: false), ]); diff --git a/floor/test/adapter/insertion_adapter_test.dart b/floor/test/adapter/insertion_adapter_test.dart index bb14672e..054fa359 100644 --- a/floor/test/adapter/insertion_adapter_test.dart +++ b/floor/test/adapter/insertion_adapter_test.dart @@ -1,3 +1,5 @@ +import 'dart:async'; + import 'package:floor/floor.dart'; import 'package:floor/src/adapter/insertion_adapter.dart'; import 'package:flutter_test/flutter_test.dart'; @@ -9,11 +11,10 @@ import '../test_util/person.dart'; void main() { final mockDatabaseExecutor = MockDatabaseExecutor(); - final mockDatabaseBatch = MockDatabaseBatch(); + final mockDatabaseBatch = MockBatch(); const entityName = 'person'; - final valueMapper = (Person person) => - {'id': person.id, 'name': person.name}; + final valueMapper = (Person person) => {'id': person.id, 'name': person.name}; const onConflictStrategy = OnConflictStrategy.ignore; const conflictAlgorithm = ConflictAlgorithm.ignore; @@ -34,10 +35,15 @@ void main() { group('insertion without return', () { test('insert item', () async { final person = Person(1, 'Simon'); + final values = {'id': person.id, 'name': person.name}; + when(mockDatabaseExecutor.insert( + entityName, + values, + conflictAlgorithm: conflictAlgorithm, + )).thenAnswer((_) => Future(() => person.id)); await underTest.insert(person, onConflictStrategy); - final values = {'id': person.id, 'name': person.name}; verify(mockDatabaseExecutor.insert( entityName, values, @@ -49,18 +55,24 @@ void main() { final person1 = Person(1, 'Simon'); final person2 = Person(2, 'Frank'); final persons = [person1, person2]; + final values1 = {'id': person1.id, 'name': person1.name}; + final values2 = {'id': person2.id, 'name': person2.name}; when(mockDatabaseExecutor.batch()).thenReturn(mockDatabaseBatch); + when(mockDatabaseExecutor.insert( + entityName, + values1, + conflictAlgorithm: conflictAlgorithm, + )).thenAnswer((_) => Future(() => person1.id)); + when(mockDatabaseExecutor.insert( + entityName, + values2, + conflictAlgorithm: conflictAlgorithm, + )).thenAnswer((_) => Future(() => person2.id)); + when(mockDatabaseBatch.commit(noResult: true)) + .thenAnswer((_) => Future(() => [null])); await underTest.insertList(persons, onConflictStrategy); - final values1 = { - 'id': person1.id, - 'name': person1.name - }; - final values2 = { - 'id': person2.id, - 'name': person2.name - }; verifyInOrder([ mockDatabaseExecutor.batch(), mockDatabaseBatch.insert( @@ -87,7 +99,7 @@ void main() { group('insertion with return', () { test('insert item and return primary key', () async { final person = Person(1, 'Simon'); - final values = {'id': person.id, 'name': person.name}; + final values = {'id': person.id, 'name': person.name}; when(mockDatabaseExecutor.insert( entityName, values, @@ -105,14 +117,14 @@ void main() { expect(actual, equals(person.id)); }); - test('insert item but transaction failed (returns null)', () async { + test('insert item but transaction failed (return 0)', () async { final person = Person(1, 'Simon'); - final values = {'id': person.id, 'name': person.name}; + final values = {'id': person.id, 'name': person.name}; when(mockDatabaseExecutor.insert( entityName, values, conflictAlgorithm: conflictAlgorithm, - )).thenAnswer((_) => Future(() => null)); + )).thenAnswer((_) => Future(() => 0)); final actual = await underTest.insertAndReturnId(person, onConflictStrategy); @@ -122,7 +134,7 @@ void main() { values, conflictAlgorithm: conflictAlgorithm, )); - expect(actual, isNull); + expect(actual, equals(0)); }); test('insert items and return primary keys', () async { @@ -137,14 +149,8 @@ void main() { final actual = await underTest.insertListAndReturnIds(persons, onConflictStrategy); - final values1 = { - 'id': person1.id, - 'name': person1.name - }; - final values2 = { - 'id': person2.id, - 'name': person2.name - }; + final values1 = {'id': person1.id, 'name': person1.name}; + final values2 = {'id': person2.id, 'name': person2.name}; verifyInOrder([ mockDatabaseExecutor.batch(), mockDatabaseBatch.insert( @@ -191,8 +197,8 @@ void main() { test('insert item', () async { final person = Person(1, 'Simon'); when(mockDatabaseExecutor.insert( - any, - any, + entityName, + valueMapper(person), conflictAlgorithm: conflictAlgorithm, )).thenAnswer((_) => Future(() => person.id)); @@ -201,13 +207,13 @@ void main() { verify(mockStreamController.add(entityName)); }); - test('insert item but transaction failed (returns null)', () async { + test('insert item but transaction failed (returns 0)', () async { final person = Person(1, 'Simon'); when(mockDatabaseExecutor.insert( - any, - any, + entityName, + valueMapper(person), conflictAlgorithm: conflictAlgorithm, - )).thenAnswer((_) => Future(() => null)); + )).thenAnswer((_) => Future(() => 0)); await underTest.insert(person, onConflictStrategy); @@ -220,7 +226,7 @@ void main() { final persons = [person1, person2]; final primaryKeys = persons.map((person) => person.id).toList(); when(mockDatabaseExecutor.batch()).thenReturn(mockDatabaseBatch); - when(mockDatabaseBatch.commit(noResult: false)) + when(mockDatabaseBatch.commit(noResult: true)) .thenAnswer((_) => Future(() => primaryKeys)); await underTest.insertList(persons, onConflictStrategy); diff --git a/floor/test/adapter/migration_adapter_test.dart b/floor/test/adapter/migration_adapter_test.dart index 8b84d6d4..87f0d147 100644 --- a/floor/test/adapter/migration_adapter_test.dart +++ b/floor/test/adapter/migration_adapter_test.dart @@ -6,10 +6,10 @@ import 'package:mockito/mockito.dart'; import '../test_util/mocks.dart'; void main() { - final mockMigrationDatabase = MockSqfliteDatabase(); + final mockDatabase = MockDatabase(); tearDown(() { - clearInteractions(mockMigrationDatabase); + clearInteractions(mockDatabase); }); test('run single migration', () async { @@ -21,15 +21,16 @@ void main() { await database.execute(sql); }) ]; + when(mockDatabase.execute(sql)).thenAnswer((_) => Future(() {})); await MigrationAdapter.runMigrations( - mockMigrationDatabase, + mockDatabase, startVersion, endVersion, migrations, ); - verify(mockMigrationDatabase.execute(sql)); + verify(mockDatabase.execute(sql)); }); test('run multiple migrations in order', () async { @@ -49,18 +50,22 @@ void main() { await database.execute(sql2); }), ]; + final future = Future(() {}); + when(mockDatabase.execute(sql1)).thenAnswer((_) => future); + when(mockDatabase.execute(sql2)).thenAnswer((_) => future); + when(mockDatabase.execute(sql3)).thenAnswer((_) => future); await MigrationAdapter.runMigrations( - mockMigrationDatabase, + mockDatabase, startVersion, endVersion, migrations, ); verifyInOrder([ - mockMigrationDatabase.execute(sql1), - mockMigrationDatabase.execute(sql2), - mockMigrationDatabase.execute(sql3), + mockDatabase.execute(sql1), + mockDatabase.execute(sql2), + mockDatabase.execute(sql3), ]); }); @@ -75,14 +80,14 @@ void main() { ]; final actual = () => MigrationAdapter.runMigrations( - mockMigrationDatabase, + mockDatabase, startVersion, endVersion, migrations, ); expect(actual, throwsStateError); - verifyZeroInteractions(mockMigrationDatabase); + verifyZeroInteractions(mockDatabase); }); test('exception when no matching end version found', () { @@ -96,13 +101,13 @@ void main() { ]; final actual = () => MigrationAdapter.runMigrations( - mockMigrationDatabase, + mockDatabase, startVersion, endVersion, migrations, ); expect(actual, throwsStateError); - verifyZeroInteractions(mockMigrationDatabase); + verifyZeroInteractions(mockDatabase); }); } diff --git a/floor/test/adapter/query_adapter_test.dart b/floor/test/adapter/query_adapter_test.dart index fc211496..e86f60e8 100644 --- a/floor/test/adapter/query_adapter_test.dart +++ b/floor/test/adapter/query_adapter_test.dart @@ -11,7 +11,8 @@ void main() { final mockDatabaseExecutor = MockDatabaseExecutor(); const sql = 'abcd'; - final mapper = (Map row) => Person(row['id'], row['name']); + final mapper = (Map row) => + Person(row['id'] as int, row['name'] as String); tearDown(() { clearInteractions(mockDatabaseExecutor); @@ -24,7 +25,7 @@ void main() { test('returns item without arguments', () async { final person = Person(1, 'Frank'); final queryResult = Future(() => [ - {'id': person.id, 'name': person.name} + {'id': person.id, 'name': person.name} ]); when(mockDatabaseExecutor.rawQuery(sql)).thenAnswer((_) => queryResult); @@ -38,7 +39,7 @@ void main() { final person = Person(1, 'Frank'); final arguments = [person.id, person.name]; final queryResult = Future(() => [ - {'id': person.id, 'name': person.name} + {'id': person.id, 'name': person.name} ]); when(mockDatabaseExecutor.rawQuery(sql, arguments)) .thenAnswer((_) => queryResult); @@ -51,7 +52,7 @@ void main() { }); test('null when query returns nothing', () async { - final queryResult = Future(() => >[]); + final queryResult = Future(() => >[]); when(mockDatabaseExecutor.rawQuery(sql)).thenAnswer((_) => queryResult); final actual = await underTest.query(sql, mapper: mapper); @@ -63,8 +64,8 @@ void main() { test('exception because query returns multiple items', () async { final person = Person(1, 'Frank'); final queryResult = Future(() => [ - {'id': person.id, 'name': person.name}, - {'id': 2, 'name': 'Peter'}, + {'id': person.id, 'name': person.name}, + {'id': 2, 'name': 'Peter'}, ]); when(mockDatabaseExecutor.rawQuery(sql)).thenAnswer((_) => queryResult); @@ -80,8 +81,8 @@ void main() { final person = Person(1, 'Frank'); final person2 = Person(2, 'Peter'); final queryResult = Future(() => [ - {'id': person.id, 'name': person.name}, - {'id': person2.id, 'name': person2.name}, + {'id': person.id, 'name': person.name}, + {'id': person2.id, 'name': person2.name}, ]); when(mockDatabaseExecutor.rawQuery(sql)).thenAnswer((_) => queryResult); @@ -96,8 +97,8 @@ void main() { final person2 = Person(2, 'Peter'); final arguments = [person.id, person2.id]; final queryResult = Future(() => [ - {'id': person.id, 'name': person.name}, - {'id': person2.id, 'name': person2.name}, + {'id': person.id, 'name': person.name}, + {'id': person2.id, 'name': person2.name}, ]); when(mockDatabaseExecutor.rawQuery(sql, arguments)) .thenAnswer((_) => queryResult); @@ -109,8 +110,8 @@ void main() { verify(mockDatabaseExecutor.rawQuery(sql, arguments)); }); - test('returns emtpy list when query returns nothing', () async { - final queryResult = Future(() => >[]); + test('returns empty list when query returns nothing', () async { + final queryResult = Future(() => >[]); when(mockDatabaseExecutor.rawQuery(sql)).thenAnswer((_) => queryResult); final actual = await underTest.queryList(sql, mapper: mapper); @@ -129,6 +130,9 @@ void main() { test('executes query with argument', () async { final arguments = [123]; + final queryResult = Future(() => >[]); + when(mockDatabaseExecutor.rawQuery(sql, arguments)) + .thenAnswer((_) => queryResult); await underTest.queryNoReturn(sql, arguments: arguments); @@ -139,10 +143,10 @@ void main() { group('stream queries', () { // ignore: close_sinks - StreamController streamController; + StreamController? streamController; const entityName = 'person'; - QueryAdapter underTest; + late QueryAdapter underTest; setUp(() { streamController = StreamController(); @@ -150,14 +154,14 @@ void main() { }); tearDown(() { - streamController.close(); + streamController!.close(); streamController = null; }); test('query item and emit persistent item without arguments', () { final person = Person(1, 'Frank'); final queryResult = Future(() => [ - {'id': person.id, 'name': person.name} + {'id': person.id, 'name': person.name} ]); when(mockDatabaseExecutor.rawQuery(sql)).thenAnswer((_) => queryResult); @@ -171,7 +175,7 @@ void main() { final person = Person(1, 'Frank'); final arguments = [person.id, person.name]; final queryResult = Future(() => [ - {'id': person.id, 'name': person.name} + {'id': person.id, 'name': person.name} ]); when(mockDatabaseExecutor.rawQuery(sql, arguments)) .thenAnswer((_) => queryResult); @@ -190,23 +194,33 @@ void main() { test('query item and emit persistent item and new', () { final person = Person(1, 'Frank'); final queryResult = Future(() => [ - {'id': person.id, 'name': person.name} + {'id': person.id, 'name': person.name} ]); when(mockDatabaseExecutor.rawQuery(sql)).thenAnswer((_) => queryResult); final actual = underTest.queryStream(sql, queryableName: entityName, isView: false, mapper: mapper); - streamController.add(entityName); + streamController!.add(entityName); expect(actual, emitsInOrder([person, person])); }); + test('query item emits null when query has no result', () { + final queryResult = Future(() => >[]); + when(mockDatabaseExecutor.rawQuery(sql)).thenAnswer((_) => queryResult); + + final actual = underTest.queryStream(sql, + queryableName: entityName, isView: false, mapper: mapper); + + expect(actual, emits(null)); + }); + test('query items and emit persistent items without arguments', () async { final person = Person(1, 'Frank'); final person2 = Person(2, 'Peter'); final queryResult = Future(() => [ - {'id': person.id, 'name': person.name}, - {'id': person2.id, 'name': person2.name}, + {'id': person.id, 'name': person.name}, + {'id': person2.id, 'name': person2.name}, ]); when(mockDatabaseExecutor.rawQuery(sql)).thenAnswer((_) => queryResult); @@ -221,8 +235,8 @@ void main() { final person2 = Person(2, 'Peter'); final arguments = [person.id, person2.id]; final queryResult = Future(() => [ - {'id': person.id, 'name': person.name}, - {'id': person2.id, 'name': person2.name}, + {'id': person.id, 'name': person.name}, + {'id': person2.id, 'name': person2.name}, ]); when(mockDatabaseExecutor.rawQuery(sql, arguments)) .thenAnswer((_) => queryResult); @@ -242,14 +256,14 @@ void main() { final person = Person(1, 'Frank'); final person2 = Person(2, 'Peter'); final queryResult = Future(() => [ - {'id': person.id, 'name': person.name}, - {'id': person2.id, 'name': person2.name}, + {'id': person.id, 'name': person.name}, + {'id': person2.id, 'name': person2.name}, ]); when(mockDatabaseExecutor.rawQuery(sql)).thenAnswer((_) => queryResult); final actual = underTest.queryListStream(sql, queryableName: entityName, isView: false, mapper: mapper); - streamController.add(entityName); + streamController!.add(entityName); expect( actual, @@ -265,8 +279,8 @@ void main() { final person = Person(1, 'Frank'); final person2 = Person(2, 'Peter'); final queryResult = Future(() => [ - {'id': person.id, 'name': person.name}, - {'id': person2.id, 'name': person2.name}, + {'id': person.id, 'name': person.name}, + {'id': person2.id, 'name': person2.name}, ]); when(mockDatabaseExecutor.rawQuery(sql)).thenAnswer((_) => queryResult); @@ -281,8 +295,8 @@ void main() { ]), ); - streamController.add(entityName); - streamController.add('otherEntity'); + streamController!.add(entityName); + streamController!.add('otherEntity'); }); }); } diff --git a/floor/test/adapter/update_adapter_test.dart b/floor/test/adapter/update_adapter_test.dart index 1a455648..55a48a3f 100644 --- a/floor/test/adapter/update_adapter_test.dart +++ b/floor/test/adapter/update_adapter_test.dart @@ -9,12 +9,11 @@ import '../test_util/person.dart'; void main() { final mockDatabaseExecutor = MockDatabaseExecutor(); - final mockDatabaseBatch = MockDatabaseBatch(); + final mockDatabaseBatch = MockBatch(); const entityName = 'person'; const primaryKeyColumnName = 'id'; - final valueMapper = (Person person) => - {'id': person.id, 'name': person.name}; + final valueMapper = (Person person) => {'id': person.id, 'name': person.name}; const onConflictStrategy = OnConflictStrategy.ignore; const conflictAlgorithm = ConflictAlgorithm.ignore; @@ -35,15 +34,22 @@ void main() { group('update without return', () { test('update item', () async { final person = Person(1, 'Simon'); + final values = {'id': person.id, 'name': person.name}; + when(mockDatabaseExecutor.update( + entityName, + values, + where: '$primaryKeyColumnName = ?', + whereArgs: [person.id], + conflictAlgorithm: conflictAlgorithm, + )).thenAnswer((_) => Future(() => 1)); await underTest.update(person, onConflictStrategy); - final values = {'id': person.id, 'name': person.name}; verify(mockDatabaseExecutor.update( entityName, values, where: '$primaryKeyColumnName = ?', - whereArgs: [person.id], + whereArgs: [person.id], conflictAlgorithm: conflictAlgorithm, )); }); @@ -58,22 +64,22 @@ void main() { await underTest.updateList(persons, onConflictStrategy); - final values1 = {'id': person1.id, 'name': person1.name}; - final values2 = {'id': person2.id, 'name': person2.name}; + final values1 = {'id': person1.id, 'name': person1.name}; + final values2 = {'id': person2.id, 'name': person2.name}; verifyInOrder([ mockDatabaseExecutor.batch(), mockDatabaseBatch.update( entityName, values1, where: '$primaryKeyColumnName = ?', - whereArgs: [person1.id], + whereArgs: [person1.id], conflictAlgorithm: conflictAlgorithm, ), mockDatabaseBatch.update( entityName, values2, where: '$primaryKeyColumnName = ?', - whereArgs: [person2.id], + whereArgs: [person2.id], conflictAlgorithm: conflictAlgorithm, ), mockDatabaseBatch.commit(noResult: false), @@ -90,12 +96,12 @@ void main() { group('update with return', () { test('update item and return changed rows (1)', () async { final person = Person(1, 'Simon'); - final values = {'id': person.id, 'name': person.name}; + final values = {'id': person.id, 'name': person.name}; when(mockDatabaseExecutor.update( entityName, values, where: '$primaryKeyColumnName = ?', - whereArgs: [person.id], + whereArgs: [person.id], conflictAlgorithm: conflictAlgorithm, )).thenAnswer((_) => Future(() => 1)); @@ -108,7 +114,7 @@ void main() { entityName, values, where: '$primaryKeyColumnName = ?', - whereArgs: [person.id], + whereArgs: [person.id], conflictAlgorithm: conflictAlgorithm, )); expect(actual, equals(1)); @@ -127,22 +133,22 @@ void main() { onConflictStrategy, ); - final values1 = {'id': person1.id, 'name': person1.name}; - final values2 = {'id': person2.id, 'name': person2.name}; + final values1 = {'id': person1.id, 'name': person1.name}; + final values2 = {'id': person2.id, 'name': person2.name}; verifyInOrder([ mockDatabaseExecutor.batch(), mockDatabaseBatch.update( entityName, values1, where: '$primaryKeyColumnName = ?', - whereArgs: [person1.id], + whereArgs: [person1.id], conflictAlgorithm: conflictAlgorithm, ), mockDatabaseBatch.update( entityName, values2, where: '$primaryKeyColumnName = ?', - whereArgs: [person2.id], + whereArgs: [person2.id], conflictAlgorithm: conflictAlgorithm, ), mockDatabaseBatch.commit(noResult: false), diff --git a/floor/test/extension/on_conflict_strategy_extensions_test.dart b/floor/test/extension/on_conflict_strategy_extensions_test.dart index 23b411cf..bc1d945d 100644 --- a/floor/test/extension/on_conflict_strategy_extensions_test.dart +++ b/floor/test/extension/on_conflict_strategy_extensions_test.dart @@ -44,11 +44,5 @@ void main() { expect(actual, equals(ConflictAlgorithm.abort)); }); - - test('falls back to abort when null', () { - final actual = null.asSqfliteConflictAlgorithm(); - - expect(actual, equals(ConflictAlgorithm.abort)); - }); }); } diff --git a/floor/test/integration/autoincrement/autoinc_test.dart b/floor/test/integration/autoincrement/autoinc_test.dart index a2a983fc..576557a3 100644 --- a/floor/test/integration/autoincrement/autoinc_test.dart +++ b/floor/test/integration/autoincrement/autoinc_test.dart @@ -9,8 +9,8 @@ part 'autoinc_test.g.dart'; void main() { group('AutoIncrement tests', () { - TestDatabase database; - AIDao aiDao; + late TestDatabase database; + late AIDao aiDao; setUp(() async { database = await $FloorTestDatabase.inMemoryDatabaseBuilder().build(); @@ -22,21 +22,22 @@ void main() { }); test('first id is 1', () async { - final obj = AutoIncEntity(1.0,id:null); + final obj = AutoIncEntity(1.0, id: null); await aiDao.insertAIEntity(obj); final actual = await aiDao.findWithId(1); - expect(actual, equals(AutoIncEntity(1.0,id:1))); + expect(actual, equals(AutoIncEntity(1.0, id: 1))); }); test('retrieve multiple entities', () async { - final obj = AutoIncEntity(1.5,id:null); - final obj2 = AutoIncEntity(1.7,id:null); + final obj = AutoIncEntity(1.5, id: null); + final obj2 = AutoIncEntity(1.7, id: null); await aiDao.insertAIEntity(obj); await aiDao.insertAIEntity(obj2); final actual = await aiDao.findAll(); - expect(actual, equals([AutoIncEntity(1.5,id:1),AutoIncEntity(1.7,id:2)])); + expect(actual, + equals([AutoIncEntity(1.5, id: 1), AutoIncEntity(1.7, id: 2)])); }); }); } @@ -44,7 +45,7 @@ void main() { @entity class AutoIncEntity { @PrimaryKey(autoGenerate: true) - final int id; + final int? id; final double decimal; @@ -75,7 +76,7 @@ abstract class TestDatabase extends FloorDatabase { @dao abstract class AIDao { @Query('SELECT * FROM AutoIncEntity where id = :val') - Future findWithId(int val); + Future findWithId(int val); @Query('SELECT * FROM AutoIncEntity') Future> findAll(); diff --git a/floor/test/integration/blob/blob_test.dart b/floor/test/integration/blob/blob_test.dart index 1b275c9a..a6c9ffa2 100644 --- a/floor/test/integration/blob/blob_test.dart +++ b/floor/test/integration/blob/blob_test.dart @@ -11,8 +11,8 @@ part 'blob_test.g.dart'; void main() { group('BLOB tests', () { - TestDatabase database; - PersonDao personDao; + late TestDatabase database; + late PersonDao personDao; setUp(() async { database = await $FloorTestDatabase.inMemoryDatabaseBuilder().build(); @@ -71,7 +71,7 @@ abstract class TestDatabase extends FloorDatabase { @dao abstract class PersonDao { @Query('SELECT * FROM Person WHERE picture = :picture') - Future findPersonByPicture(Uint8List picture); + Future findPersonByPicture(Uint8List picture); @insert Future insertPerson(Person person); diff --git a/floor/test/integration/boolean_conversions/bool_test.dart b/floor/test/integration/boolean_conversions/bool_test.dart index 80bd3155..952982a1 100644 --- a/floor/test/integration/boolean_conversions/bool_test.dart +++ b/floor/test/integration/boolean_conversions/bool_test.dart @@ -9,8 +9,8 @@ part 'bool_test.g.dart'; void main() { group('Bool tests', () { - TestDatabase database; - BoolDao boolDao; + late TestDatabase database; + late BoolDao boolDao; setUp(() async { database = await $FloorTestDatabase.inMemoryDatabaseBuilder().build(); @@ -22,7 +22,7 @@ void main() { }); test('find by nonNull true', () async { - final obj = BooleanClass(true, nullable: false, nonnullable: true); + final obj = BooleanClass(true, nullable: false, nonNullable: true); await boolDao.insertBoolC(obj); final actual = await boolDao.findWithNonNullable(true); @@ -30,27 +30,15 @@ void main() { }); test('find by nonNull false and convert null boolean', () async { - final obj = BooleanClass(true, nullable: null, nonnullable: false); + final obj = BooleanClass(true, nullable: null, nonNullable: false); await boolDao.insertBoolC(obj); final actual = await boolDao.findWithNonNullable(false); expect(actual, equals(obj)); }); - test('find by nullable null', () async { - final obj = BooleanClass(true, nullable: null, nonnullable: true); - await boolDao.insertBoolC(obj); - - // null == null is false in SQL so this will not return any results. - final actual = await boolDao.findWithNullable(null); - expect(actual, equals(null)); - - final actual2 = await boolDao.findWithNullableBeingNull(); - expect(actual2, equals(obj)); - }); - test('find by nullable true', () async { - final obj = BooleanClass(true, nullable: true, nonnullable: true); + final obj = BooleanClass(true, nullable: true, nonNullable: true); await boolDao.insertBoolC(obj); final actual = await boolDao.findWithNullable(true); @@ -58,7 +46,7 @@ void main() { }); test('find by nullable false', () async { - final obj = BooleanClass(true, nullable: false, nonnullable: true); + final obj = BooleanClass(true, nullable: false, nonNullable: true); await boolDao.insertBoolC(obj); final actual = await boolDao.findWithNullable(false); @@ -70,15 +58,13 @@ void main() { @entity class BooleanClass { @primaryKey - final bool id; + final bool? id; - @ColumnInfo(nullable: true) - final bool nullable; + final bool? nullable; - @ColumnInfo(nullable: false) - final bool nonnullable; + final bool nonNullable; - BooleanClass(this.id, {this.nullable, this.nonnullable}); + BooleanClass(this.id, {this.nullable, required this.nonNullable}); @override bool operator ==(Object other) => @@ -87,14 +73,14 @@ class BooleanClass { runtimeType == other.runtimeType && id == other.id && nullable == other.nullable && - nonnullable == other.nonnullable; + nonNullable == other.nonNullable; @override - int get hashCode => id.hashCode ^ nullable.hashCode ^ nonnullable.hashCode; + int get hashCode => id.hashCode ^ nullable.hashCode ^ nonNullable.hashCode; @override String toString() { - return 'BooleanClass{id: $id, nullable: $nullable, nonnullable: $nonnullable}'; + return 'BooleanClass{id: $id, nullable: $nullable, nonNullable: $nonNullable}'; } } @@ -105,14 +91,14 @@ abstract class TestDatabase extends FloorDatabase { @dao abstract class BoolDao { - @Query('SELECT * FROM BooleanClass where nonnullable = :val') - Future findWithNonNullable(bool val); + @Query('SELECT * FROM BooleanClass where nonNullable = :val') + Future findWithNonNullable(bool val); @Query('SELECT * FROM BooleanClass where nullable = :val') - Future findWithNullable(bool val); + Future findWithNullable(bool val); - @Query('SELECT * FROM BooleanClass where nullable is null') - Future findWithNullableBeingNull(); + @Query('SELECT * FROM BooleanClass where nullable IS NULL') + Future findWithNullableBeingNull(); @insert Future insertBoolC(BooleanClass person); diff --git a/floor/test/integration/dao/dog_dao.dart b/floor/test/integration/dao/dog_dao.dart index 0be242b4..c1eb8250 100644 --- a/floor/test/integration/dao/dog_dao.dart +++ b/floor/test/integration/dao/dog_dao.dart @@ -8,7 +8,7 @@ abstract class DogDao { Future insertDog(Dog dog); @Query('SELECT * FROM dog WHERE owner_id = :id') - Future findDogForPersonId(int id); + Future findDogForPersonId(int id); @Query('SELECT * FROM dog') Future> findAllDogs(); diff --git a/floor/test/integration/dao/name_dao.dart b/floor/test/integration/dao/name_dao.dart index 30496573..cb6ccad0 100644 --- a/floor/test/integration/dao/name_dao.dart +++ b/floor/test/integration/dao/name_dao.dart @@ -12,11 +12,11 @@ abstract class NameDao { Stream> findAllNamesAsStream(); @Query('SELECT * FROM names WHERE name = :name') - Future findExactName(String name); + Future findExactName(String name); @Query('SELECT * FROM names WHERE name LIKE :suffix ORDER BY name ASC') Future> findNamesLike(String suffix); @Query('SELECT * FROM multiline_query_names WHERE name = :name') - Future findMultilineQueryName(String name); + Future findMultilineQueryName(String name); } diff --git a/floor/test/integration/dao/person_dao.dart b/floor/test/integration/dao/person_dao.dart index 4a808f95..70d8ff81 100644 --- a/floor/test/integration/dao/person_dao.dart +++ b/floor/test/integration/dao/person_dao.dart @@ -12,13 +12,13 @@ abstract class PersonDao { Stream> findAllPersonsAsStream(); @Query('SELECT * FROM person WHERE id = :id') - Future findPersonById(int id); + Future findPersonById(int id); @Query('SELECT * FROM person WHERE id = :id') - Stream findPersonByIdAsStream(int id); + Stream findPersonByIdAsStream(int id); @Query('SELECT * FROM person WHERE id = :id AND custom_name = :name') - Future findPersonByIdAndName(int id, String name); + Future findPersonByIdAndName(int id, String name); @Query('SELECT * FROM person WHERE id IN (:ids)') Future> findPersonsWithIds(List ids); diff --git a/floor/test/integration/database_test.dart b/floor/test/integration/database_test.dart index 687de90d..c86e1765 100644 --- a/floor/test/integration/database_test.dart +++ b/floor/test/integration/database_test.dart @@ -12,9 +12,9 @@ import 'model/person.dart'; void main() { group('database tests', () { - TestDatabase database; - PersonDao personDao; - DogDao dogDao; + late TestDatabase database; + late PersonDao personDao; + late DogDao dogDao; setUp(() async { final migration1to2 = Migration(1, 2, (database) async { @@ -68,7 +68,7 @@ void main() { await personDao.updatePerson(updatedPerson); - final actual = await personDao.findPersonById(person.id); + final actual = await personDao.findPersonById(person.id!); expect(actual, equals(updatedPerson)); }); @@ -216,7 +216,7 @@ void main() { final actual = await personDao.updatePersonWithReturn(updatedPerson); - final persistentPerson = await personDao.findPersonById(person.id); + final persistentPerson = await personDao.findPersonById(person.id!); expect(persistentPerson, equals(updatedPerson)); expect(actual, equals(1)); }); @@ -265,10 +265,10 @@ void main() { test('find dog for person', () async { final person = Person(1, 'Simon'); await personDao.insertPerson(person); - final dog = Dog(2, 'Peter', 'Pete', person.id); + final dog = Dog(2, 'Peter', 'Pete', person.id!); await dogDao.insertDog(dog); - final actual = await dogDao.findDogForPersonId(person.id); + final actual = await dogDao.findDogForPersonId(person.id!); expect(actual, equals(dog)); }); @@ -276,7 +276,7 @@ void main() { test('cascade delete dog on deletion of person', () async { final person = Person(1, 'Simon'); await personDao.insertPerson(person); - final dog = Dog(2, 'Peter', 'Pete', person.id); + final dog = Dog(2, 'Peter', 'Pete', person.id!); await dogDao.insertDog(dog); await personDao.deletePerson(person); @@ -304,7 +304,7 @@ void main() { final person2 = Person(2, 'Frank'); final person3 = Person(3, 'Paul'); await personDao.insertPersons([person1, person2, person3]); - final ids = [person1.id, person2.id]; + final ids = [person1.id!, person2.id!]; final actual = await personDao.findPersonsWithIds(ids); diff --git a/floor/test/integration/fts/mail_dao.dart b/floor/test/integration/fts/mail_dao.dart index d173c405..df1b7311 100644 --- a/floor/test/integration/fts/mail_dao.dart +++ b/floor/test/integration/fts/mail_dao.dart @@ -5,7 +5,7 @@ import 'mail.dart'; @dao abstract class MailDao { @Query('SELECT * FROM mail WHERE rowid = :id') - Future findMailById(int id); + Future findMailById(int id); @Query('SELECT * FROM mail WHERE text match :key') Future> findMailByKey(String key); diff --git a/floor/test/integration/fts/mail_database_test.dart b/floor/test/integration/fts/mail_database_test.dart index dece6b65..9979cb04 100644 --- a/floor/test/integration/fts/mail_database_test.dart +++ b/floor/test/integration/fts/mail_database_test.dart @@ -14,8 +14,8 @@ void main() { }; group('Fts Database Test', () { - MailDatabase mailDatabase; - MailDao mailDao; + late MailDatabase mailDatabase; + late MailDao mailDao; setUp(() async { mailDatabase = await $FloorMailDatabase.inMemoryDatabaseBuilder().build(); @@ -35,7 +35,7 @@ void main() { expect(mailList.length, equals(5)); - final mail = await mailDao.findMailById(1); + final mail = (await mailDao.findMailById(1))!; expect(mailMap[1], mail.text); diff --git a/floor/test/integration/inheritance/dao_inheritance_test.dart b/floor/test/integration/inheritance/dao_inheritance_test.dart index 51dd916e..4e80fb5f 100644 --- a/floor/test/integration/inheritance/dao_inheritance_test.dart +++ b/floor/test/integration/inheritance/dao_inheritance_test.dart @@ -9,8 +9,8 @@ part 'dao_inheritance_test.g.dart'; void main() { group('dao inheritance tests', () { - TestDatabase database; - PersonDao personDao; + late TestDatabase database; + late PersonDao personDao; setUp(() async { database = await $FloorTestDatabase.inMemoryDatabaseBuilder().build(); @@ -40,7 +40,7 @@ abstract class TestDatabase extends FloorDatabase { @dao abstract class PersonDao extends AbstractDao { @Query('SELECT * FROM Person WHERE id = :id') - Future findPersonById(int id); + Future findPersonById(int id); } abstract class AbstractDao { diff --git a/floor/test/integration/inheritance/entity_inheritance_test.dart b/floor/test/integration/inheritance/entity_inheritance_test.dart index 65f27796..da6c8970 100644 --- a/floor/test/integration/inheritance/entity_inheritance_test.dart +++ b/floor/test/integration/inheritance/entity_inheritance_test.dart @@ -9,8 +9,8 @@ part 'entity_inheritance_test.g.dart'; void main() { group('entity inheritance tests', () { - TestDatabase database; - CommentDao commentDao; + late TestDatabase database; + late CommentDao commentDao; setUp(() async { database = await $FloorTestDatabase.inMemoryDatabaseBuilder().build(); @@ -34,19 +34,19 @@ void main() { // data models: class BaseObject { - @PrimaryKey() + @primaryKey final int id; - @ColumnInfo(name: 'create_time', nullable: false) + @ColumnInfo(name: 'create_time') final String createTime; @ColumnInfo(name: 'update_time') - final String updateTime; + final String? updateTime; BaseObject( - this.id, - this.updateTime, { - String createTime, + this.id, { + this.updateTime, + String? createTime, }) : createTime = createTime ?? DateTime.now().toString(); } @@ -56,9 +56,13 @@ class Comment extends BaseObject { final String content; - Comment(int id, this.author, - {this.content = '', String createTime, String updateTime}) - : super(id, updateTime, createTime: createTime); + Comment( + int id, + this.author, { + this.content = '', + String? createTime, + String? updateTime, + }) : super(id, updateTime: updateTime, createTime: createTime); @override bool operator ==(Object other) => @@ -94,7 +98,7 @@ abstract class TestDatabase extends FloorDatabase { @dao abstract class CommentDao { @Query('SELECT * FROM comments WHERE id = :id') - Future findCommentById(int id); + Future findCommentById(int id); @insert Future addComment(Comment c); diff --git a/floor/test/integration/model/dog.dart b/floor/test/integration/model/dog.dart index da907edd..2e2ff55b 100644 --- a/floor/test/integration/model/dog.dart +++ b/floor/test/integration/model/dog.dart @@ -15,7 +15,7 @@ import 'person.dart'; ) class Dog { @primaryKey - final int id; + final int? id; final String name; diff --git a/floor/test/integration/model/person.dart b/floor/test/integration/model/person.dart index 817f9aa9..5bdafe89 100644 --- a/floor/test/integration/model/person.dart +++ b/floor/test/integration/model/person.dart @@ -8,9 +8,9 @@ import 'package:floor/floor.dart'; ) class Person { @primaryKey - final int id; + final int? id; - @ColumnInfo(name: 'custom_name', nullable: false) + @ColumnInfo(name: 'custom_name') final String name; Person(this.id, this.name); diff --git a/floor/test/integration/stream_query_test.dart b/floor/test/integration/stream_query_test.dart index 594af82f..26d3b99e 100644 --- a/floor/test/integration/stream_query_test.dart +++ b/floor/test/integration/stream_query_test.dart @@ -8,8 +8,8 @@ import 'model/person.dart'; void main() { group('stream query tests', () { - TestDatabase database; - PersonDao personDao; + late TestDatabase database; + late PersonDao personDao; setUp(() async { database = await $FloorTestDatabase.inMemoryDatabaseBuilder().build(); @@ -34,7 +34,7 @@ void main() { final person = Person(1, 'Simon'); await personDao.insertPerson(person); - final actual = personDao.findPersonByIdAsStream(person.id); + final actual = personDao.findPersonByIdAsStream(person.id!); expect(actual, emits(person)); }); @@ -68,7 +68,7 @@ void main() { final updatedPerson = Person(person.id, 'Frank'); await personDao.insertPerson(person); - final actual = personDao.findPersonByIdAsStream(person.id); + final actual = personDao.findPersonByIdAsStream(person.id!); expect(actual, emits(person)); await personDao.updatePerson(updatedPerson); @@ -129,10 +129,10 @@ void main() { test('regression test streaming updates from other Dao', () async { final person1 = Person(1, 'Simon'); final person2 = Person(2, 'Frank'); - final dog1 = Dog(1, 'Dog', 'Doggie', person1.id); - final dog2 = Dog(2, 'OtherDog', 'Doggo', person2.id); + final dog1 = Dog(1, 'Dog', 'Doggie', person1.id!); + final dog2 = Dog(2, 'OtherDog', 'Doggo', person2.id!); - final actual = personDao.findAllDogsOfPersonAsStream(person1.id); + final actual = personDao.findAllDogsOfPersonAsStream(person1.id!); expect( actual, emitsInOrder(>[ diff --git a/floor/test/integration/type_converter/type_converter_database_test.dart b/floor/test/integration/type_converter/type_converter_database_test.dart index 016a83d0..5a0ccc5e 100644 --- a/floor/test/integration/type_converter/type_converter_database_test.dart +++ b/floor/test/integration/type_converter/type_converter_database_test.dart @@ -5,8 +5,8 @@ import 'order_dao.dart'; import 'order_database.dart'; void main() { - OrderDatabase database; - OrderDao orderDao; + late OrderDatabase database; + late OrderDao orderDao; setUp(() async { database = await $FloorOrderDatabase.inMemoryDatabaseBuilder().build(); diff --git a/floor/test/integration/view/view_test.dart b/floor/test/integration/view/view_test.dart index fb028fe8..1a923342 100644 --- a/floor/test/integration/view/view_test.dart +++ b/floor/test/integration/view/view_test.dart @@ -29,10 +29,10 @@ abstract class ViewTestDatabase extends FloorDatabase { void main() { group('database tests', () { - ViewTestDatabase database; - PersonDao personDao; - DogDao dogDao; - NameDao nameDao; + late ViewTestDatabase database; + late PersonDao personDao; + late DogDao dogDao; + late NameDao nameDao; setUp(() async { database = await $FloorViewTestDatabase.inMemoryDatabaseBuilder().build(); diff --git a/floor/test/test_util/mocks.dart b/floor/test/test_util/mocks.dart index 042f0bc1..ee19f529 100644 --- a/floor/test/test_util/mocks.dart +++ b/floor/test/test_util/mocks.dart @@ -1,12 +1,435 @@ -import 'dart:async'; +import 'dart:async' as _i3; -import 'package:mockito/mockito.dart'; -import 'package:sqflite/sqflite.dart'; +import 'package:mockito/mockito.dart' as _i1; +import 'package:sqflite_common/sqlite_api.dart' as _i2; +import 'package:sqflite_common/src/sql_builder.dart' as _i4; -class MockDatabaseExecutor extends Mock implements DatabaseExecutor {} +// ignore_for_file: comment_references +// ignore_for_file: unnecessary_parenthesis -class MockDatabaseBatch extends Mock implements Batch {} +class _FakeBatch extends _i1.Fake implements _i2.Batch {} -class MockSqfliteDatabase extends Mock implements Database {} +class _FakeStreamSink extends _i1.Fake implements _i3.StreamSink {} -class MockStreamController extends Mock implements StreamController {} +/// A class which mocks [DatabaseExecutor]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockDatabaseExecutor extends _i1.Mock implements _i2.DatabaseExecutor { + MockDatabaseExecutor() { + _i1.throwOnMissingStub(this); + } + + @override + _i3.Future execute(String? sql, [List? arguments]) => + (super.noSuchMethod(Invocation.method(#execute, [sql, arguments]), + returnValue: Future.value(null), + returnValueForMissingStub: Future.value()) as _i3.Future); + + @override + _i3.Future rawInsert(String? sql, [List? arguments]) => + (super.noSuchMethod(Invocation.method(#rawInsert, [sql, arguments]), + returnValue: Future.value(0)) as _i3.Future); + + @override + _i3.Future insert(String? table, Map? values, + {String? nullColumnHack, _i4.ConflictAlgorithm? conflictAlgorithm}) => + (super.noSuchMethod( + Invocation.method(#insert, [ + table, + values + ], { + #nullColumnHack: nullColumnHack, + #conflictAlgorithm: conflictAlgorithm + }), + returnValue: Future.value(0)) as _i3.Future); + + @override + _i3.Future>> query(String? table, + {bool? distinct, + List? columns, + String? where, + List? whereArgs, + String? groupBy, + String? having, + String? orderBy, + int? limit, + int? offset}) => + (super.noSuchMethod( + Invocation.method(#query, [ + table + ], { + #distinct: distinct, + #columns: columns, + #where: where, + #whereArgs: whereArgs, + #groupBy: groupBy, + #having: having, + #orderBy: orderBy, + #limit: limit, + #offset: offset + }), + returnValue: Future.value(>[])) + as _i3.Future>>); + + @override + _i3.Future>> rawQuery(String? sql, + [List? arguments]) => + (super.noSuchMethod(Invocation.method(#rawQuery, [sql, arguments]), + returnValue: Future.value(>[])) + as _i3.Future>>); + + @override + _i3.Future rawUpdate(String? sql, [List? arguments]) => + (super.noSuchMethod(Invocation.method(#rawUpdate, [sql, arguments]), + returnValue: Future.value(0)) as _i3.Future); + + @override + _i3.Future update(String? table, Map? values, + {String? where, + List? whereArgs, + _i4.ConflictAlgorithm? conflictAlgorithm}) => + (super.noSuchMethod( + Invocation.method(#update, [ + table, + values + ], { + #where: where, + #whereArgs: whereArgs, + #conflictAlgorithm: conflictAlgorithm + }), + returnValue: Future.value(0)) as _i3.Future); + + @override + _i3.Future rawDelete(String? sql, [List? arguments]) => + (super.noSuchMethod(Invocation.method(#rawDelete, [sql, arguments]), + returnValue: Future.value(0)) as _i3.Future); + + @override + _i3.Future delete(String? table, + {String? where, List? whereArgs}) => + (super.noSuchMethod( + Invocation.method( + #delete, [table], {#where: where, #whereArgs: whereArgs}), + returnValue: Future.value(0)) as _i3.Future); + + @override + _i2.Batch batch() => (super.noSuchMethod(Invocation.method(#batch, []), + returnValue: _FakeBatch()) as _i2.Batch); +} + +/// A class which mocks [Batch]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockBatch extends _i1.Mock implements _i2.Batch { + MockBatch() { + _i1.throwOnMissingStub(this); + } + + @override + _i3.Future> commit( + {bool? exclusive, bool? noResult, bool? continueOnError}) => + (super.noSuchMethod( + Invocation.method(#commit, [], { + #exclusive: exclusive, + #noResult: noResult, + #continueOnError: continueOnError + }), + returnValue: Future.value([])) as _i3.Future>); + + @override + void rawInsert(String? sql, [List? arguments]) => + super.noSuchMethod(Invocation.method(#rawInsert, [sql, arguments]), + returnValueForMissingStub: null); + + @override + void insert(String? table, Map? values, + {String? nullColumnHack, _i4.ConflictAlgorithm? conflictAlgorithm}) => + super.noSuchMethod( + Invocation.method(#insert, [ + table, + values + ], { + #nullColumnHack: nullColumnHack, + #conflictAlgorithm: conflictAlgorithm + }), + returnValueForMissingStub: null); + + @override + void rawUpdate(String? sql, [List? arguments]) => + super.noSuchMethod(Invocation.method(#rawUpdate, [sql, arguments]), + returnValueForMissingStub: null); + + @override + void update(String? table, Map? values, + {String? where, + List? whereArgs, + _i4.ConflictAlgorithm? conflictAlgorithm}) => + super.noSuchMethod( + Invocation.method(#update, [ + table, + values + ], { + #where: where, + #whereArgs: whereArgs, + #conflictAlgorithm: conflictAlgorithm + }), + returnValueForMissingStub: null); + + @override + void rawDelete(String? sql, [List? arguments]) => + super.noSuchMethod(Invocation.method(#rawDelete, [sql, arguments]), + returnValueForMissingStub: null); + + @override + void delete(String? table, {String? where, List? whereArgs}) => + super.noSuchMethod( + Invocation.method( + #delete, [table], {#where: where, #whereArgs: whereArgs}), + returnValueForMissingStub: null); + + @override + void execute(String? sql, [List? arguments]) => + super.noSuchMethod(Invocation.method(#execute, [sql, arguments]), + returnValueForMissingStub: null); + + @override + void query(String? table, + {bool? distinct, + List? columns, + String? where, + List? whereArgs, + String? groupBy, + String? having, + String? orderBy, + int? limit, + int? offset}) => + super.noSuchMethod( + Invocation.method(#query, [ + table + ], { + #distinct: distinct, + #columns: columns, + #where: where, + #whereArgs: whereArgs, + #groupBy: groupBy, + #having: having, + #orderBy: orderBy, + #limit: limit, + #offset: offset + }), + returnValueForMissingStub: null); + + @override + void rawQuery(String? sql, [List? arguments]) => + super.noSuchMethod(Invocation.method(#rawQuery, [sql, arguments]), + returnValueForMissingStub: null); +} + +/// A class which mocks [StreamController]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockStreamController extends _i1.Mock + implements _i3.StreamController { + MockStreamController() { + _i1.throwOnMissingStub(this); + } + + @override + _i3.Stream get stream => (super.noSuchMethod(Invocation.getter(#stream), + returnValue: Stream.empty()) as _i3.Stream); + + @override + _i3.StreamSink get sink => (super.noSuchMethod(Invocation.getter(#sink), + returnValue: _FakeStreamSink()) as _i3.StreamSink); + + @override + bool get isClosed => + (super.noSuchMethod(Invocation.getter(#isClosed), returnValue: false) + as bool); + + @override + bool get isPaused => + (super.noSuchMethod(Invocation.getter(#isPaused), returnValue: false) + as bool); + + @override + bool get hasListener => + (super.noSuchMethod(Invocation.getter(#hasListener), returnValue: false) + as bool); + + @override + _i3.Future get done => (super.noSuchMethod(Invocation.getter(#done), + returnValue: Future.value(null)) as _i3.Future); + + @override + void add(T? event) => super.noSuchMethod(Invocation.method(#add, [event]), + returnValueForMissingStub: null); + + @override + void addError(Object? error, [StackTrace? stackTrace]) => + super.noSuchMethod(Invocation.method(#addError, [error, stackTrace]), + returnValueForMissingStub: null); + + @override + _i3.Future close() => + (super.noSuchMethod(Invocation.method(#close, []), + returnValue: Future.value(null)) as _i3.Future); + + @override + _i3.Future addStream(_i3.Stream? source, {bool? cancelOnError}) => + (super.noSuchMethod( + Invocation.method( + #addStream, [source], {#cancelOnError: cancelOnError}), + returnValue: Future.value(null)) as _i3.Future); +} + +/// A class which mocks [Database]. +/// +/// See the documentation for Mockito's code generation for more information. +class MockDatabase extends _i1.Mock implements _i2.Database { + MockDatabase() { + _i1.throwOnMissingStub(this); + } + + @override + String get path => + (super.noSuchMethod(Invocation.getter(#path), returnValue: '') as String); + + @override + bool get isOpen => + (super.noSuchMethod(Invocation.getter(#isOpen), returnValue: false) + as bool); + + @override + _i3.Future close() => (super.noSuchMethod(Invocation.method(#close, []), + returnValue: Future.value(null), + returnValueForMissingStub: Future.value()) as _i3.Future); + + @override + _i3.Future transaction(_i3.Future Function(_i2.Transaction)? action, + {bool? exclusive}) => + (super.noSuchMethod( + Invocation.method(#transaction, [action], {#exclusive: exclusive}), + returnValue: Future.value(null)) as _i3.Future); + + @override + _i3.Future getVersion() => + (super.noSuchMethod(Invocation.method(#getVersion, []), + returnValue: Future.value(0)) as _i3.Future); + + @override + _i3.Future setVersion(int? version) => + (super.noSuchMethod(Invocation.method(#setVersion, [version]), + returnValue: Future.value(null), + returnValueForMissingStub: Future.value()) as _i3.Future); + + @override + _i3.Future devInvokeMethod(String? method, [dynamic arguments]) => + (super.noSuchMethod( + Invocation.method(#devInvokeMethod, [method, arguments]), + returnValue: Future.value(null)) as _i3.Future); + + @override + _i3.Future devInvokeSqlMethod(String? method, String? sql, + [List? arguments]) => + (super.noSuchMethod( + Invocation.method(#devInvokeSqlMethod, [method, sql, arguments]), + returnValue: Future.value(null)) as _i3.Future); + + @override + _i3.Future execute(String? sql, [List? arguments]) => + (super.noSuchMethod(Invocation.method(#execute, [sql, arguments]), + returnValue: Future.value(null), + returnValueForMissingStub: Future.value()) as _i3.Future); + + @override + _i3.Future rawInsert(String? sql, [List? arguments]) => + (super.noSuchMethod(Invocation.method(#rawInsert, [sql, arguments]), + returnValue: Future.value(0)) as _i3.Future); + + @override + _i3.Future insert(String? table, Map? values, + {String? nullColumnHack, _i4.ConflictAlgorithm? conflictAlgorithm}) => + (super.noSuchMethod( + Invocation.method(#insert, [ + table, + values + ], { + #nullColumnHack: nullColumnHack, + #conflictAlgorithm: conflictAlgorithm + }), + returnValue: Future.value(0)) as _i3.Future); + + @override + _i3.Future>> query(String? table, + {bool? distinct, + List? columns, + String? where, + List? whereArgs, + String? groupBy, + String? having, + String? orderBy, + int? limit, + int? offset}) => + (super.noSuchMethod( + Invocation.method(#query, [ + table + ], { + #distinct: distinct, + #columns: columns, + #where: where, + #whereArgs: whereArgs, + #groupBy: groupBy, + #having: having, + #orderBy: orderBy, + #limit: limit, + #offset: offset + }), + returnValue: Future.value(>[])) + as _i3.Future>>); + + @override + _i3.Future>> rawQuery(String? sql, + [List? arguments]) => + (super.noSuchMethod(Invocation.method(#rawQuery, [sql, arguments]), + returnValue: Future.value(>[])) + as _i3.Future>>); + + @override + _i3.Future rawUpdate(String? sql, [List? arguments]) => + (super.noSuchMethod(Invocation.method(#rawUpdate, [sql, arguments]), + returnValue: Future.value(0)) as _i3.Future); + + @override + _i3.Future update(String? table, Map? values, + {String? where, + List? whereArgs, + _i4.ConflictAlgorithm? conflictAlgorithm}) => + (super.noSuchMethod( + Invocation.method(#update, [ + table, + values + ], { + #where: where, + #whereArgs: whereArgs, + #conflictAlgorithm: conflictAlgorithm + }), + returnValue: Future.value(0)) as _i3.Future); + + @override + _i3.Future rawDelete(String? sql, [List? arguments]) => + (super.noSuchMethod(Invocation.method(#rawDelete, [sql, arguments]), + returnValue: Future.value(0)) as _i3.Future); + + @override + _i3.Future delete(String? table, + {String? where, List? whereArgs}) => + (super.noSuchMethod( + Invocation.method( + #delete, [table], {#where: where, #whereArgs: whereArgs}), + returnValue: Future.value(0)) as _i3.Future); + + @override + _i2.Batch batch() => (super.noSuchMethod(Invocation.method(#batch, []), + returnValue: _FakeBatch()) as _i2.Batch); +} diff --git a/floor/test/util/primary_key_helper_test.dart b/floor/test/util/primary_key_helper_test.dart index abf64474..ac4c1707 100644 --- a/floor/test/util/primary_key_helper_test.dart +++ b/floor/test/util/primary_key_helper_test.dart @@ -33,7 +33,7 @@ void main() { expect(actual, equals(expected)); }); - test('Get group primary key values from composit primary key', () { + test('Get group primary key values from composite primary key', () { final primaryKey = ['foo', 'bar']; final values = {'foo': 1, 'bar': 2}; @@ -42,5 +42,28 @@ void main() { final expected = [1, 2]; expect(actual, equals(expected)); }); + + test('Get group primary key values from composite primary key without null', + () { + final primaryKey = ['foo', 'bar']; + final values = {'foo': 1, 'bar': null}; + + final actual = PrimaryKeyHelper.getPrimaryKeyValues(primaryKey, values); + + final expected = [1]; + expect(actual, equals(expected)); + }); + + test( + 'Get group primary key values from composite primary key without null when value for key is missing', + () { + final primaryKey = ['foo', 'bar']; + final values = {'foo': 1}; + + final actual = PrimaryKeyHelper.getPrimaryKeyValues(primaryKey, values); + + final expected = [1]; + expect(actual, equals(expected)); + }); }); } diff --git a/floor_annotation/CHANGELOG.md b/floor_annotation/CHANGELOG.md index 61c9420b..53739a0d 100644 --- a/floor_annotation/CHANGELOG.md +++ b/floor_annotation/CHANGELOG.md @@ -1,5 +1,17 @@ # Changelog +# 1.0.0-nullsafety.1 + +### Changes + +* Migrate to stable Dart 2.12 + +# 1.0.0-nullsafety.0 + +### 🚀 Features + +* Make floor null safe + # 0.12.0 * Add `@Fts3` and `@Fts4` annotations @@ -18,7 +30,8 @@ # 0.8.0 -**⚠️ You need to migrate the explicit usages of `OnConflictStrategy` and `ForeignKeyAction` from snake case to camel case.** +**⚠️ You need to migrate the explicit usages of `OnConflictStrategy` and `ForeignKeyAction` from snake case to camel +case.** * Apply camel case to constants @@ -59,7 +72,7 @@ ### 🐛 Bug Fixes -* Correct mapper instance name referenced by generated query methods +* Correct mapper instance name referenced by generated query methods # 0.2.0 diff --git a/floor_annotation/lib/src/column_info.dart b/floor_annotation/lib/src/column_info.dart index a65b052e..6b4f1233 100644 --- a/floor_annotation/lib/src/column_info.dart +++ b/floor_annotation/lib/src/column_info.dart @@ -1,10 +1,7 @@ /// Allows customization of the column associated with this field. class ColumnInfo { /// The custom name of the column. - final String name; + final String? name; - /// Defines if the associated column is allowed to contain 'null'. - final bool nullable; - - const ColumnInfo({this.name, this.nullable = true}); + const ColumnInfo({this.name}); } diff --git a/floor_annotation/lib/src/database.dart b/floor_annotation/lib/src/database.dart index 13b2bc5f..d3bf873e 100644 --- a/floor_annotation/lib/src/database.dart +++ b/floor_annotation/lib/src/database.dart @@ -1,5 +1,3 @@ -import 'package:meta/meta.dart'; - /// Marks a class as a FloorDatabase. class Database { /// The database version. @@ -13,8 +11,8 @@ class Database { /// Marks a class as a FloorDatabase. const Database({ - @required this.version, - @required this.entities, + required this.version, + required this.entities, this.views = const [], }); } diff --git a/floor_annotation/lib/src/database_view.dart b/floor_annotation/lib/src/database_view.dart index 393d4209..e9e2044d 100644 --- a/floor_annotation/lib/src/database_view.dart +++ b/floor_annotation/lib/src/database_view.dart @@ -1,7 +1,7 @@ /// Marks a class as a database view (a fixed select statement). class DatabaseView { /// The table name of the SQLite view. - final String viewName; + final String? viewName; /// The SELECT query on which the view is based on. final String query; diff --git a/floor_annotation/lib/src/entity.dart b/floor_annotation/lib/src/entity.dart index b1b8ef33..5573715b 100644 --- a/floor_annotation/lib/src/entity.dart +++ b/floor_annotation/lib/src/entity.dart @@ -4,7 +4,7 @@ import 'package:floor_annotation/src/index.dart'; /// Marks a class as a database entity (table). class Entity { /// The table name of the SQLite table. - final String tableName; + final String? tableName; /// List of indices on the table. final List indices; diff --git a/floor_annotation/lib/src/foreign_key.dart b/floor_annotation/lib/src/foreign_key.dart index 1e76b89c..2276b0fe 100644 --- a/floor_annotation/lib/src/foreign_key.dart +++ b/floor_annotation/lib/src/foreign_key.dart @@ -1,5 +1,3 @@ -import 'package:meta/meta.dart'; - /// Declares a foreign key on another [Entity]. class ForeignKey { /// The list of column names in the current [Entity]. @@ -25,9 +23,9 @@ class ForeignKey { /// Declares a foreign key on another [Entity]. const ForeignKey({ - @required this.childColumns, - @required this.parentColumns, - @required this.entity, + required this.childColumns, + required this.parentColumns, + required this.entity, this.onUpdate = ForeignKeyAction.noAction, this.onDelete = ForeignKeyAction.noAction, }); diff --git a/floor_annotation/lib/src/index.dart b/floor_annotation/lib/src/index.dart index e59fd3bb..ce6b50f3 100644 --- a/floor_annotation/lib/src/index.dart +++ b/floor_annotation/lib/src/index.dart @@ -1,5 +1,3 @@ -import 'package:meta/meta.dart'; - /// Declares an index on an Entity. /// see: SQLite Index Documentation class Index { @@ -10,7 +8,7 @@ class Index { /// and with an index of {"bar", "baz"}, generated index name will be /// 'index_Foo_bar_baz'. If you need to specify the index in a query, you /// should never rely on this name, instead, specify a name for your index. - final String name; + final String? name; /// If set to true, this will be a unique index and any duplicates will be /// rejected. @@ -27,5 +25,5 @@ class Index { /// Declares an index on an Entity. /// /// Is not [unique] by default. - const Index({this.name, this.unique = false, @required this.value}); + const Index({this.name, this.unique = false, required this.value}); } diff --git a/floor_annotation/pubspec.lock b/floor_annotation/pubspec.lock index e2979061..1af6b40f 100644 --- a/floor_annotation/pubspec.lock +++ b/floor_annotation/pubspec.lock @@ -7,6 +7,6 @@ packages: name: meta url: "https://pub.dartlang.org" source: hosted - version: "1.2.4" + version: "1.3.0" sdks: - dart: ">=2.6.0 <3.0.0" + dart: ">=2.12.0 <3.0.0" diff --git a/floor_annotation/pubspec.yaml b/floor_annotation/pubspec.yaml index a84f40ae..776df515 100644 --- a/floor_annotation/pubspec.yaml +++ b/floor_annotation/pubspec.yaml @@ -2,12 +2,12 @@ name: floor_annotation description: > The typesafe, reactive, and lightweight SQLite abstraction for your Flutter applications. Don't use this package directly. Import the floor package instead. -version: 0.12.0 +version: 1.0.0-nullsafety.1 homepage: https://github.com/vitusortner/floor author: Vitus Ortner environment: - sdk: '>=2.6.0 <3.0.0' + sdk: '>=2.12.0 <3.0.0' dependencies: - meta: ^1.2.4 + meta: ^1.3.0 \ No newline at end of file diff --git a/floor_generator/CHANGELOG.md b/floor_generator/CHANGELOG.md index 54cc4604..588e1948 100644 --- a/floor_generator/CHANGELOG.md +++ b/floor_generator/CHANGELOG.md @@ -1,5 +1,17 @@ # Changelog +# 1.0.0-nullsafety.1 + +### Changes + +* Migrate to stable Dart 2.12 + +# 1.0.0-nullsafety.0 + +### 🚀 Features + +* Make floor null safe + # 0.19.1 ### Changes @@ -84,7 +96,8 @@ ### ⚠️ Breaking Changes -**You need to migrate the explicit usages of `OnConflictStrategy` and `ForeignKeyAction` from snake case to camel case.** +**You need to migrate the explicit usages of `OnConflictStrategy` and `ForeignKeyAction` from snake case to camel +case.** * Apply camel case to constants diff --git a/floor_generator/lib/builder.dart b/floor_generator/lib/builder.dart index 08ec55a1..918db7ec 100644 --- a/floor_generator/lib/builder.dart +++ b/floor_generator/lib/builder.dart @@ -1,3 +1,4 @@ +// ignore_for_file: import_of_legacy_library_into_null_safe import 'package:build/build.dart'; import 'package:floor_generator/generator.dart'; import 'package:source_gen/source_gen.dart'; diff --git a/floor_generator/lib/generator.dart b/floor_generator/lib/generator.dart index 938252e0..1175abdd 100644 --- a/floor_generator/lib/generator.dart +++ b/floor_generator/lib/generator.dart @@ -1,11 +1,11 @@ +// ignore_for_file: import_of_legacy_library_into_null_safe import 'dart:async'; import 'package:analyzer/dart/element/element.dart'; import 'package:build/build.dart'; import 'package:code_builder/code_builder.dart'; -import 'package:dartx/dartx.dart'; import 'package:floor_annotation/floor_annotation.dart' as annotations; -import 'package:floor_generator/misc/annotations.dart'; +import 'package:floor_generator/misc/extension/iterable_extension.dart'; import 'package:floor_generator/processor/database_processor.dart'; import 'package:floor_generator/value_object/database.dart'; import 'package:floor_generator/writer/dao_writer.dart'; @@ -24,7 +24,6 @@ class FloorGenerator extends GeneratorForAnnotation { final BuildStep buildStep, ) { final database = _getDatabase(element); - if (database == null) return null; final databaseClass = DatabaseWriter(database).write(); final daoClasses = database.daoGetters @@ -56,7 +55,6 @@ class FloorGenerator extends GeneratorForAnnotation { return library.accept(DartEmitter()).toString(); } - @nonNull Database _getDatabase(final Element element) { if (element is! ClassElement) { throw InvalidGenerationSourceError( @@ -64,13 +62,12 @@ class FloorGenerator extends GeneratorForAnnotation { element: element); } - final classElement = element as ClassElement; - if (!classElement.isAbstract) { + if (!element.isAbstract) { throw InvalidGenerationSourceError( 'The database class has to be abstract.', - element: classElement); + element: element); } - return DatabaseProcessor(classElement).process(); + return DatabaseProcessor(element).process(); } } diff --git a/floor_generator/lib/misc/annotation_expression.dart b/floor_generator/lib/misc/annotation_expression.dart index 5a2d09eb..36353e96 100644 --- a/floor_generator/lib/misc/annotation_expression.dart +++ b/floor_generator/lib/misc/annotation_expression.dart @@ -1,3 +1,4 @@ +// ignore_for_file: import_of_legacy_library_into_null_safe import 'package:code_builder/code_builder.dart'; /// Represents an annotation as an [Expression]. diff --git a/floor_generator/lib/misc/annotations.dart b/floor_generator/lib/misc/annotations.dart deleted file mode 100644 index a96420f0..00000000 --- a/floor_generator/lib/misc/annotations.dart +++ /dev/null @@ -1,25 +0,0 @@ -class _NonNull { - const _NonNull(); -} - -/// An element annotated with @notNull claims null value is -/// forbidden to return (for methods), pass to (parameters) and hold -/// (local variables and fields). -/// -/// Apart from documentation purposes this annotation is intended to be used by -/// static analysis tools to validate against probable runtime errors and -/// element contract violations. -const nonNull = _NonNull(); - -class _Nullable { - const _Nullable(); -} - -/// An element annotated with @nullable claims null value is -/// perfectly valid to return (for methods), pass to (parameters) and -/// hold (local variables and fields). -/// -/// Apart from documentation purposes this annotation is intended to be used by -/// static analysis tools to validate against probable runtime errors and -/// element contract violations. -const nullable = _Nullable(); diff --git a/floor_generator/lib/misc/change_method_processor_helper.dart b/floor_generator/lib/misc/change_method_processor_helper.dart index 879c9746..e4b26f38 100644 --- a/floor_generator/lib/misc/change_method_processor_helper.dart +++ b/floor_generator/lib/misc/change_method_processor_helper.dart @@ -1,6 +1,6 @@ +// ignore_for_file: import_of_legacy_library_into_null_safe import 'package:analyzer/dart/element/element.dart'; import 'package:analyzer/dart/element/type.dart'; -import 'package:floor_generator/misc/annotations.dart'; import 'package:floor_generator/misc/type_utils.dart'; import 'package:floor_generator/value_object/entity.dart'; import 'package:source_gen/source_gen.dart'; @@ -13,12 +13,9 @@ class ChangeMethodProcessorHelper { const ChangeMethodProcessorHelper( final MethodElement methodElement, final List entities, - ) : assert(methodElement != null), - assert(entities != null), - _methodElement = methodElement, + ) : _methodElement = methodElement, _entities = entities; - @nonNull ParameterElement getParameterElement() { final parameters = _methodElement.parameters; if (parameters.isEmpty) { @@ -35,9 +32,8 @@ class ChangeMethodProcessorHelper { return parameters.first; } - @nonNull DartType getFlattenedParameterType( - @nonNull final ParameterElement parameterElement, + final ParameterElement parameterElement, ) { final changesMultipleItems = parameterElement.type.isDartCoreList; @@ -46,8 +42,7 @@ class ChangeMethodProcessorHelper { : parameterElement.type; } - @nonNull - Entity getEntity(@nonNull final DartType flattenedParameterType) { + Entity getEntity(final DartType flattenedParameterType) { return _entities.firstWhere( (entity) => entity.classElement.displayName == diff --git a/floor_generator/lib/misc/change_method_writer_helper.dart b/floor_generator/lib/misc/change_method_writer_helper.dart index 4bd4c4a2..81164da4 100644 --- a/floor_generator/lib/misc/change_method_writer_helper.dart +++ b/floor_generator/lib/misc/change_method_writer_helper.dart @@ -1,14 +1,13 @@ +// ignore_for_file: import_of_legacy_library_into_null_safe import 'package:code_builder/code_builder.dart'; import 'package:floor_generator/misc/annotation_expression.dart'; -import 'package:floor_generator/misc/annotations.dart'; import 'package:floor_generator/value_object/change_method.dart'; class ChangeMethodWriterHelper { final ChangeMethod _changeMethod; ChangeMethodWriterHelper(final ChangeMethod changeMethod) - : assert(changeMethod != null), - _changeMethod = changeMethod; + : _changeMethod = changeMethod; /// Adds the change method signature to the [MethodBuilder]. void addChangeMethodSignature(final MethodBuilder methodBuilder) { @@ -25,7 +24,6 @@ class ChangeMethodWriterHelper { } } - @nonNull Parameter _generateParameter() { final parameter = _changeMethod.parameterElement; diff --git a/floor_generator/lib/misc/constants.dart b/floor_generator/lib/misc/constants.dart index 95bee72a..689c1f7e 100644 --- a/floor_generator/lib/misc/constants.dart +++ b/floor_generator/lib/misc/constants.dart @@ -8,7 +8,6 @@ abstract class AnnotationField { static const databaseViews = 'views'; static const columnInfoName = 'name'; - static const columnInfoNullable = 'nullable'; static const entityTableName = 'tableName'; static const entityForeignKeys = 'foreignKeys'; diff --git a/floor_generator/lib/misc/extension/dart_object_extension.dart b/floor_generator/lib/misc/extension/dart_object_extension.dart index 12e6f49a..aeac9881 100644 --- a/floor_generator/lib/misc/extension/dart_object_extension.dart +++ b/floor_generator/lib/misc/extension/dart_object_extension.dart @@ -1,36 +1,34 @@ import 'package:analyzer/dart/constant/value.dart'; import 'package:analyzer/dart/element/type.dart'; +import 'package:collection/collection.dart'; import 'package:floor_annotation/floor_annotation.dart'; -import 'package:meta/meta.dart'; extension DartObjectExtension on DartObject { - /// get the String representation of the enum value, or the result of - /// [orElse] if the enum was not valid. - String toEnumValueString({@required String orElse()}) { + /// get the String representation of the enum value, + /// or `null` if the enum was not valid + String? toEnumValueString() { final interfaceType = type as InterfaceType; final enumName = interfaceType.getDisplayString(withNullability: false); final enumValue = interfaceType.element.fields .where((element) => element.isEnumConstant) .map((fieldElement) => fieldElement.name) - .singleWhere((valueName) => getField(valueName) != null, - orElse: () => null); + .singleWhereOrNull((valueName) => getField(valueName) != null); if (enumValue == null) { - return orElse(); + return null; } else { return '$enumName.$enumValue'; } } - /// get the ForeignKeyAction this enum represents, or the result of - /// [orElse] if the enum did not contain a valid value. - ForeignKeyAction toForeignKeyAction({@required ForeignKeyAction orElse()}) { - final enumValueString = toEnumValueString(orElse: () => null); + /// get the ForeignKeyAction this enum represents, + /// or the result of `null` if the enum did not contain a valid value + ForeignKeyAction? toForeignKeyAction() { + final enumValueString = toEnumValueString(); if (enumValueString == null) { - return orElse(); + return null; } else { - return ForeignKeyAction.values.singleWhere( - (foreignKeyAction) => foreignKeyAction.toString() == enumValueString, - orElse: orElse); + return ForeignKeyAction.values.singleWhereOrNull( + (foreignKeyAction) => foreignKeyAction.toString() == enumValueString); } } } diff --git a/floor_generator/lib/misc/extension/dart_type_extension.dart b/floor_generator/lib/misc/extension/dart_type_extension.dart new file mode 100644 index 00000000..0b83f829 --- /dev/null +++ b/floor_generator/lib/misc/extension/dart_type_extension.dart @@ -0,0 +1,15 @@ +import 'package:analyzer/dart/element/nullability_suffix.dart'; +import 'package:analyzer/dart/element/type.dart'; + +extension DartTypeExtension on DartType { + /// Whether this [DartType] is nullable + bool get isNullable { + switch (nullabilitySuffix) { + case NullabilitySuffix.question: + case NullabilitySuffix.star: // support legacy code without non-nullables + return true; + case NullabilitySuffix.none: + return false; + } + } +} diff --git a/floor_generator/lib/misc/extension/foreign_key_action_extension.dart b/floor_generator/lib/misc/extension/foreign_key_action_extension.dart index 5351c8c0..7daba4ea 100644 --- a/floor_generator/lib/misc/extension/foreign_key_action_extension.dart +++ b/floor_generator/lib/misc/extension/foreign_key_action_extension.dart @@ -1,8 +1,6 @@ import 'package:floor_annotation/floor_annotation.dart'; -import 'package:floor_generator/misc/annotations.dart'; extension ForeignKeyActionExtension on ForeignKeyAction { - @nonNull String toSql() { switch (this) { case ForeignKeyAction.noAction: @@ -15,9 +13,6 @@ extension ForeignKeyActionExtension on ForeignKeyAction { return 'SET DEFAULT'; case ForeignKeyAction.cascade: return 'CASCADE'; - default: // can only match null - throw ArgumentError('toSql() should not be called on a null value. ' - 'This is a bug in floor.'); } } } diff --git a/floor_generator/lib/misc/extension/iterable_extension.dart b/floor_generator/lib/misc/extension/iterable_extension.dart new file mode 100644 index 00000000..7a00c90d --- /dev/null +++ b/floor_generator/lib/misc/extension/iterable_extension.dart @@ -0,0 +1,26 @@ +import 'dart:collection'; + +extension IterableExtension on Iterable { + Iterable sortedByDescending(Comparable Function(T element) selector) { + return toList()..sort((b, a) => selector(a).compareTo(selector(b))); + } + + Iterable mapNotNull(R? Function(T element) transform) sync* { + for (final element in this) { + final transformed = transform(element); + if (transformed != null) yield transformed; + } + } + + /// Returns a new lazy [Iterable] containing only elements from the collection + /// having distinct keys returned by the given [selector] function. + /// + /// The elements in the resulting list are in the same order as they were in + /// the source collection. + Iterable distinctBy(R Function(T element) selector) sync* { + final existing = HashSet(); + for (final current in this) { + if (existing.add(selector(current))) yield current; + } + } +} diff --git a/floor_generator/lib/misc/extension/list_equality_extension.dart b/floor_generator/lib/misc/extension/list_equality_extension.dart deleted file mode 100644 index bab35d05..00000000 --- a/floor_generator/lib/misc/extension/list_equality_extension.dart +++ /dev/null @@ -1,5 +0,0 @@ -import 'package:collection/collection.dart'; - -extension ListEqualityExtension on List { - bool equals(List other) => ListEquality().equals(this, other); -} diff --git a/floor_generator/lib/misc/extension/string_extension.dart b/floor_generator/lib/misc/extension/string_extension.dart new file mode 100644 index 00000000..19bde368 --- /dev/null +++ b/floor_generator/lib/misc/extension/string_extension.dart @@ -0,0 +1,19 @@ +extension StringExtension on String { + /// Returns a copy of this string having its first letter lowercased, or the + /// original string, if it's empty or already starts with a lower case letter. + /// + /// ```dart + /// print('abcd'.decapitalize()) // abcd + /// print('Abcd'.decapitalize()) // abcd + /// ``` + String decapitalize() { + switch (length) { + case 0: + return this; + case 1: + return toLowerCase(); + default: + return this[0].toLowerCase() + substring(1); + } + } +} diff --git a/floor_generator/lib/misc/extension/type_converter_element_extension.dart b/floor_generator/lib/misc/extension/type_converter_element_extension.dart index 7ca0ed51..66600e11 100644 --- a/floor_generator/lib/misc/extension/type_converter_element_extension.dart +++ b/floor_generator/lib/misc/extension/type_converter_element_extension.dart @@ -1,28 +1,29 @@ import 'package:analyzer/dart/element/element.dart'; import 'package:floor_annotation/floor_annotation.dart' as annotations; -import 'package:floor_generator/misc/annotations.dart'; import 'package:floor_generator/misc/constants.dart'; +import 'package:floor_generator/misc/extension/iterable_extension.dart'; import 'package:floor_generator/misc/type_utils.dart'; +import 'package:floor_generator/processor/error/processor_error.dart'; import 'package:floor_generator/processor/type_converter_processor.dart'; import 'package:floor_generator/value_object/type_converter.dart'; -import 'package:source_gen/source_gen.dart'; extension TypeConverterElementExtension on Element { /// Returns a set of [TypeConverter]s found in the @TypeConverters /// annotation on this element - @nonNull Set getTypeConverters(final TypeConverterScope scope) { if (hasAnnotation(annotations.TypeConverters)) { final typeConverterElements = getAnnotation(annotations.TypeConverters) .getField(AnnotationField.typeConverterValue) ?.toListValue() - ?.map((object) => object.toTypeValue().element); + ?.mapNotNull((object) => object.toTypeValue()?.element); if (typeConverterElements == null || typeConverterElements.isEmpty) { - throw InvalidGenerationSourceError( - 'There are no type converts defined even though the @TypeConverters annotation is used.', - todo: 'Supply a type converter class to the annotation.', - element: this); + throw ProcessorError( + message: + 'There are no type converts defined even though the @TypeConverters annotation is used.', + todo: 'Supply a type converter class to the annotation.', + element: this, + ); } final typeConverterClassElements = @@ -30,8 +31,9 @@ extension TypeConverterElementExtension on Element { if (typeConverterClassElements .any((element) => !element.isTypeConverter)) { - throw InvalidGenerationSourceError( - 'Only classes that inherit from TypeConverter can be used as type converters.', + throw ProcessorError( + message: + 'Only classes that inherit from TypeConverter can be used as type converters.', todo: 'Make sure use a class that inherits from TypeConverter.', element: this, ); @@ -47,5 +49,5 @@ extension TypeConverterElementExtension on Element { } extension on ClassElement { - bool get isTypeConverter => supertype.element.displayName == 'TypeConverter'; + bool get isTypeConverter => supertype?.element.displayName == 'TypeConverter'; } diff --git a/floor_generator/lib/misc/extension/type_converters_extension.dart b/floor_generator/lib/misc/extension/type_converters_extension.dart index d608c3cc..d50390b3 100644 --- a/floor_generator/lib/misc/extension/type_converters_extension.dart +++ b/floor_generator/lib/misc/extension/type_converters_extension.dart @@ -1,29 +1,27 @@ +// ignore_for_file: import_of_legacy_library_into_null_safe import 'package:analyzer/dart/element/type.dart'; -import 'package:dartx/dartx.dart'; -import 'package:floor_generator/misc/annotations.dart'; +import 'package:collection/collection.dart'; +import 'package:floor_generator/misc/extension/iterable_extension.dart'; import 'package:floor_generator/value_object/type_converter.dart'; import 'package:source_gen/source_gen.dart'; extension TypeConvertersExtension on Iterable { /// Returns the [TypeConverter] in the closest [TypeConverterScope] or null - @nullable - TypeConverter get closestOrNull { + TypeConverter? get closestOrNull { return sortedByDescending((typeConverter) => typeConverter.scope.index) .firstOrNull; } /// Returns the [TypeConverter] in the closest [TypeConverterScope] for /// [dartType] or null - @nullable - TypeConverter getClosestOrNull(DartType dartType) { + TypeConverter? getClosestOrNull(DartType dartType) { return sortedByDescending((typeConverter) => typeConverter.scope.index) - .firstOrNullWhere( + .firstWhereOrNull( (typeConverter) => typeConverter.fieldType == dartType); } /// Returns the [TypeConverter] in the closest [TypeConverterScope] for /// [dartType] - @nonNull TypeConverter getClosest(DartType dartType) { final closest = getClosestOrNull(dartType); if (closest == null) { diff --git a/floor_generator/lib/misc/string_utils.dart b/floor_generator/lib/misc/string_utils.dart deleted file mode 100644 index 99e07a89..00000000 --- a/floor_generator/lib/misc/string_utils.dart +++ /dev/null @@ -1,6 +0,0 @@ -extension StringUtils on String { - /// Makes the first letter of the supplied string [value] lowercase. - String decapitalize() { - return '${this[0].toLowerCase()}${substring(1)}'; - } -} diff --git a/floor_generator/lib/misc/type_utils.dart b/floor_generator/lib/misc/type_utils.dart index bc5cec56..815dc454 100644 --- a/floor_generator/lib/misc/type_utils.dart +++ b/floor_generator/lib/misc/type_utils.dart @@ -1,9 +1,9 @@ +// ignore_for_file: import_of_legacy_library_into_null_safe import 'dart:typed_data'; import 'package:analyzer/dart/constant/value.dart'; import 'package:analyzer/dart/element/element.dart'; import 'package:analyzer/dart/element/type.dart'; -import 'package:floor_generator/misc/annotations.dart'; import 'package:source_gen/source_gen.dart'; extension SupportedTypeChecker on DartType { @@ -13,7 +13,6 @@ extension SupportedTypeChecker on DartType { /// - int /// - double /// - Uint8List - @nonNull bool get isDefaultSqlType { return TypeChecker.any([ _stringTypeChecker, @@ -26,37 +25,31 @@ extension SupportedTypeChecker on DartType { } extension Uint8ListTypeChecker on DartType { - @nonNull bool get isUint8List => getDisplayString(withNullability: false) == 'Uint8List'; } extension StreamTypeChecker on DartType { - @nonNull bool get isStream => _streamTypeChecker.isExactlyType(this); } extension FlattenUtil on DartType { - @nonNull DartType flatten() { return (this as ParameterizedType).typeArguments.first; } } extension AnnotationChecker on Element { - @nonNull bool hasAnnotation(final Type type) { return _typeChecker(type).hasAnnotationOfExact(this); } /// Returns the first annotation object found on [type] - @nonNull DartObject getAnnotation(final Type type) { return _typeChecker(type).firstAnnotationOfExact(this); } } -@nonNull TypeChecker _typeChecker(final Type type) => TypeChecker.fromRuntime(type); final _stringTypeChecker = _typeChecker(String); diff --git a/floor_generator/lib/processor/dao_processor.dart b/floor_generator/lib/processor/dao_processor.dart index 175ccf70..27f62acb 100644 --- a/floor_generator/lib/processor/dao_processor.dart +++ b/floor_generator/lib/processor/dao_processor.dart @@ -34,13 +34,7 @@ class DaoProcessor extends Processor { final List entities, final List views, final Set typeConverters, - ) : assert(classElement != null), - assert(daoGetterName != null), - assert(databaseName != null), - assert(entities != null), - assert(views != null), - assert(typeConverters != null), - _classElement = classElement, + ) : _classElement = classElement, _daoGetterName = daoGetterName, _databaseName = databaseName, _entities = entities, diff --git a/floor_generator/lib/processor/database_processor.dart b/floor_generator/lib/processor/database_processor.dart index 3043fc5d..aea8e4ad 100644 --- a/floor_generator/lib/processor/database_processor.dart +++ b/floor_generator/lib/processor/database_processor.dart @@ -1,8 +1,7 @@ import 'package:analyzer/dart/element/element.dart'; -import 'package:dartx/dartx.dart'; import 'package:floor_annotation/floor_annotation.dart' as annotations; -import 'package:floor_generator/misc/annotations.dart'; import 'package:floor_generator/misc/constants.dart'; +import 'package:floor_generator/misc/extension/iterable_extension.dart'; import 'package:floor_generator/misc/extension/set_extension.dart'; import 'package:floor_generator/misc/extension/type_converter_element_extension.dart'; import 'package:floor_generator/misc/type_utils.dart'; @@ -24,11 +23,9 @@ class DatabaseProcessor extends Processor { final ClassElement _classElement; DatabaseProcessor(final ClassElement classElement) - : assert(classElement != null), - _classElement = classElement, + : _classElement = classElement, _processorError = DatabaseProcessorError(classElement); - @nonNull @override Database process() { final databaseName = _classElement.displayName; @@ -60,7 +57,6 @@ class DatabaseProcessor extends Processor { ); } - @nonNull int _getDatabaseVersion() { final version = _classElement .getAnnotation(annotations.Database) @@ -73,7 +69,6 @@ class DatabaseProcessor extends Processor { return version; } - @nonNull List _getDaoGetters( final String databaseName, final List entities, @@ -97,19 +92,16 @@ class DatabaseProcessor extends Processor { }).toList(); } - @nonNull bool _isDao(final FieldElement fieldElement) { final element = fieldElement.type.element; return element is ClassElement ? _isDaoClass(element) : false; } - @nonNull bool _isDaoClass(final ClassElement classElement) { return classElement.hasAnnotation(annotations.dao.runtimeType) && classElement.isAbstract; } - @nonNull List _getEntities( final ClassElement databaseClassElement, final Set typeConverters, @@ -118,14 +110,14 @@ class DatabaseProcessor extends Processor { .getAnnotation(annotations.Database) .getField(AnnotationField.databaseEntities) ?.toListValue() - ?.map((object) => object.toTypeValue().element) - ?.whereType() - ?.where(_isEntity) - ?.map((classElement) => EntityProcessor( + ?.mapNotNull((object) => object.toTypeValue()?.element) + .whereType() + .where(_isEntity) + .map((classElement) => EntityProcessor( classElement, typeConverters, ).process()) - ?.toList(); + .toList(); if (entities == null || entities.isEmpty) { throw _processorError.noEntitiesDefined; @@ -134,7 +126,6 @@ class DatabaseProcessor extends Processor { return entities; } - @nonNull List _getViews( final ClassElement databaseClassElement, final Set typeConverters, @@ -143,18 +134,17 @@ class DatabaseProcessor extends Processor { .getAnnotation(annotations.Database) .getField(AnnotationField.databaseViews) ?.toListValue() - ?.map((object) => object.toTypeValue().element) - ?.whereType() - ?.where(_isView) - ?.map((classElement) => ViewProcessor( + ?.mapNotNull((object) => object.toTypeValue()?.element) + .whereType() + .where(_isView) + .map((classElement) => ViewProcessor( classElement, typeConverters, ).process()) - ?.toList() ?? + .toList() ?? []; } - @nonNull Set _getAllTypeConverters( final List daoGetters, final List queryables, @@ -179,13 +169,11 @@ class DatabaseProcessor extends Processor { fieldTypeConverters; } - @nonNull bool _isEntity(final ClassElement classElement) { return classElement.hasAnnotation(annotations.Entity) && !classElement.isAbstract; } - @nonNull bool _isView(final ClassElement classElement) { return classElement.hasAnnotation(annotations.DatabaseView) && !classElement.isAbstract; diff --git a/floor_generator/lib/processor/deletion_method_processor.dart b/floor_generator/lib/processor/deletion_method_processor.dart index b6f4b851..86cf6f2b 100644 --- a/floor_generator/lib/processor/deletion_method_processor.dart +++ b/floor_generator/lib/processor/deletion_method_processor.dart @@ -1,6 +1,6 @@ +// ignore_for_file: import_of_legacy_library_into_null_safe import 'package:analyzer/dart/element/element.dart'; import 'package:analyzer/dart/element/type.dart'; -import 'package:floor_generator/misc/annotations.dart'; import 'package:floor_generator/misc/change_method_processor_helper.dart'; import 'package:floor_generator/processor/processor.dart'; import 'package:floor_generator/value_object/deletion_method.dart'; @@ -14,14 +14,11 @@ class DeletionMethodProcessor implements Processor { DeletionMethodProcessor( final MethodElement methodElement, final List entities, [ - final ChangeMethodProcessorHelper changeMethodProcessorHelper, - ]) : assert(methodElement != null), - assert(entities != null), - _methodElement = methodElement, + final ChangeMethodProcessorHelper? changeMethodProcessorHelper, + ]) : _methodElement = methodElement, _helper = changeMethodProcessorHelper ?? ChangeMethodProcessorHelper(methodElement, entities); - @nonNull @override DeletionMethod process() { final name = _methodElement.name; @@ -58,7 +55,6 @@ class DeletionMethodProcessor implements Processor { ); } - @nonNull DartType _getFlattenedReturnType(final DartType returnType) { return _methodElement.library.typeSystem.flatten(returnType); } diff --git a/floor_generator/lib/processor/entity_processor.dart b/floor_generator/lib/processor/entity_processor.dart index f41cee99..b7c54028 100644 --- a/floor_generator/lib/processor/entity_processor.dart +++ b/floor_generator/lib/processor/entity_processor.dart @@ -1,10 +1,11 @@ import 'package:analyzer/dart/constant/value.dart'; import 'package:analyzer/dart/element/element.dart'; -import 'package:dartx/dartx.dart'; +import 'package:collection/collection.dart'; import 'package:floor_annotation/floor_annotation.dart' as annotations; -import 'package:floor_generator/misc/annotations.dart'; import 'package:floor_generator/misc/constants.dart'; import 'package:floor_generator/misc/extension/dart_object_extension.dart'; +import 'package:floor_generator/misc/extension/iterable_extension.dart'; +import 'package:floor_generator/misc/extension/string_extension.dart'; import 'package:floor_generator/misc/extension/type_converters_extension.dart'; import 'package:floor_generator/misc/type_utils.dart'; import 'package:floor_generator/processor/error/entity_processor_error.dart'; @@ -26,7 +27,6 @@ class EntityProcessor extends QueryableProcessor { ) : _processorError = EntityProcessorError(classElement), super(classElement, typeConverters); - @nonNull @override Entity process() { final name = _getName(); @@ -52,16 +52,14 @@ class EntityProcessor extends QueryableProcessor { ); } - @nonNull String _getName() { return classElement .getAnnotation(annotations.Entity) .getField(AnnotationField.entityTableName) - .toStringValue() ?? + ?.toStringValue() ?? classElement.displayName; } - @nonNull List _getForeignKeys() { return classElement .getAnnotation(annotations.Entity) @@ -107,12 +105,11 @@ class EntityProcessor extends QueryableProcessor { onUpdate, onDelete, ); - })?.toList() ?? + }).toList() ?? []; } - @nullable - Fts _getFts() { + Fts? _getFts() { if (classElement.hasAnnotation(annotations.Fts3)) { return _getFts3(); } else if (classElement.hasAnnotation(annotations.Fts4)) { @@ -126,14 +123,14 @@ class EntityProcessor extends QueryableProcessor { final ftsObject = classElement.getAnnotation(annotations.Fts3); final tokenizer = - ftsObject?.getField(Fts3Field.tokenizer)?.toStringValue() ?? + ftsObject.getField(Fts3Field.tokenizer)?.toStringValue() ?? annotations.FtsTokenizer.simple; final tokenizerArgs = ftsObject .getField(Fts3Field.tokenizerArgs) ?.toListValue() - ?.map((object) => object.toStringValue()) - ?.toList() ?? + ?.mapNotNull((object) => object.toStringValue()) + .toList() ?? []; return Fts3(tokenizer, tokenizerArgs); @@ -143,20 +140,19 @@ class EntityProcessor extends QueryableProcessor { final ftsObject = classElement.getAnnotation(annotations.Fts4); final tokenizer = - ftsObject?.getField(Fts4Field.tokenizer)?.toStringValue() ?? + ftsObject.getField(Fts4Field.tokenizer)?.toStringValue() ?? annotations.FtsTokenizer.simple; final tokenizerArgs = ftsObject .getField(Fts4Field.tokenizerArgs) ?.toListValue() - ?.map((object) => object.toStringValue()) - ?.toList() ?? + ?.mapNotNull((object) => object.toStringValue()) + .toList() ?? []; return Fts4(tokenizer, tokenizerArgs); } - @nonNull List _getIndices(final List fields, final String tableName) { return classElement .getAnnotation(annotations.Entity) @@ -164,12 +160,14 @@ class EntityProcessor extends QueryableProcessor { ?.toListValue() ?.map((indexObject) { final unique = indexObject.getField(IndexField.unique)?.toBoolValue(); + // can't happen as Index.unique is non-nullable + if (unique == null) throw ArgumentError.notNull(); final values = indexObject .getField(IndexField.value) ?.toListValue() - ?.map((valueObject) => valueObject.toStringValue()) - ?.toList(); + ?.mapNotNull((valueObject) => valueObject.toStringValue()) + .toList(); if (values == null || values.isEmpty) { throw _processorError.missingIndexColumnName; @@ -188,11 +186,10 @@ class EntityProcessor extends QueryableProcessor { _generateIndexName(tableName, indexColumnNames); return Index(name, tableName, unique, indexColumnNames); - })?.toList() ?? + }).toList() ?? []; } - @nonNull String _generateIndexName( final String tableName, final List columnNames, @@ -200,7 +197,6 @@ class EntityProcessor extends QueryableProcessor { return Index.defaultPrefix + tableName + '_' + columnNames.join('_'); } - @nonNull List _getColumns( final DartObject object, final String foreignKeyField, @@ -208,12 +204,11 @@ class EntityProcessor extends QueryableProcessor { return object .getField(foreignKeyField) ?.toListValue() - ?.map((object) => object.toStringValue()) - ?.toList() ?? + ?.mapNotNull((object) => object.toStringValue()) + .toList() ?? []; } - @nonNull PrimaryKey _getPrimaryKey(final List fields) { final compoundPrimaryKey = _getCompoundPrimaryKey(fields); @@ -224,8 +219,7 @@ class EntityProcessor extends QueryableProcessor { } } - @nullable - PrimaryKey _getCompoundPrimaryKey(final List fields) { + PrimaryKey? _getCompoundPrimaryKey(final List fields) { final compoundPrimaryKeyColumnNames = classElement .getAnnotation(annotations.Entity) .getField(AnnotationField.entityPrimaryKeys) @@ -249,7 +243,6 @@ class EntityProcessor extends QueryableProcessor { return PrimaryKey(compoundPrimaryKeyFields, false); } - @nonNull PrimaryKey _getPrimaryKeyFromAnnotation(final List fields) { final primaryKeyField = fields.firstWhere( (field) => field.fieldElement.hasAnnotation(annotations.PrimaryKey), @@ -264,16 +257,14 @@ class EntityProcessor extends QueryableProcessor { return PrimaryKey([primaryKeyField], autoGenerate); } - @nonNull bool _getWithoutRowid() { return classElement .getAnnotation(annotations.Entity) .getField(AnnotationField.entityWithoutRowid) - .toBoolValue() ?? + ?.toBoolValue() ?? false; } - @nonNull String _getValueMapping(final List fields) { final keyValueList = fields.map((field) { final columnName = field.columnName; @@ -281,10 +272,9 @@ class EntityProcessor extends QueryableProcessor { return "'$columnName': $attributeValue"; }).toList(); - return '{${keyValueList.join(', ')}}'; + return '{${keyValueList.join(', ')}}'; } - @nonNull String _getAttributeValue(final Field field) { final fieldElement = field.fieldElement; final parameterName = fieldElement.displayName; @@ -295,16 +285,18 @@ class EntityProcessor extends QueryableProcessor { if (fieldType.isDefaultSqlType) { attributeValue = 'item.$parameterName'; } else { - final typeConverter = [...queryableTypeConverters, field.typeConverter] - .filterNotNull() - .getClosest(fieldType); + final typeConverter = [ + ...queryableTypeConverters, + field.typeConverter, + ].whereNotNull().getClosest(fieldType); attributeValue = '_${typeConverter.name.decapitalize()}.encode(item.$parameterName)'; } if (fieldType.isDartCoreBool) { if (field.isNullable) { - return '$attributeValue == null ? null : ($attributeValue ? 1 : 0)'; + // force! underlying non-nullable type as null check has been done + return '$attributeValue == null ? null : ($attributeValue! ? 1 : 0)'; } else { return '$attributeValue ? 1 : 0'; } @@ -313,17 +305,21 @@ class EntityProcessor extends QueryableProcessor { } } - @nonNull annotations.ForeignKeyAction _getForeignKeyAction( - DartObject foreignKeyObject, String triggerName) { + DartObject foreignKeyObject, + String triggerName, + ) { final field = foreignKeyObject.getField(triggerName); if (field == null) { // field was not defined, return default value return annotations.ForeignKeyAction.noAction; } - return field.toForeignKeyAction( - orElse: () => - throw _processorError.wrongForeignKeyAction(field, triggerName)); + final foreignKeyAction = field.toForeignKeyAction(); + if (foreignKeyAction == null) { + throw _processorError.wrongForeignKeyAction(field, triggerName); + } else { + return foreignKeyAction; + } } } diff --git a/floor_generator/lib/processor/error/database_processor_error.dart b/floor_generator/lib/processor/error/database_processor_error.dart index 43358f0e..f9a9625d 100644 --- a/floor_generator/lib/processor/error/database_processor_error.dart +++ b/floor_generator/lib/processor/error/database_processor_error.dart @@ -1,3 +1,4 @@ +// ignore_for_file: import_of_legacy_library_into_null_safe import 'package:analyzer/dart/element/element.dart'; import 'package:source_gen/source_gen.dart'; @@ -5,8 +6,7 @@ class DatabaseProcessorError { final ClassElement _classElement; DatabaseProcessorError(final ClassElement classElement) - : assert(classElement != null), - _classElement = classElement; + : _classElement = classElement; InvalidGenerationSourceError get versionIsMissing { return InvalidGenerationSourceError( diff --git a/floor_generator/lib/processor/error/entity_processor_error.dart b/floor_generator/lib/processor/error/entity_processor_error.dart index 58098520..76a50d0a 100644 --- a/floor_generator/lib/processor/error/entity_processor_error.dart +++ b/floor_generator/lib/processor/error/entity_processor_error.dart @@ -1,3 +1,4 @@ +// ignore_for_file: import_of_legacy_library_into_null_safe import 'package:analyzer/dart/constant/value.dart'; import 'package:analyzer/dart/element/element.dart'; import 'package:source_gen/source_gen.dart'; @@ -6,8 +7,7 @@ class EntityProcessorError { final ClassElement _classElement; EntityProcessorError(final ClassElement classElement) - : assert(classElement != null), - _classElement = classElement; + : _classElement = classElement; InvalidGenerationSourceError get missingPrimaryKey { return InvalidGenerationSourceError( diff --git a/floor_generator/lib/processor/error/processor_error.dart b/floor_generator/lib/processor/error/processor_error.dart new file mode 100644 index 00000000..66de041f --- /dev/null +++ b/floor_generator/lib/processor/error/processor_error.dart @@ -0,0 +1,39 @@ +// ignore_for_file: import_of_legacy_library_into_null_safe +import 'package:analyzer/dart/element/element.dart'; +import 'package:source_gen/source_gen.dart'; + +class ProcessorError extends Error { + /// Description of the error. + final String message; + + /// What could have been changed in the source code to resolve this error. + final String todo; + + /// The code element associated with this error. + final Element element; + + ProcessorError({ + required this.message, + required this.todo, + required this.element, + }); + + @override + String toString() { + final buffer = StringBuffer('$message $todo'); + + try { + final span = spanForElement(element); + buffer + ..writeln() + ..writeln(span.start.toolString) + ..write(span.highlight()); + } catch (_) { + // Source for `element` wasn't found, it must be in a summary with no + // associated source. We can still give the name. + buffer..writeln()..writeln('Cause: $element'); + } + + return buffer.toString(); + } +} diff --git a/floor_generator/lib/processor/error/query_method_processor_error.dart b/floor_generator/lib/processor/error/query_method_processor_error.dart index 30680a22..2d587dfa 100644 --- a/floor_generator/lib/processor/error/query_method_processor_error.dart +++ b/floor_generator/lib/processor/error/query_method_processor_error.dart @@ -1,12 +1,13 @@ +// ignore_for_file: import_of_legacy_library_into_null_safe import 'package:analyzer/dart/element/element.dart'; +import 'package:floor_generator/processor/error/processor_error.dart'; import 'package:source_gen/source_gen.dart'; class QueryMethodProcessorError { final MethodElement _methodElement; QueryMethodProcessorError(final MethodElement methodElement) - : assert(methodElement != null), - _methodElement = methodElement; + : _methodElement = methodElement; InvalidGenerationSourceError get noQueryDefined { return InvalidGenerationSourceError( @@ -31,4 +32,34 @@ class QueryMethodProcessorError { element: _methodElement, ); } + + ProcessorError queryMethodParameterIsNullable( + final ParameterElement parameterElement, + ) { + return ProcessorError( + message: 'Query method parameters have to be non-nullable.', + todo: 'Define ${parameterElement.displayName} as non-nullable.' + '\nIf you want to assert null, change your query to use the `IS NULL`/' + '`IS NOT NULL` operator without passing a nullable parameter.', + element: _methodElement, + ); + } + + ProcessorError get doesNotReturnNullableStream { + return ProcessorError( + message: 'Queries returning streams of single elements might emit null.', + todo: + 'Make the method return a Stream of a nullable type e.g. Stream.', + element: _methodElement, + ); + } + + ProcessorError get doesNotReturnNullableFuture { + return ProcessorError( + message: 'Queries returning single elements might return null.', + todo: + 'Make the method return a Future of a nullable type e.g. Future.', + element: _methodElement, + ); + } } diff --git a/floor_generator/lib/processor/error/queryable_processor_error.dart b/floor_generator/lib/processor/error/queryable_processor_error.dart index f3acc806..ac7769a1 100644 --- a/floor_generator/lib/processor/error/queryable_processor_error.dart +++ b/floor_generator/lib/processor/error/queryable_processor_error.dart @@ -1,3 +1,4 @@ +// ignore_for_file: import_of_legacy_library_into_null_safe import 'package:analyzer/dart/element/element.dart'; import 'package:source_gen/source_gen.dart'; @@ -5,8 +6,7 @@ class QueryableProcessorError { final ClassElement _classElement; QueryableProcessorError(final ClassElement classElement) - : assert(classElement != null), - _classElement = classElement; + : _classElement = classElement; InvalidGenerationSourceError get prohibitedMixinUsage { return InvalidGenerationSourceError( diff --git a/floor_generator/lib/processor/error/transaction_method_processor_error.dart b/floor_generator/lib/processor/error/transaction_method_processor_error.dart index 5dbb9ac5..54ad2243 100644 --- a/floor_generator/lib/processor/error/transaction_method_processor_error.dart +++ b/floor_generator/lib/processor/error/transaction_method_processor_error.dart @@ -1,11 +1,11 @@ +// ignore_for_file: import_of_legacy_library_into_null_safe import 'package:analyzer/dart/element/element.dart'; import 'package:source_gen/source_gen.dart'; class TransactionMethodProcessorError { final MethodElement _methodElement; - TransactionMethodProcessorError(final this._methodElement) - : assert(_methodElement != null); + TransactionMethodProcessorError(final this._methodElement); InvalidGenerationSourceError get shouldReturnFuture { return InvalidGenerationSourceError( diff --git a/floor_generator/lib/processor/error/view_processor_error.dart b/floor_generator/lib/processor/error/view_processor_error.dart index b6f36d5c..b3d02335 100644 --- a/floor_generator/lib/processor/error/view_processor_error.dart +++ b/floor_generator/lib/processor/error/view_processor_error.dart @@ -1,3 +1,4 @@ +// ignore_for_file: import_of_legacy_library_into_null_safe import 'package:analyzer/dart/element/element.dart'; import 'package:source_gen/source_gen.dart'; @@ -5,8 +6,7 @@ class ViewProcessorError { final ClassElement _classElement; ViewProcessorError(final ClassElement classElement) - : assert(classElement != null), - _classElement = classElement; + : _classElement = classElement; InvalidGenerationSourceError get missingQuery { return InvalidGenerationSourceError( diff --git a/floor_generator/lib/processor/field_processor.dart b/floor_generator/lib/processor/field_processor.dart index 62672e70..548f30f1 100644 --- a/floor_generator/lib/processor/field_processor.dart +++ b/floor_generator/lib/processor/field_processor.dart @@ -1,9 +1,10 @@ +// ignore_for_file: import_of_legacy_library_into_null_safe import 'package:analyzer/dart/element/element.dart'; import 'package:analyzer/dart/element/type.dart'; -import 'package:dartx/dartx.dart'; +import 'package:collection/collection.dart'; import 'package:floor_annotation/floor_annotation.dart' as annotations; -import 'package:floor_generator/misc/annotations.dart'; import 'package:floor_generator/misc/constants.dart'; +import 'package:floor_generator/misc/extension/dart_type_extension.dart'; import 'package:floor_generator/misc/extension/type_converter_element_extension.dart'; import 'package:floor_generator/misc/extension/type_converters_extension.dart'; import 'package:floor_generator/misc/type_utils.dart'; @@ -13,30 +14,24 @@ import 'package:floor_generator/value_object/type_converter.dart'; import 'package:source_gen/source_gen.dart'; class FieldProcessor extends Processor { - @nonNull final FieldElement _fieldElement; - @nullable - final TypeConverter _typeConverter; + final TypeConverter? _typeConverter; FieldProcessor( - @nonNull final FieldElement fieldElement, - @nullable final TypeConverter typeConverter, - ) : assert(fieldElement != null), - _fieldElement = fieldElement, + final FieldElement fieldElement, + final TypeConverter? typeConverter, + ) : _fieldElement = fieldElement, _typeConverter = typeConverter; - @nonNull @override Field process() { final name = _fieldElement.name; - final hasColumnInfoAnnotation = - _fieldElement.hasAnnotation(annotations.ColumnInfo); - final columnName = _getColumnName(hasColumnInfoAnnotation, name); - final isNullable = _getIsNullable(hasColumnInfoAnnotation); + final columnName = _getColumnName(name); + final isNullable = _fieldElement.type.isNullable; final typeConverter = { ..._fieldElement.getTypeConverters(TypeConverterScope.field), _typeConverter - }.filterNotNull().closestOrNull; + }.whereNotNull().closestOrNull; return Field( _fieldElement, @@ -48,9 +43,8 @@ class FieldProcessor extends Processor { ); } - @nonNull - String _getColumnName(bool hasColumnInfoAnnotation, String name) { - return hasColumnInfoAnnotation + String _getColumnName(final String name) { + return _fieldElement.hasAnnotation(annotations.ColumnInfo) ? _fieldElement .getAnnotation(annotations.ColumnInfo) .getField(AnnotationField.columnInfoName) @@ -59,19 +53,7 @@ class FieldProcessor extends Processor { : name; } - @nonNull - bool _getIsNullable(bool hasColumnInfoAnnotation) { - return hasColumnInfoAnnotation - ? _fieldElement - .getAnnotation(annotations.ColumnInfo) - .getField(AnnotationField.columnInfoNullable) - ?.toBoolValue() ?? - true - : true; // all Dart fields are nullable by default - } - - @nonNull - String _getSqlType(@nullable final TypeConverter typeConverter) { + String _getSqlType(final TypeConverter? typeConverter) { final type = _fieldElement.type; if (type.isDefaultSqlType) { return type.asSqlType(); diff --git a/floor_generator/lib/processor/insertion_method_processor.dart b/floor_generator/lib/processor/insertion_method_processor.dart index 873975ec..844dc435 100644 --- a/floor_generator/lib/processor/insertion_method_processor.dart +++ b/floor_generator/lib/processor/insertion_method_processor.dart @@ -1,8 +1,8 @@ +// ignore_for_file: import_of_legacy_library_into_null_safe import 'package:analyzer/dart/element/element.dart'; import 'package:analyzer/dart/element/type.dart'; import 'package:floor_annotation/floor_annotation.dart' as annotations show Insert, OnConflictStrategy; -import 'package:floor_generator/misc/annotations.dart'; import 'package:floor_generator/misc/change_method_processor_helper.dart'; import 'package:floor_generator/misc/constants.dart'; import 'package:floor_generator/misc/extension/dart_object_extension.dart'; @@ -19,14 +19,11 @@ class InsertionMethodProcessor implements Processor { InsertionMethodProcessor( final MethodElement methodElement, final List entities, [ - final ChangeMethodProcessorHelper changeMethodProcessorHelper, - ]) : assert(methodElement != null), - assert(entities != null), - _methodElement = methodElement, + final ChangeMethodProcessorHelper? changeMethodProcessorHelper, + ]) : _methodElement = methodElement, _helper = changeMethodProcessorHelper ?? ChangeMethodProcessorHelper(methodElement, entities); - @nonNull @override InsertionMethod process() { final name = _methodElement.name; @@ -67,13 +64,11 @@ class InsertionMethodProcessor implements Processor { ); } - @nonNull bool _getReturnsList(final DartType returnType) { final type = _methodElement.library.typeSystem.flatten(returnType); return type.isDartCoreList; } - @nonNull DartType _getFlattenedReturnType( final DartType returnType, final bool returnsList, @@ -82,16 +77,20 @@ class InsertionMethodProcessor implements Processor { return returnsList ? type.flatten() : type; } - @nonNull String _getOnConflictStrategy() { - return _methodElement + final onConflictStrategy = _methodElement .getAnnotation(annotations.Insert) .getField(AnnotationField.onConflict) - .toEnumValueString( - orElse: () => throw InvalidGenerationSourceError( - 'Value of ${AnnotationField.onConflict} must be one of ${annotations.OnConflictStrategy.values.map((e) => e.toString()).join(',')}', - element: _methodElement, - )); + ?.toEnumValueString(); + + if (onConflictStrategy == null) { + throw InvalidGenerationSourceError( + 'Value of ${AnnotationField.onConflict} must be one of ${annotations.OnConflictStrategy.values.map((e) => e.toString()).join(',')}', + element: _methodElement, + ); + } else { + return onConflictStrategy; + } } void _assertMethodReturnsFuture(final DartType returnType) { diff --git a/floor_generator/lib/processor/query_method_processor.dart b/floor_generator/lib/processor/query_method_processor.dart index eff58d5b..cf0417d3 100644 --- a/floor_generator/lib/processor/query_method_processor.dart +++ b/floor_generator/lib/processor/query_method_processor.dart @@ -1,9 +1,10 @@ import 'package:analyzer/dart/element/element.dart'; import 'package:analyzer/dart/element/type.dart'; -import 'package:dartx/dartx.dart'; +import 'package:collection/collection.dart'; import 'package:floor_annotation/floor_annotation.dart' as annotations; -import 'package:floor_generator/misc/annotations.dart'; import 'package:floor_generator/misc/constants.dart'; +import 'package:floor_generator/misc/extension/dart_type_extension.dart'; +import 'package:floor_generator/misc/extension/iterable_extension.dart'; import 'package:floor_generator/misc/extension/set_extension.dart'; import 'package:floor_generator/misc/extension/type_converter_element_extension.dart'; import 'package:floor_generator/misc/type_utils.dart'; @@ -24,15 +25,11 @@ class QueryMethodProcessor extends Processor { final MethodElement methodElement, final List queryables, final Set typeConverters, - ) : assert(methodElement != null), - assert(queryables != null), - assert(typeConverters != null), - _methodElement = methodElement, + ) : _methodElement = methodElement, _queryables = queryables, _typeConverters = typeConverters, _processorError = QueryMethodProcessorError(methodElement); - @nonNull @override QueryMethod process() { final name = _methodElement.displayName; @@ -44,16 +41,22 @@ class QueryMethodProcessor extends Processor { _assertReturnsFutureOrStream(rawReturnType, returnsStream); + final returnsList = _getReturnsList(rawReturnType, returnsStream); final flattenedReturnType = _getFlattenedReturnType( rawReturnType, returnsStream, + returnsList, + ); + + _assertReturnsNullableSingle( + returnsStream, + returnsList, + flattenedReturnType, ); - final queryable = _queryables.firstWhere( - (queryable) => - queryable.classElement.displayName == - flattenedReturnType.getDisplayString(withNullability: false), - orElse: () => null); + final queryable = _queryables.firstWhereOrNull((queryable) => + queryable.classElement.displayName == + flattenedReturnType.getDisplayString(withNullability: false)); final parameterTypeConverters = parameters .expand((parameter) => @@ -82,15 +85,14 @@ class QueryMethodProcessor extends Processor { ); } - @nonNull String _getQuery() { final query = _methodElement .getAnnotation(annotations.Query) .getField(AnnotationField.queryValue) ?.toStringValue() ?.replaceAll('\n', ' ') - ?.replaceAll(RegExp(r'[ ]{2,}'), ' ') - ?.trim(); + .replaceAll(RegExp(r'[ ]{2,}'), ' ') + .trim(); if (query == null || query.isEmpty) throw _processorError.noQueryDefined; @@ -99,7 +101,6 @@ class QueryMethodProcessor extends Processor { return _replaceInClauseArguments(substitutedQuery); } - @nonNull String _replaceInClauseArguments(final String query) { var index = 0; return query.replaceAllMapped( @@ -114,23 +115,17 @@ class QueryMethodProcessor extends Processor { ); } - @nonNull DartType _getFlattenedReturnType( final DartType rawReturnType, final bool returnsStream, + final bool returnsList, ) { - final returnsList = _getReturnsList(rawReturnType, returnsStream); - final type = returnsStream ? _methodElement.returnType.flatten() : _methodElement.library.typeSystem.flatten(rawReturnType); - if (returnsList) { - return type.flatten(); - } - return type; + return returnsList ? type.flatten() : type; } - @nonNull bool _getReturnsList(final DartType returnType, final bool returnsStream) { final type = returnsStream ? returnType.flatten() @@ -152,10 +147,31 @@ class QueryMethodProcessor extends Processor { final String query, final List parameterElements, ) { - final queryParameterCount = RegExp(r'\?').allMatches(query).length; + for (final parameter in parameterElements) { + if (parameter.type.isNullable) { + throw _processorError.queryMethodParameterIsNullable(parameter); + } + } + final queryParameterCount = RegExp(r'\?').allMatches(query).length; if (queryParameterCount != parameterElements.length) { throw _processorError.queryArgumentsAndMethodParametersDoNotMatch; } } + + void _assertReturnsNullableSingle( + final bool returnsStream, + final bool returnsList, + final DartType flattenedReturnType, + ) { + if (!returnsList && + !flattenedReturnType.isVoid && + !flattenedReturnType.isNullable) { + if (returnsStream) { + throw _processorError.doesNotReturnNullableStream; + } else { + throw _processorError.doesNotReturnNullableFuture; + } + } + } } diff --git a/floor_generator/lib/processor/queryable_processor.dart b/floor_generator/lib/processor/queryable_processor.dart index 7885514a..9c04278b 100644 --- a/floor_generator/lib/processor/queryable_processor.dart +++ b/floor_generator/lib/processor/queryable_processor.dart @@ -1,9 +1,10 @@ +// ignore_for_file: import_of_legacy_library_into_null_safe import 'package:analyzer/dart/element/element.dart'; import 'package:analyzer/dart/element/type.dart'; -import 'package:dartx/dartx.dart'; +import 'package:collection/collection.dart'; import 'package:floor_annotation/floor_annotation.dart' as annotations; -import 'package:floor_generator/misc/annotations.dart'; import 'package:floor_generator/misc/extension/set_extension.dart'; +import 'package:floor_generator/misc/extension/string_extension.dart'; import 'package:floor_generator/misc/extension/type_converter_element_extension.dart'; import 'package:floor_generator/misc/extension/type_converters_extension.dart'; import 'package:floor_generator/misc/type_utils.dart'; @@ -28,13 +29,10 @@ abstract class QueryableProcessor extends Processor { QueryableProcessor( this.classElement, final Set typeConverters, - ) : assert(classElement != null), - assert(typeConverters != null), - _queryableProcessorError = QueryableProcessorError(classElement), + ) : _queryableProcessorError = QueryableProcessorError(classElement), queryableTypeConverters = typeConverters + classElement.getTypeConverters(TypeConverterScope.queryable); - @nonNull @protected List getFields() { if (classElement.mixins.isNotEmpty) { @@ -54,7 +52,6 @@ abstract class QueryableProcessor extends Processor { }).toList(); } - @nonNull @protected String getConstructor(final List fields) { final constructorParameters = classElement.constructors.first.parameters; @@ -67,16 +64,14 @@ abstract class QueryableProcessor extends Processor { } /// Returns `null` whenever field is @ignored - @nullable - String _getParameterValue( + String? _getParameterValue( final ParameterElement parameterElement, final List fields, ) { final parameterName = parameterElement.displayName; - final field = fields.firstWhere( - (field) => field.name == parameterName, - orElse: () => null, // whenever field is @ignored - ); + final field = + // null whenever field is @ignored + fields.firstWhereOrNull((field) => field.name == parameterName); if (field != null) { final databaseValue = "row['${field.columnName}']"; @@ -90,7 +85,7 @@ abstract class QueryableProcessor extends Processor { ); } else { final typeConverter = [...queryableTypeConverters, field.typeConverter] - .filterNotNull() + .whereNotNull() .getClosest(parameterElement.type); final castedDatabaseValue = databaseValue.cast( typeConverter.databaseType, @@ -127,13 +122,13 @@ extension on String { return '($this as int) != 0'; } } else if (dartType.isDartCoreString) { - return '$this as String'; + return '$this as String${isNullable ? '?' : ''}'; } else if (dartType.isDartCoreInt) { - return '$this as int'; + return '$this as int${isNullable ? '?' : ''}'; } else if (dartType.isUint8List) { - return '$this as Uint8List'; + return '$this as Uint8List${isNullable ? '?' : ''}'; } else if (dartType.isDartCoreDouble) { - return '$this as double'; // must be double + return '$this as double${isNullable ? '?' : ''}'; } else { throw InvalidGenerationSourceError( 'Trying to convert unsupported type $dartType.', diff --git a/floor_generator/lib/processor/transaction_method_processor.dart b/floor_generator/lib/processor/transaction_method_processor.dart index 8c55022e..2e05659d 100644 --- a/floor_generator/lib/processor/transaction_method_processor.dart +++ b/floor_generator/lib/processor/transaction_method_processor.dart @@ -1,5 +1,4 @@ import 'package:analyzer/dart/element/element.dart'; -import 'package:floor_generator/misc/annotations.dart'; import 'package:floor_generator/processor/error/transaction_method_processor_error.dart'; import 'package:floor_generator/processor/processor.dart'; import 'package:floor_generator/value_object/transaction_method.dart'; @@ -13,14 +12,10 @@ class TransactionMethodProcessor implements Processor { final MethodElement methodElement, final String daoGetterName, final String databaseName, - ) : assert(methodElement != null), - assert(daoGetterName != null), - assert(daoGetterName != null), - _methodElement = methodElement, + ) : _methodElement = methodElement, _daoGetterName = daoGetterName, _databaseName = databaseName; - @nonNull @override TransactionMethod process() { final name = _methodElement.displayName; diff --git a/floor_generator/lib/processor/type_converter_processor.dart b/floor_generator/lib/processor/type_converter_processor.dart index e76cc915..5e160f50 100644 --- a/floor_generator/lib/processor/type_converter_processor.dart +++ b/floor_generator/lib/processor/type_converter_processor.dart @@ -1,8 +1,8 @@ import 'package:analyzer/dart/element/element.dart'; import 'package:floor_generator/misc/type_utils.dart'; +import 'package:floor_generator/processor/error/processor_error.dart'; import 'package:floor_generator/processor/processor.dart'; import 'package:floor_generator/value_object/type_converter.dart'; -import 'package:source_gen/source_gen.dart'; class TypeConverterProcessor extends Processor { final ClassElement _classElement; @@ -16,13 +16,23 @@ class TypeConverterProcessor extends Processor { @override TypeConverter process() { - final typeArguments = _classElement.supertype.typeArguments; + final supertype = _classElement.supertype; + if (supertype == null) { + throw ProcessorError( + message: + 'Only classes that inherit from TypeConverter can be used as type converters.', + todo: 'Make sure use a class that inherits from TypeConverter.', + element: _classElement, + ); + } + final typeArguments = supertype.typeArguments; final fieldType = typeArguments[0]; final databaseType = typeArguments[1]; if (!databaseType.isDefaultSqlType) { - throw InvalidGenerationSourceError( - 'Type converters have to convert to a database-compatible type.', + throw ProcessorError( + message: + 'Type converters have to convert to a database-compatible type.', todo: 'Make the class convert to either int, double, String, bool or Uint8List.', element: _classElement, diff --git a/floor_generator/lib/processor/update_method_processor.dart b/floor_generator/lib/processor/update_method_processor.dart index 2dface6a..776d153f 100644 --- a/floor_generator/lib/processor/update_method_processor.dart +++ b/floor_generator/lib/processor/update_method_processor.dart @@ -1,8 +1,8 @@ +// ignore_for_file: import_of_legacy_library_into_null_safe import 'package:analyzer/dart/element/element.dart'; import 'package:analyzer/dart/element/type.dart'; import 'package:floor_annotation/floor_annotation.dart' as annotations show Update, OnConflictStrategy; -import 'package:floor_generator/misc/annotations.dart'; import 'package:floor_generator/misc/change_method_processor_helper.dart'; import 'package:floor_generator/misc/constants.dart'; import 'package:floor_generator/misc/extension/dart_object_extension.dart'; @@ -19,14 +19,11 @@ class UpdateMethodProcessor implements Processor { UpdateMethodProcessor( final MethodElement methodElement, final List entities, [ - final ChangeMethodProcessorHelper changeMethodProcessorHelper, - ]) : assert(methodElement != null), - assert(entities != null), - _methodElement = methodElement, + final ChangeMethodProcessorHelper? changeMethodProcessorHelper, + ]) : _methodElement = methodElement, _helper = changeMethodProcessorHelper ?? ChangeMethodProcessorHelper(methodElement, entities); - @nonNull @override UpdateMethod process() { final name = _methodElement.name; @@ -64,19 +61,22 @@ class UpdateMethodProcessor implements Processor { ); } - @nonNull String _getOnConflictStrategy() { - return _methodElement + final onConflictStrategy = _methodElement .getAnnotation(annotations.Update) .getField(AnnotationField.onConflict) - .toEnumValueString( - orElse: () => throw InvalidGenerationSourceError( - 'Value of ${AnnotationField.onConflict} must be one of ${annotations.OnConflictStrategy.values.map((e) => e.toString()).join(',')}', - element: _methodElement, - )); + ?.toEnumValueString(); + + if (onConflictStrategy == null) { + throw InvalidGenerationSourceError( + 'Value of ${AnnotationField.onConflict} must be one of ${annotations.OnConflictStrategy.values.map((e) => e.toString()).join(',')}', + element: _methodElement, + ); + } else { + return onConflictStrategy; + } } - @nonNull DartType _getFlattenedReturnType(final DartType returnType) { return _methodElement.library.typeSystem.flatten(returnType); } diff --git a/floor_generator/lib/processor/view_processor.dart b/floor_generator/lib/processor/view_processor.dart index 885524f2..8aad8e80 100644 --- a/floor_generator/lib/processor/view_processor.dart +++ b/floor_generator/lib/processor/view_processor.dart @@ -1,6 +1,5 @@ import 'package:analyzer/dart/element/element.dart'; import 'package:floor_annotation/floor_annotation.dart' as annotations; -import 'package:floor_generator/misc/annotations.dart'; import 'package:floor_generator/misc/constants.dart'; import 'package:floor_generator/misc/type_utils.dart'; import 'package:floor_generator/processor/error/view_processor_error.dart'; @@ -17,7 +16,6 @@ class ViewProcessor extends QueryableProcessor { ) : _processorError = ViewProcessorError(classElement), super(classElement, typeConverters); - @nonNull @override View process() { final fields = getFields(); @@ -30,7 +28,6 @@ class ViewProcessor extends QueryableProcessor { ); } - @nonNull String _getName() { return classElement .getAnnotation(annotations.DatabaseView) @@ -39,7 +36,6 @@ class ViewProcessor extends QueryableProcessor { classElement.displayName; } - @nonNull String _getQuery() { final query = classElement .getAnnotation(annotations.DatabaseView) diff --git a/floor_generator/lib/value_object/dao.dart b/floor_generator/lib/value_object/dao.dart index 9eff7a4e..c8fc1888 100644 --- a/floor_generator/lib/value_object/dao.dart +++ b/floor_generator/lib/value_object/dao.dart @@ -1,5 +1,5 @@ import 'package:analyzer/dart/element/element.dart'; -import 'package:floor_generator/misc/extension/list_equality_extension.dart'; +import 'package:collection/collection.dart'; import 'package:floor_generator/misc/extension/set_equality_extension.dart'; import 'package:floor_generator/value_object/deletion_method.dart'; import 'package:floor_generator/value_object/entity.dart'; diff --git a/floor_generator/lib/value_object/database.dart b/floor_generator/lib/value_object/database.dart index 97f86a24..a830db14 100644 --- a/floor_generator/lib/value_object/database.dart +++ b/floor_generator/lib/value_object/database.dart @@ -1,5 +1,5 @@ import 'package:analyzer/dart/element/element.dart'; -import 'package:floor_generator/misc/extension/list_equality_extension.dart'; +import 'package:collection/collection.dart'; import 'package:floor_generator/misc/extension/set_equality_extension.dart'; import 'package:floor_generator/value_object/dao_getter.dart'; import 'package:floor_generator/value_object/entity.dart'; diff --git a/floor_generator/lib/value_object/entity.dart b/floor_generator/lib/value_object/entity.dart index 5c524e28..fda23624 100644 --- a/floor_generator/lib/value_object/entity.dart +++ b/floor_generator/lib/value_object/entity.dart @@ -1,6 +1,5 @@ import 'package:analyzer/dart/element/element.dart'; -import 'package:floor_generator/misc/annotations.dart'; -import 'package:floor_generator/misc/extension/list_equality_extension.dart'; +import 'package:collection/collection.dart'; import 'package:floor_generator/value_object/field.dart'; import 'package:floor_generator/value_object/foreign_key.dart'; import 'package:floor_generator/value_object/index.dart'; @@ -15,7 +14,7 @@ class Entity extends Queryable { final List indices; final bool withoutRowid; final String valueMapping; - final Fts fts; + final Fts? fts; Entity( ClassElement classElement, @@ -30,7 +29,6 @@ class Entity extends Queryable { this.fts, ) : super(classElement, name, fields, constructor); - @nonNull String getCreateTableStatement() { final databaseDefinition = fields.map((field) { final autoIncrement = @@ -52,15 +50,14 @@ class Entity extends Queryable { if (fts == null) { return 'CREATE TABLE IF NOT EXISTS `$name` (${databaseDefinition.join(', ')})$withoutRowidClause'; } else { - if (fts.tableCreateOption().isNotEmpty) { - databaseDefinition.add('${fts.tableCreateOption()}'); + if (fts!.tableCreateOption().isNotEmpty) { + databaseDefinition.add('${fts!.tableCreateOption()}'); } - return 'CREATE VIRTUAL TABLE IF NOT EXISTS `$name` ${fts.usingOption}(${databaseDefinition.join(', ')})'; + return 'CREATE VIRTUAL TABLE IF NOT EXISTS `$name` ${fts!.usingOption}(${databaseDefinition.join(', ')})'; } } - @nullable - String _createPrimaryKeyDefinition() { + String? _createPrimaryKeyDefinition() { if (primaryKey.autoGenerateId) { return null; } else { diff --git a/floor_generator/lib/value_object/field.dart b/floor_generator/lib/value_object/field.dart index 5b2dc98d..0f265ed4 100644 --- a/floor_generator/lib/value_object/field.dart +++ b/floor_generator/lib/value_object/field.dart @@ -1,5 +1,4 @@ import 'package:analyzer/dart/element/element.dart'; -import 'package:floor_generator/misc/annotations.dart'; import 'package:floor_generator/value_object/type_converter.dart'; /// Represents an Entity field and thus a table column. @@ -9,8 +8,7 @@ class Field { final String columnName; final bool isNullable; final String sqlType; - @nullable - final TypeConverter typeConverter; + final TypeConverter? typeConverter; Field( this.fieldElement, @@ -22,7 +20,6 @@ class Field { ); /// The database column definition. - @nonNull String getDatabaseDefinition(final bool autoGenerate) { final columnSpecification = StringBuffer(); diff --git a/floor_generator/lib/value_object/foreign_key.dart b/floor_generator/lib/value_object/foreign_key.dart index fa8dd6f0..0cffd131 100644 --- a/floor_generator/lib/value_object/foreign_key.dart +++ b/floor_generator/lib/value_object/foreign_key.dart @@ -1,6 +1,5 @@ import 'package:collection/collection.dart'; import 'package:floor_annotation/floor_annotation.dart' show ForeignKeyAction; -import 'package:floor_generator/misc/annotations.dart'; import 'package:floor_generator/misc/extension/foreign_key_action_extension.dart'; class ForeignKey { @@ -18,7 +17,6 @@ class ForeignKey { this.onDelete, ); - @nonNull String getDefinition() { final escapedChildColumns = childColumns.map((column) => '`$column`').join(', '); diff --git a/floor_generator/lib/value_object/fts.dart b/floor_generator/lib/value_object/fts.dart index c3e3adc4..06841319 100644 --- a/floor_generator/lib/value_object/fts.dart +++ b/floor_generator/lib/value_object/fts.dart @@ -36,7 +36,7 @@ class Fts3 extends Fts { String tableCreateOption() { final StringBuffer stringBuffer = StringBuffer(); stringBuffer.write('tokenize=$tokenizer '); - if (tokenizerArgs != null && tokenizerArgs.isNotEmpty) { + if (tokenizerArgs.isNotEmpty) { stringBuffer.write(tokenizerArgs.join(' ')); } @@ -60,7 +60,7 @@ class Fts4 extends Fts { String tableCreateOption() { final StringBuffer stringBuffer = StringBuffer(); stringBuffer.write('tokenize=$tokenizer '); - if (tokenizerArgs != null && tokenizerArgs.isNotEmpty) { + if (tokenizerArgs.isNotEmpty) { stringBuffer.write(tokenizerArgs.join(' ')); } return stringBuffer.toString(); diff --git a/floor_generator/lib/value_object/index.dart b/floor_generator/lib/value_object/index.dart index d0056893..b3422d27 100644 --- a/floor_generator/lib/value_object/index.dart +++ b/floor_generator/lib/value_object/index.dart @@ -1,5 +1,3 @@ -import 'package:floor_generator/misc/annotations.dart'; - class Index { final String name; final String tableName; @@ -8,7 +6,6 @@ class Index { Index(this.name, this.tableName, this.unique, this.columnNames); - @nonNull String createQuery() { final uniqueSql = unique ? ' UNIQUE' : ''; final escapedColumnNames = diff --git a/floor_generator/lib/value_object/query_method.dart b/floor_generator/lib/value_object/query_method.dart index ed2af0cc..0a393bd6 100644 --- a/floor_generator/lib/value_object/query_method.dart +++ b/floor_generator/lib/value_object/query_method.dart @@ -1,6 +1,6 @@ import 'package:analyzer/dart/element/element.dart'; import 'package:analyzer/dart/element/type.dart'; -import 'package:floor_generator/misc/extension/list_equality_extension.dart'; +import 'package:collection/collection.dart'; import 'package:floor_generator/misc/extension/set_equality_extension.dart'; import 'package:floor_generator/misc/type_utils.dart'; import 'package:floor_generator/value_object/queryable.dart'; @@ -30,7 +30,7 @@ class QueryMethod { final List parameters; - final Queryable queryable; + final Queryable? queryable; final Set typeConverters; diff --git a/floor_generator/lib/value_object/view.dart b/floor_generator/lib/value_object/view.dart index a38e21d8..28a079f8 100644 --- a/floor_generator/lib/value_object/view.dart +++ b/floor_generator/lib/value_object/view.dart @@ -1,6 +1,5 @@ import 'package:analyzer/dart/element/element.dart'; import 'package:collection/collection.dart'; -import 'package:floor_generator/misc/annotations.dart'; import 'package:floor_generator/value_object/field.dart'; import 'package:floor_generator/value_object/queryable.dart'; @@ -15,7 +14,6 @@ class View extends Queryable { String constructor, ) : super(classElement, name, fields, constructor); - @nonNull String getCreateViewStatement() { return 'CREATE VIEW IF NOT EXISTS `$name` AS $query'; } diff --git a/floor_generator/lib/writer/dao_writer.dart b/floor_generator/lib/writer/dao_writer.dart index a17eca8a..31248553 100644 --- a/floor_generator/lib/writer/dao_writer.dart +++ b/floor_generator/lib/writer/dao_writer.dart @@ -1,5 +1,6 @@ +// ignore_for_file: import_of_legacy_library_into_null_safe import 'package:code_builder/code_builder.dart'; -import 'package:floor_generator/misc/string_utils.dart'; +import 'package:floor_generator/misc/extension/string_extension.dart'; import 'package:floor_generator/value_object/dao.dart'; import 'package:floor_generator/value_object/deletion_method.dart'; import 'package:floor_generator/value_object/entity.dart'; diff --git a/floor_generator/lib/writer/database_builder_writer.dart b/floor_generator/lib/writer/database_builder_writer.dart index c5f8b64b..7550ebb1 100644 --- a/floor_generator/lib/writer/database_builder_writer.dart +++ b/floor_generator/lib/writer/database_builder_writer.dart @@ -1,5 +1,5 @@ +// ignore_for_file: import_of_legacy_library_into_null_safe import 'package:code_builder/code_builder.dart'; -import 'package:floor_generator/misc/annotations.dart'; import 'package:floor_generator/writer/writer.dart'; class DatabaseBuilderWriter extends Writer { @@ -8,14 +8,13 @@ class DatabaseBuilderWriter extends Writer { DatabaseBuilderWriter(final String databaseName) : _databaseName = databaseName; - @nonNull @override Class write() { final databaseBuilderName = '_\$${_databaseName}Builder'; final nameField = Field((builder) => builder ..name = 'name' - ..type = refer('String') + ..type = refer('String?') ..modifier = FieldModifier.final$); final migrationsField = Field((builder) => builder @@ -26,7 +25,7 @@ class DatabaseBuilderWriter extends Writer { final callbackField = Field((builder) => builder ..name = '_callback' - ..type = refer('Callback')); + ..type = refer('Callback?')); final constructor = Constructor((builder) => builder ..requiredParameters.add(Parameter((builder) => builder @@ -64,7 +63,7 @@ class DatabaseBuilderWriter extends Writer { ..docs.add('/// Creates the database and initializes it.') ..body = Code(''' final path = name != null - ? await sqfliteDatabaseFactory.getDatabasePath(name) + ? await sqfliteDatabaseFactory.getDatabasePath(name!) : ':memory:'; final database = _\$$_databaseName(); database.database = await database.open( diff --git a/floor_generator/lib/writer/database_writer.dart b/floor_generator/lib/writer/database_writer.dart index 0e3de2dc..46f89094 100644 --- a/floor_generator/lib/writer/database_writer.dart +++ b/floor_generator/lib/writer/database_writer.dart @@ -1,6 +1,6 @@ +// ignore_for_file: import_of_legacy_library_into_null_safe import 'package:code_builder/code_builder.dart'; import 'package:floor_generator/misc/annotation_expression.dart'; -import 'package:floor_generator/misc/annotations.dart'; import 'package:floor_generator/value_object/database.dart'; import 'package:floor_generator/value_object/entity.dart'; import 'package:floor_generator/writer/writer.dart'; @@ -11,13 +11,11 @@ class DatabaseWriter implements Writer { DatabaseWriter(final this.database); - @nonNull @override Class write() { return _generateDatabaseImplementation(database); } - @nonNull Class _generateDatabaseImplementation(final Database database) { final databaseName = database.name; @@ -30,14 +28,13 @@ class DatabaseWriter implements Writer { ..constructors.add(_generateConstructor())); } - @nonNull Constructor _generateConstructor() { return Constructor((builder) { final parameter = Parameter((builder) => builder ..name = 'listener' - ..type = refer('StreamController')); + ..type = refer('StreamController?')); - return builder + builder ..body = const Code( 'changeListener = listener ?? StreamController.broadcast();', ) @@ -45,7 +42,6 @@ class DatabaseWriter implements Writer { }); } - @nonNull List _generateDaoGetters(final Database database) { return database.daoGetters.map((daoGetter) { final daoGetterName = daoGetter.name; @@ -61,19 +57,17 @@ class DatabaseWriter implements Writer { }).toList(); } - @nonNull List _generateDaoInstances(final Database database) { return database.daoGetters.map((daoGetter) { final daoGetterName = daoGetter.name; final daoTypeName = daoGetter.dao.classElement.displayName; return Field((builder) => builder - ..type = refer(daoTypeName) + ..type = refer('$daoTypeName?') ..name = '_${daoGetterName}Instance'); }).toList(); } - @nonNull Method _generateOpenMethod(final Database database) { final createTableStatements = _generateCreateTableSqlStatements(database.entities) @@ -97,7 +91,7 @@ class DatabaseWriter implements Writer { ..type = refer('List')); final callbackParameter = Parameter((builder) => builder ..name = 'callback' - ..type = refer('Callback')); + ..type = refer('Callback?')); return Method((builder) => builder ..name = 'open' @@ -131,7 +125,6 @@ class DatabaseWriter implements Writer { ''')); } - @nonNull List _generateCreateTableSqlStatements(final List entities) { return entities.map((entity) => entity.getCreateTableStatement()).toList(); } diff --git a/floor_generator/lib/writer/deletion_method_writer.dart b/floor_generator/lib/writer/deletion_method_writer.dart index ab5c9cd4..193ced7e 100644 --- a/floor_generator/lib/writer/deletion_method_writer.dart +++ b/floor_generator/lib/writer/deletion_method_writer.dart @@ -1,7 +1,7 @@ +// ignore_for_file: import_of_legacy_library_into_null_safe import 'package:code_builder/code_builder.dart'; -import 'package:floor_generator/misc/annotations.dart'; import 'package:floor_generator/misc/change_method_writer_helper.dart'; -import 'package:floor_generator/misc/string_utils.dart'; +import 'package:floor_generator/misc/extension/string_extension.dart'; import 'package:floor_generator/value_object/deletion_method.dart'; import 'package:floor_generator/writer/writer.dart'; @@ -11,12 +11,10 @@ class DeletionMethodWriter implements Writer { DeletionMethodWriter( final DeletionMethod method, [ - final ChangeMethodWriterHelper helper, - ]) : assert(method != null), - _method = method, + final ChangeMethodWriterHelper? helper, + ]) : _method = method, _helper = helper ?? ChangeMethodWriterHelper(method); - @nonNull @override Method write() { final methodBuilder = MethodBuilder()..body = Code(_generateMethodBody()); @@ -24,7 +22,6 @@ class DeletionMethodWriter implements Writer { return methodBuilder.build(); } - @nonNull String _generateMethodBody() { final entityClassName = _method.entity.classElement.displayName.decapitalize(); @@ -44,7 +41,6 @@ class DeletionMethodWriter implements Writer { } } - @nonNull String _generateVoidReturnMethodBody( final String methodSignatureParameterName, final String entityClassName, @@ -56,7 +52,6 @@ class DeletionMethodWriter implements Writer { } } - @nonNull String _generateIntReturnMethodBody( final String methodSignatureParameterName, final String entityClassName, diff --git a/floor_generator/lib/writer/floor_writer.dart b/floor_generator/lib/writer/floor_writer.dart index a1c9289c..c3bb88f7 100644 --- a/floor_generator/lib/writer/floor_writer.dart +++ b/floor_generator/lib/writer/floor_writer.dart @@ -1,5 +1,5 @@ +// ignore_for_file: import_of_legacy_library_into_null_safe import 'package:code_builder/code_builder.dart'; -import 'package:floor_generator/misc/annotations.dart'; import 'package:floor_generator/writer/writer.dart'; class FloorWriter extends Writer { @@ -7,7 +7,6 @@ class FloorWriter extends Writer { FloorWriter(final String databaseName) : _databaseName = databaseName; - @nonNull @override Class write() { final databaseBuilderName = '_\$${_databaseName}Builder'; diff --git a/floor_generator/lib/writer/insertion_method_writer.dart b/floor_generator/lib/writer/insertion_method_writer.dart index 860c2982..c115e30d 100644 --- a/floor_generator/lib/writer/insertion_method_writer.dart +++ b/floor_generator/lib/writer/insertion_method_writer.dart @@ -1,7 +1,7 @@ +// ignore_for_file: import_of_legacy_library_into_null_safe import 'package:code_builder/code_builder.dart'; -import 'package:floor_generator/misc/annotations.dart'; import 'package:floor_generator/misc/change_method_writer_helper.dart'; -import 'package:floor_generator/misc/string_utils.dart'; +import 'package:floor_generator/misc/extension/string_extension.dart'; import 'package:floor_generator/value_object/insertion_method.dart'; import 'package:floor_generator/writer/writer.dart'; @@ -11,12 +11,10 @@ class InsertionMethodWriter implements Writer { InsertionMethodWriter( final InsertionMethod method, [ - final ChangeMethodWriterHelper helper, - ]) : assert(method != null), - _method = method, + final ChangeMethodWriterHelper? helper, + ]) : _method = method, _helper = helper ?? ChangeMethodWriterHelper(method); - @nonNull @override Method write() { final methodBuilder = MethodBuilder()..body = Code(_generateMethodBody()); @@ -24,7 +22,6 @@ class InsertionMethodWriter implements Writer { return methodBuilder.build(); } - @nonNull String _generateMethodBody() { final entityClassName = _method.entity.classElement.displayName.decapitalize(); @@ -44,7 +41,6 @@ class InsertionMethodWriter implements Writer { } } - @nonNull String _generateVoidReturnMethodBody( final String methodSignatureParameterName, final String entityClassName, @@ -56,7 +52,6 @@ class InsertionMethodWriter implements Writer { } } - @nonNull String _generateIntReturnMethodBody( final String methodSignatureParameterName, final String entityClassName, diff --git a/floor_generator/lib/writer/query_method_writer.dart b/floor_generator/lib/writer/query_method_writer.dart index d443abdb..a7e86bee 100644 --- a/floor_generator/lib/writer/query_method_writer.dart +++ b/floor_generator/lib/writer/query_method_writer.dart @@ -1,9 +1,10 @@ +// ignore_for_file: import_of_legacy_library_into_null_safe import 'dart:core'; import 'package:code_builder/code_builder.dart'; -import 'package:dartx/dartx.dart'; +import 'package:collection/collection.dart'; import 'package:floor_generator/misc/annotation_expression.dart'; -import 'package:floor_generator/misc/annotations.dart'; +import 'package:floor_generator/misc/extension/string_extension.dart'; import 'package:floor_generator/misc/extension/type_converters_extension.dart'; import 'package:floor_generator/misc/type_utils.dart'; import 'package:floor_generator/value_object/query_method.dart'; @@ -13,16 +14,14 @@ import 'package:floor_generator/writer/writer.dart'; class QueryMethodWriter implements Writer { final QueryMethod _queryMethod; - QueryMethodWriter(final QueryMethod queryMethod) - : assert(queryMethod != null), - _queryMethod = queryMethod; + QueryMethodWriter(final QueryMethod queryMethod) : _queryMethod = queryMethod; @override Method write() { final builder = MethodBuilder() ..annotations.add(overrideAnnotationExpression) ..returns = refer(_queryMethod.rawReturnType.getDisplayString( - withNullability: false, + withNullability: true, )) ..name = _queryMethod.name ..requiredParameters.addAll(_generateMethodParameters()) @@ -39,7 +38,9 @@ class QueryMethodWriter implements Writer { return Parameter((builder) => builder ..name = parameter.name ..type = refer(parameter.type.getDisplayString( - withNullability: false, + // processor disallows nullable method parameters and throws if found, + // still interested in nullability here to future-proof codebase + withNullability: true, ))); }).toList(); } @@ -53,13 +54,15 @@ class QueryMethodWriter implements Writer { } final arguments = _generateArguments(); - if (_queryMethod.returnsVoid) { + final queryable = _queryMethod.queryable; + // null queryable implies void-returning query method + if (_queryMethod.returnsVoid || queryable == null) { _methodBody.write(_generateNoReturnQuery(arguments)); return _methodBody.toString(); } - final constructor = _queryMethod.queryable.constructor; - final mapper = '(Map row) => $constructor'; + final constructor = queryable.constructor; + final mapper = '(Map row) => $constructor'; if (_queryMethod.returnsStream) { _methodBody.write(_generateStreamQuery(arguments, mapper)); @@ -70,7 +73,6 @@ class QueryMethodWriter implements Writer { return _methodBody.toString(); } - @nonNull List _generateInClauseValueLists() { return _queryMethod.parameters .where((parameter) => parameter.type.isDartCoreList) @@ -89,14 +91,14 @@ class QueryMethodWriter implements Writer { }).toList(); } - @nonNull List _generateParameters() { return _queryMethod.parameters .where((parameter) => !parameter.type.isDartCoreList) .map((parameter) { if (parameter.type.isDefaultSqlType) { if (parameter.type.isDartCoreBool) { - return '${parameter.displayName} == null ? null : (${parameter.displayName} ? 1 : 0)'; + // query method parameters can't be null + return '${parameter.displayName} ? 1 : 0'; } else { return parameter.displayName; } @@ -108,23 +110,20 @@ class QueryMethodWriter implements Writer { }).toList(); } - @nullable - String _generateArguments() { + String? _generateArguments() { final parameters = _generateParameters(); - return parameters.isNotEmpty ? '[${parameters.join(', ')}]' : null; + return parameters.isNotEmpty ? '[${parameters.join(', ')}]' : null; } - @nonNull - String _generateNoReturnQuery(@nullable final String arguments) { + String _generateNoReturnQuery(final String? arguments) { final parameters = StringBuffer()..write("'${_queryMethod.query}'"); if (arguments != null) parameters.write(', arguments: $arguments'); return 'await _queryAdapter.queryNoReturn($parameters);'; } - @nonNull String _generateQuery( - @nullable final String arguments, - @nonNull final String mapper, + final String? arguments, + final String mapper, ) { final parameters = StringBuffer()..write("'${_queryMethod.query}', "); if (arguments != null) parameters.write('arguments: $arguments, '); @@ -137,13 +136,16 @@ class QueryMethodWriter implements Writer { } } - @nonNull String _generateStreamQuery( - @nullable final String arguments, - @nonNull final String mapper, + final String? arguments, + final String mapper, ) { - final queryableName = _queryMethod.queryable.name; - final isView = _queryMethod.queryable is View; + final queryable = _queryMethod.queryable; + // can't be null as validated before + if (queryable == null) throw ArgumentError.notNull(); + + final queryableName = queryable.name; + final isView = queryable is View; final parameters = StringBuffer()..write("'${_queryMethod.query}', "); if (arguments != null) parameters.write('arguments: $arguments, '); parameters diff --git a/floor_generator/lib/writer/transaction_method_writer.dart b/floor_generator/lib/writer/transaction_method_writer.dart index e4425a38..21e52bbb 100644 --- a/floor_generator/lib/writer/transaction_method_writer.dart +++ b/floor_generator/lib/writer/transaction_method_writer.dart @@ -1,3 +1,4 @@ +// ignore_for_file: import_of_legacy_library_into_null_safe import 'package:code_builder/code_builder.dart'; import 'package:floor_generator/misc/annotation_expression.dart'; import 'package:floor_generator/misc/type_utils.dart'; diff --git a/floor_generator/lib/writer/type_converter_field_writer.dart b/floor_generator/lib/writer/type_converter_field_writer.dart index 43d47f9b..b580c17b 100644 --- a/floor_generator/lib/writer/type_converter_field_writer.dart +++ b/floor_generator/lib/writer/type_converter_field_writer.dart @@ -1,5 +1,6 @@ +// ignore_for_file: import_of_legacy_library_into_null_safe import 'package:code_builder/code_builder.dart'; -import 'package:dartx/dartx.dart'; +import 'package:floor_generator/misc/extension/string_extension.dart'; import 'package:floor_generator/writer/writer.dart'; class TypeConverterFieldWriter extends Writer { diff --git a/floor_generator/lib/writer/update_method_writer.dart b/floor_generator/lib/writer/update_method_writer.dart index ed86b95e..4379d132 100644 --- a/floor_generator/lib/writer/update_method_writer.dart +++ b/floor_generator/lib/writer/update_method_writer.dart @@ -1,7 +1,7 @@ +// ignore_for_file: import_of_legacy_library_into_null_safe import 'package:code_builder/code_builder.dart'; -import 'package:floor_generator/misc/annotations.dart'; import 'package:floor_generator/misc/change_method_writer_helper.dart'; -import 'package:floor_generator/misc/string_utils.dart'; +import 'package:floor_generator/misc/extension/string_extension.dart'; import 'package:floor_generator/value_object/update_method.dart'; import 'package:floor_generator/writer/writer.dart'; @@ -11,12 +11,10 @@ class UpdateMethodWriter implements Writer { UpdateMethodWriter( final UpdateMethod method, [ - final ChangeMethodWriterHelper helper, - ]) : assert(method != null), - _method = method, + final ChangeMethodWriterHelper? helper, + ]) : _method = method, _helper = helper ?? ChangeMethodWriterHelper(method); - @nonNull @override Method write() { final methodBuilder = MethodBuilder()..body = Code(_generateMethodBody()); @@ -24,7 +22,6 @@ class UpdateMethodWriter implements Writer { return methodBuilder.build(); } - @nonNull String _generateMethodBody() { final entityClassName = _method.entity.classElement.displayName.decapitalize(); @@ -44,7 +41,6 @@ class UpdateMethodWriter implements Writer { } } - @nonNull String _generateIntReturnMethodBody( final String methodSignatureParameterName, final String entityClassName, @@ -56,7 +52,6 @@ class UpdateMethodWriter implements Writer { } } - @nonNull String _generateVoidReturnMethodBody( final String methodSignatureParameterName, final String entityClassName, diff --git a/floor_generator/lib/writer/writer.dart b/floor_generator/lib/writer/writer.dart index cb6fa02d..ce25849d 100644 --- a/floor_generator/lib/writer/writer.dart +++ b/floor_generator/lib/writer/writer.dart @@ -1,3 +1,4 @@ +// ignore_for_file: import_of_legacy_library_into_null_safe import 'package:code_builder/code_builder.dart'; abstract class Writer { diff --git a/floor_generator/pubspec.lock b/floor_generator/pubspec.lock index e80f8601..72351ec0 100644 --- a/floor_generator/pubspec.lock +++ b/floor_generator/pubspec.lock @@ -7,42 +7,42 @@ packages: name: _fe_analyzer_shared url: "https://pub.dartlang.org" source: hosted - version: "14.0.0" + version: "17.0.0" analyzer: dependency: "direct main" description: name: analyzer url: "https://pub.dartlang.org" source: hosted - version: "0.41.2" + version: "1.1.0" args: dependency: transitive description: name: args url: "https://pub.dartlang.org" source: hosted - version: "1.6.0" + version: "2.0.0" async: dependency: transitive description: name: async url: "https://pub.dartlang.org" source: hosted - version: "2.4.2" + version: "2.5.0" boolean_selector: dependency: transitive description: name: boolean_selector url: "https://pub.dartlang.org" source: hosted - version: "2.0.0" + version: "2.1.0" build: dependency: "direct main" description: name: build url: "https://pub.dartlang.org" source: hosted - version: "1.6.2" + version: "1.6.3" build_config: dependency: "direct main" description: @@ -56,56 +56,49 @@ packages: name: build_resolvers url: "https://pub.dartlang.org" source: hosted - version: "1.5.3" + version: "1.5.4" build_test: dependency: "direct dev" description: name: build_test url: "https://pub.dartlang.org" source: hosted - version: "1.3.6" + version: "1.3.7" built_collection: dependency: transitive description: name: built_collection url: "https://pub.dartlang.org" source: hosted - version: "4.3.2" + version: "5.0.0" built_value: dependency: transitive description: name: built_value url: "https://pub.dartlang.org" source: hosted - version: "7.1.0" - characters: - dependency: transitive - description: - name: characters - url: "https://pub.dartlang.org" - source: hosted - version: "1.0.0" + version: "8.0.0" charcode: dependency: transitive description: name: charcode url: "https://pub.dartlang.org" source: hosted - version: "1.1.3" + version: "1.2.0" checked_yaml: dependency: transitive description: name: checked_yaml url: "https://pub.dartlang.org" source: hosted - version: "1.0.2" + version: "1.0.4" cli_util: dependency: transitive description: name: cli_util url: "https://pub.dartlang.org" source: hosted - version: "0.2.0" + version: "0.3.0" code_builder: dependency: "direct main" description: @@ -119,70 +112,70 @@ packages: name: collection url: "https://pub.dartlang.org" source: hosted - version: "1.14.13" + version: "1.15.0" convert: dependency: transitive description: name: convert url: "https://pub.dartlang.org" source: hosted - version: "2.1.1" + version: "3.0.0" coverage: dependency: transitive description: name: coverage url: "https://pub.dartlang.org" source: hosted - version: "0.13.11" + version: "1.0.1" crypto: dependency: transitive description: name: crypto url: "https://pub.dartlang.org" source: hosted - version: "2.1.5" + version: "3.0.0" csslib: dependency: transitive description: name: csslib url: "https://pub.dartlang.org" source: hosted - version: "0.16.2" + version: "0.17.0" dart_style: dependency: "direct dev" description: name: dart_style url: "https://pub.dartlang.org" source: hosted - version: "1.3.12" - dartx: - dependency: "direct main" + version: "1.3.14" + file: + dependency: transitive description: - name: dartx + name: file url: "https://pub.dartlang.org" source: hosted - version: "0.5.0" + version: "6.1.0" fixnum: dependency: transitive description: name: fixnum url: "https://pub.dartlang.org" source: hosted - version: "0.10.11" + version: "1.0.0" floor_annotation: dependency: "direct main" description: path: "../floor_annotation" relative: true source: path - version: "0.12.0" + version: "1.0.0-nullsafety.1" glob: dependency: transitive description: name: glob url: "https://pub.dartlang.org" source: hosted - version: "1.2.0" + version: "2.0.0" graphs: dependency: transitive description: @@ -196,189 +189,154 @@ packages: name: html url: "https://pub.dartlang.org" source: hosted - version: "0.14.0+4" - http: - dependency: transitive - description: - name: http - url: "https://pub.dartlang.org" - source: hosted - version: "0.12.2" + version: "0.15.0" http_multi_server: dependency: transitive description: name: http_multi_server url: "https://pub.dartlang.org" source: hosted - version: "2.2.0" + version: "3.0.0" http_parser: dependency: transitive description: name: http_parser url: "https://pub.dartlang.org" source: hosted - version: "3.1.4" + version: "4.0.0" io: dependency: transitive description: name: io url: "https://pub.dartlang.org" source: hosted - version: "0.3.4" + version: "1.0.0" js: dependency: transitive description: name: js url: "https://pub.dartlang.org" source: hosted - version: "0.6.2" + version: "0.6.3" json_annotation: dependency: transitive description: name: json_annotation url: "https://pub.dartlang.org" source: hosted - version: "3.1.0" - lcov: - dependency: transitive - description: - name: lcov - url: "https://pub.dartlang.org" - source: hosted - version: "5.7.0" + version: "4.0.0" logging: dependency: transitive description: name: logging url: "https://pub.dartlang.org" source: hosted - version: "0.11.4" + version: "1.0.0" matcher: dependency: transitive description: name: matcher url: "https://pub.dartlang.org" source: hosted - version: "0.12.9" + version: "0.12.10" meta: dependency: "direct main" description: name: meta url: "https://pub.dartlang.org" source: hosted - version: "1.2.4" + version: "1.3.0" mime: dependency: transitive description: name: mime url: "https://pub.dartlang.org" source: hosted - version: "0.9.7" + version: "1.0.0" mockito: dependency: "direct dev" description: name: mockito url: "https://pub.dartlang.org" source: hosted - version: "4.1.1" - node_interop: - dependency: transitive - description: - name: node_interop - url: "https://pub.dartlang.org" - source: hosted - version: "1.1.1" - node_io: - dependency: transitive - description: - name: node_io - url: "https://pub.dartlang.org" - source: hosted - version: "1.1.1" + version: "5.0.0" node_preamble: dependency: transitive description: name: node_preamble url: "https://pub.dartlang.org" source: hosted - version: "1.4.12" + version: "1.4.13" package_config: dependency: transitive description: name: package_config url: "https://pub.dartlang.org" source: hosted - version: "1.9.3" + version: "2.0.0" path: dependency: "direct dev" description: name: path url: "https://pub.dartlang.org" source: hosted - version: "1.7.0" + version: "1.8.0" pedantic: dependency: transitive description: name: pedantic url: "https://pub.dartlang.org" source: hosted - version: "1.9.2" + version: "1.11.0" pool: dependency: transitive description: name: pool url: "https://pub.dartlang.org" source: hosted - version: "1.4.0" + version: "1.5.0" pub_semver: dependency: transitive description: name: pub_semver url: "https://pub.dartlang.org" source: hosted - version: "1.4.4" + version: "2.0.0" pubspec_parse: dependency: transitive description: name: pubspec_parse url: "https://pub.dartlang.org" source: hosted - version: "0.1.5" - quiver: - dependency: transitive - description: - name: quiver - url: "https://pub.dartlang.org" - source: hosted - version: "2.1.3" + version: "0.1.8" shelf: dependency: transitive description: name: shelf url: "https://pub.dartlang.org" source: hosted - version: "0.7.9" + version: "1.0.0" shelf_packages_handler: dependency: transitive description: name: shelf_packages_handler url: "https://pub.dartlang.org" source: hosted - version: "2.0.0" + version: "3.0.0" shelf_static: dependency: transitive description: name: shelf_static url: "https://pub.dartlang.org" source: hosted - version: "0.2.8" + version: "1.0.0" shelf_web_socket: dependency: transitive description: name: shelf_web_socket url: "https://pub.dartlang.org" source: hosted - version: "0.2.3" + version: "1.0.0" source_gen: dependency: "direct main" description: @@ -392,132 +350,125 @@ packages: name: source_map_stack_trace url: "https://pub.dartlang.org" source: hosted - version: "2.0.0" + version: "2.1.0" source_maps: dependency: transitive description: name: source_maps url: "https://pub.dartlang.org" source: hosted - version: "0.10.9" + version: "0.10.10" source_span: dependency: transitive description: name: source_span url: "https://pub.dartlang.org" source: hosted - version: "1.7.0" + version: "1.8.1" stack_trace: dependency: transitive description: name: stack_trace url: "https://pub.dartlang.org" source: hosted - version: "1.9.5" + version: "1.10.0" stream_channel: dependency: transitive description: name: stream_channel url: "https://pub.dartlang.org" source: hosted - version: "2.0.0" + version: "2.1.0" stream_transform: dependency: transitive description: name: stream_transform url: "https://pub.dartlang.org" source: hosted - version: "1.2.0" + version: "2.0.0" string_scanner: dependency: transitive description: name: string_scanner url: "https://pub.dartlang.org" source: hosted - version: "1.0.5" + version: "1.1.0" term_glyph: dependency: transitive description: name: term_glyph url: "https://pub.dartlang.org" source: hosted - version: "1.1.0" + version: "1.2.0" test: dependency: "direct dev" description: name: test url: "https://pub.dartlang.org" source: hosted - version: "1.15.7" + version: "1.16.5" test_api: dependency: transitive description: name: test_api url: "https://pub.dartlang.org" source: hosted - version: "0.2.18+1" + version: "0.2.19" test_core: dependency: transitive description: name: test_core url: "https://pub.dartlang.org" source: hosted - version: "0.3.11+4" + version: "0.3.15" test_cov: dependency: "direct dev" description: name: test_cov url: "https://pub.dartlang.org" source: hosted - version: "0.2.1" - time: - dependency: transitive - description: - name: time - url: "https://pub.dartlang.org" - source: hosted - version: "1.3.0" + version: "1.0.1" typed_data: dependency: transitive description: name: typed_data url: "https://pub.dartlang.org" source: hosted - version: "1.2.0" + version: "1.3.0" vm_service: dependency: transitive description: name: vm_service url: "https://pub.dartlang.org" source: hosted - version: "4.2.0" + version: "6.1.0+1" watcher: dependency: transitive description: name: watcher url: "https://pub.dartlang.org" source: hosted - version: "0.9.7+15" + version: "1.0.0" web_socket_channel: dependency: transitive description: name: web_socket_channel url: "https://pub.dartlang.org" source: hosted - version: "1.1.0" + version: "2.0.0" webkit_inspection_protocol: dependency: transitive description: name: webkit_inspection_protocol url: "https://pub.dartlang.org" source: hosted - version: "0.7.3" + version: "1.0.0" yaml: dependency: transitive description: name: yaml url: "https://pub.dartlang.org" source: hosted - version: "2.2.1" + version: "3.1.0" sdks: - dart: ">=2.10.0 <3.0.0" + dart: ">=2.12.0 <3.0.0" diff --git a/floor_generator/pubspec.yaml b/floor_generator/pubspec.yaml index 932f0213..fe6cd17f 100644 --- a/floor_generator/pubspec.yaml +++ b/floor_generator/pubspec.yaml @@ -2,29 +2,29 @@ name: floor_generator description: > The typesafe, reactive, and lightweight SQLite abstraction for your Flutter applications. This library is the dev dependency. -version: 0.19.1 +version: 1.0.0-nullsafety.1 homepage: https://github.com/vitusortner/floor author: Vitus Ortner +publish_to: none environment: - sdk: '>=2.6.0 <3.0.0' + sdk: '>=2.12.0 <3.0.0' dependencies: - analyzer: ^0.41.2 - build: ^1.6.2 - build_config: ^0.4.5 + analyzer: ^1.1.0 + build: ^1.6.3 + build_config: ^0.4.6 code_builder: ^3.6.0 - collection: ^1.14.13 - meta: ^1.2.4 - source_gen: ^0.9.10+3 - dartx: ^0.5.0 + collection: ^1.15.0 floor_annotation: path: ../floor_annotation/ + meta: ^1.3.0 + source_gen: ^0.9.10+3 dev_dependencies: - build_test: ^1.3.6 - dart_style: ^1.3.12 - mockito: ^4.1.1 - path: ^1.7.0 - test_cov: ^0.2.1 - test: ^1.15.7 + build_test: ^1.3.7 + dart_style: ^1.3.14 + mockito: ^5.0.0 + path: ^1.8.0 + test: ^1.16.5 + test_cov: ^1.0.1 diff --git a/floor_generator/test/misc/extension/dart_type_extension_test.dart b/floor_generator/test/misc/extension/dart_type_extension_test.dart new file mode 100644 index 00000000..950a9e03 --- /dev/null +++ b/floor_generator/test/misc/extension/dart_type_extension_test.dart @@ -0,0 +1,22 @@ +import 'package:floor_generator/misc/extension/dart_type_extension.dart'; +import 'package:test/test.dart'; + +import '../../test_utils.dart'; + +void main() { + test('nullable string is nullable', () async { + final type = await getDartTypeFromDeclaration("final String? foo = '';"); + + final actual = type.isNullable; + + expect(actual, isTrue); + }); + + test('non-nullable string is non-nullable', () async { + final type = await getDartTypeFromDeclaration("final String foo = '';"); + + final actual = type.isNullable; + + expect(actual, isFalse); + }); +} diff --git a/floor_generator/test/misc/extension/foreign_key_action_extension_test.dart b/floor_generator/test/misc/extension/foreign_key_action_extension_test.dart index 44b79086..5131cc72 100644 --- a/floor_generator/test/misc/extension/foreign_key_action_extension_test.dart +++ b/floor_generator/test/misc/extension/foreign_key_action_extension_test.dart @@ -28,9 +28,5 @@ void main() { final actual = annotations.ForeignKeyAction.cascade.toSql(); expect(actual, equals('CASCADE')); }); - - test('null throws ArgumentError', () { - expect(() => null.toSql(), throwsArgumentError); - }); }); } diff --git a/floor_generator/test/misc/extension/iterable_extension_test.dart b/floor_generator/test/misc/extension/iterable_extension_test.dart new file mode 100644 index 00000000..e2cd58f4 --- /dev/null +++ b/floor_generator/test/misc/extension/iterable_extension_test.dart @@ -0,0 +1,53 @@ +import 'package:floor_generator/misc/extension/iterable_extension.dart'; +import 'package:test/test.dart'; + +void main() { + group('sortedByDescending', () { + test('sorts iterable descending by selector', () { + final actual = [_Box(4), _Box(0), _Box(2), _Box(1), _Box(3)] + .sortedByDescending((box) => box.number); + + expect(actual, equals([_Box(4), _Box(3), _Box(2), _Box(1), _Box(0)])); + }); + }); + + group('mapNotNull', () { + test('applies transformation yielding only non-null values', () { + final actual = [_NullableBox(0), _NullableBox(null), _NullableBox(1)] + .mapNotNull((box) => box.number); + + expect(actual, equals([0, 1])); + }); + }); + + group('distinctBy', () { + test('distincts values by selector', () { + final actual = [_Box(1), _Box(1), _Box(0), _Box(2), _Box(2)] + .distinctBy((box) => box.number); + + expect(actual, equals([_Box(1), _Box(0), _Box(2)])); + }); + }); +} + +class _Box { + final int number; + + _Box(this.number); + + @override + bool operator ==(Object other) => + identical(this, other) || + other is _Box && + runtimeType == other.runtimeType && + number == other.number; + + @override + int get hashCode => number.hashCode; +} + +class _NullableBox { + final int? number; + + _NullableBox(this.number); +} diff --git a/floor_generator/test/misc/extension/string_extension_test.dart b/floor_generator/test/misc/extension/string_extension_test.dart new file mode 100644 index 00000000..3cf948c9 --- /dev/null +++ b/floor_generator/test/misc/extension/string_extension_test.dart @@ -0,0 +1,24 @@ +import 'package:floor_generator/misc/extension/string_extension.dart'; +import 'package:test/test.dart'; + +void main() { + group('decapitalize', () { + test('returns empty string for empty string', () { + final actual = ''.decapitalize(); + + expect(actual, equals('')); + }); + + test('decapitalizes first character for single character string', () { + final actual = 'A'.decapitalize(); + + expect(actual, equals('a')); + }); + + test('decapitalize word (first letter to lowercase)', () { + final actual = 'FOO'.decapitalize(); + + expect(actual, equals('fOO')); + }); + }); +} diff --git a/floor_generator/test/misc/extension/type_converter_element_extension_test.dart b/floor_generator/test/misc/extension/type_converter_element_extension_test.dart index 2c8d9ac8..98d0de85 100644 --- a/floor_generator/test/misc/extension/type_converter_element_extension_test.dart +++ b/floor_generator/test/misc/extension/type_converter_element_extension_test.dart @@ -47,7 +47,7 @@ void main() { final actual = () => element.getTypeConverters(typeConverterScope); - expect(actual, throwsInvalidGenerationSourceError()); + expect(actual, throwsProcessorError()); }); test('throws error when empty list in annotation', () async { @@ -60,7 +60,7 @@ void main() { final actual = () => element.getTypeConverters(typeConverterScope); - expect(actual, throwsInvalidGenerationSourceError()); + expect(actual, throwsProcessorError()); }); test('throws error when element in annotation is not TypeConverter', @@ -74,7 +74,7 @@ void main() { final actual = () => element.getTypeConverters(typeConverterScope); - expect(actual, throwsInvalidGenerationSourceError()); + expect(actual, throwsProcessorError()); }); }); } diff --git a/floor_generator/test/misc/string_utils_test.dart b/floor_generator/test/misc/string_utils_test.dart deleted file mode 100644 index 537bfa45..00000000 --- a/floor_generator/test/misc/string_utils_test.dart +++ /dev/null @@ -1,10 +0,0 @@ -import 'package:floor_generator/misc/string_utils.dart'; -import 'package:test/test.dart'; - -void main() { - test('decapitalize word (first letter to lowercase)', () { - final actual = 'FOO'.decapitalize(); - - expect(actual, 'fOO'); - }); -} diff --git a/floor_generator/test/processor/dao_processor_test.dart b/floor_generator/test/processor/dao_processor_test.dart index 3a7ee51c..ccb3d2b5 100644 --- a/floor_generator/test/processor/dao_processor_test.dart +++ b/floor_generator/test/processor/dao_processor_test.dart @@ -1,3 +1,4 @@ +// ignore_for_file: import_of_legacy_library_into_null_safe import 'package:analyzer/dart/element/element.dart'; import 'package:build_test/build_test.dart'; import 'package:floor_annotation/floor_annotation.dart' as annotations; @@ -12,11 +13,13 @@ import 'package:source_gen/source_gen.dart'; import 'package:test/test.dart'; void main() { - List entities; - List views; + late List entities; + late List views; - setUpAll(() async => entities = await _getEntities()); - setUpAll(() async => views = await _getViews()); + setUpAll(() async { + entities = await _getEntities(); + views = await _getViews(); + }); test('Includes methods from abstract parent class', () async { final classElement = await _createDao(''' diff --git a/floor_generator/test/processor/database_processor_test.dart b/floor_generator/test/processor/database_processor_test.dart index 63ce4e5f..270e9099 100644 --- a/floor_generator/test/processor/database_processor_test.dart +++ b/floor_generator/test/processor/database_processor_test.dart @@ -1,3 +1,4 @@ +// ignore_for_file: import_of_legacy_library_into_null_safe import 'package:analyzer/dart/element/element.dart'; import 'package:build_test/build_test.dart'; import 'package:floor_generator/processor/database_processor.dart'; diff --git a/floor_generator/test/processor/entity_processor_test.dart b/floor_generator/test/processor/entity_processor_test.dart index 77c3b75d..37a5e801 100644 --- a/floor_generator/test/processor/entity_processor_test.dart +++ b/floor_generator/test/processor/entity_processor_test.dart @@ -1,3 +1,4 @@ +// ignore_for_file: import_of_legacy_library_into_null_safe import 'package:analyzer/dart/element/element.dart'; import 'package:build_test/build_test.dart'; import 'package:floor_annotation/floor_annotation.dart' as annotations; @@ -40,7 +41,7 @@ void main() { const foreignKeys = []; const indices = []; const constructor = "Person(row['id'] as int, row['name'] as String)"; - const valueMapping = "{'id': item.id, 'name': item.name}"; + const valueMapping = "{'id': item.id, 'name': item.name}"; final expected = Entity( classElement, name, @@ -78,7 +79,7 @@ void main() { const foreignKeys = []; const indices = []; const constructor = "Person(row['id'] as int, row['name'] as String)"; - const valueMapping = "{'id': item.id, 'name': item.name}"; + const valueMapping = "{'id': item.id, 'name': item.name}"; final expected = Entity( classElement, name, @@ -271,7 +272,7 @@ void main() { indices, true, constructor, - "{'id': item.id, 'name': item.name}", + "{'id': item.id, 'name': item.name}", null, ); expect(actual, equals(expected)); @@ -285,7 +286,6 @@ void main() { @primaryKey final int id; - @ColumnInfo(nullable: false) final bool isSomething; Person(this.id, this.isSomething); @@ -294,7 +294,7 @@ void main() { final actual = EntityProcessor(classElement, {}).process().valueMapping; - const expected = '{' + const expected = '{' "'id': item.id, " "'isSomething': item.isSomething ? 1 : 0" '}'; @@ -308,8 +308,7 @@ void main() { @primaryKey final int id; - @ColumnInfo(nullable: true) - final bool isSomething; + final bool? isSomething; Person(this.id, this.isSomething); } @@ -317,9 +316,9 @@ void main() { final actual = EntityProcessor(classElement, {}).process().valueMapping; - const expected = '{' + const expected = '{' "'id': item.id, " - "'isSomething': item.isSomething == null ? null : (item.isSomething ? 1 : 0)" + "'isSomething': item.isSomething == null ? null : (item.isSomething! ? 1 : 0)" '}'; expect(actual, equals(expected)); }); diff --git a/floor_generator/test/processor/field_processor_test.dart b/floor_generator/test/processor/field_processor_test.dart index de33cbf6..51fe5774 100644 --- a/floor_generator/test/processor/field_processor_test.dart +++ b/floor_generator/test/processor/field_processor_test.dart @@ -1,3 +1,4 @@ +// ignore_for_file: import_of_legacy_library_into_null_safe import 'package:analyzer/dart/element/element.dart'; import 'package:build_test/build_test.dart'; import 'package:floor_generator/misc/constants.dart'; @@ -17,6 +18,28 @@ void main() { final actual = FieldProcessor(fieldElement, null).process(); + const name = 'id'; + const columnName = 'id'; + const isNullable = false; + const sqlType = SqlType.integer; + final expected = Field( + fieldElement, + name, + columnName, + isNullable, + sqlType, + null, + ); + expect(actual, equals(expected)); + }); + + test('Process field with nullable Dart type', () async { + final fieldElement = await _generateFieldElement(''' + final int? id; + '''); + + final actual = FieldProcessor(fieldElement, null).process(); + const name = 'id'; const columnName = 'id'; const isNullable = true; @@ -70,7 +93,7 @@ void main() { const name = 'dateTime'; const columnName = 'dateTime'; - const isNullable = true; + const isNullable = false; const sqlType = SqlType.integer; // converted from DateTime final expected = Field( fieldElement, @@ -93,7 +116,7 @@ void main() { const name = 'dateTime'; const columnName = 'dateTime'; - const isNullable = true; + const isNullable = false; const sqlType = SqlType.integer; // converted from DateTime final typeConverter = TypeConverter( 'DateTimeConverter', @@ -129,7 +152,7 @@ void main() { const name = 'dateTime'; const columnName = 'dateTime'; - const isNullable = true; + const isNullable = false; const sqlType = SqlType.integer; // converted from DateTime final typeConverter = TypeConverter( 'DateTimeConverter', diff --git a/floor_generator/test/processor/insertion_method_processor_test.dart b/floor_generator/test/processor/insertion_method_processor_test.dart index 5c62e525..cc89f926 100644 --- a/floor_generator/test/processor/insertion_method_processor_test.dart +++ b/floor_generator/test/processor/insertion_method_processor_test.dart @@ -1,3 +1,4 @@ +// ignore_for_file: import_of_legacy_library_into_null_safe import 'package:floor_generator/processor/insertion_method_processor.dart'; import 'package:source_gen/source_gen.dart'; import 'package:test/test.dart'; diff --git a/floor_generator/test/processor/query_method_processor_test.dart b/floor_generator/test/processor/query_method_processor_test.dart index 87a063e2..07103606 100644 --- a/floor_generator/test/processor/query_method_processor_test.dart +++ b/floor_generator/test/processor/query_method_processor_test.dart @@ -1,3 +1,4 @@ +// ignore_for_file: import_of_legacy_library_into_null_safe import 'package:analyzer/dart/element/element.dart'; import 'package:build_test/build_test.dart'; import 'package:floor_annotation/floor_annotation.dart' as annotations; @@ -15,11 +16,13 @@ import 'package:test/test.dart'; import '../test_utils.dart'; void main() { - List entities; - List views; + late List entities; + late List views; - setUpAll(() async => entities = await _getEntities()); - setUpAll(() async => views = await _getViews()); + setUpAll(() async { + entities = await _getEntities(); + views = await _getViews(); + }); test('create query method', () async { final methodElement = await _createQueryMethodElement(''' @@ -79,7 +82,7 @@ void main() { test('parse query', () async { final methodElement = await _createQueryMethodElement(''' @Query('SELECT * FROM Person WHERE id = :id') - Future findPerson(int id); + Future findPerson(int id); '''); final actual = @@ -94,7 +97,7 @@ void main() { SELECT * FROM person WHERE id = :id AND custom_name = :name ''') - Future findPersonByIdAndName(int id, String name); + Future findPersonByIdAndName(int id, String name); """); final actual = @@ -110,7 +113,7 @@ void main() { final methodElement = await _createQueryMethodElement(''' @Query('SELECT * FROM person ' 'WHERE id = :id AND custom_name = :name') - Future findPersonByIdAndName(int id, String name); + Future findPersonByIdAndName(int id, String name); '''); final actual = @@ -232,7 +235,7 @@ void main() { test('exception when method does not return future', () async { final methodElement = await _createQueryMethodElement(''' @Query('SELECT * FROM Person') - List findAllPersons(); + List findAllPersons(); '''); final actual = () => @@ -276,7 +279,7 @@ void main() { () async { final methodElement = await _createQueryMethodElement(''' @Query('SELECT * FROM Person WHERE id = :id AND name = :name') - Future findPersonByIdAndName(int id); + Future findPersonByIdAndName(int id); '''); final actual = () => @@ -288,11 +291,28 @@ void main() { expect(actual, throwsInvalidGenerationSourceError(error)); }); + test('exception when passing nullable method parameter to query method', + () async { + final methodElement = await _createQueryMethodElement(''' + @Query('SELECT * FROM Person WHERE id = :id') + Future findPersonByIdAndName(int? id); + '''); + + final actual = () => + QueryMethodProcessor(methodElement, [...entities, ...views], {}) + .process(); + + final parameterElement = methodElement.parameters.first; + final error = QueryMethodProcessorError(methodElement) + .queryMethodParameterIsNullable(parameterElement); + expect(actual, throwsProcessorError(error)); + }); + test('exception when query arguments do not match method parameters', () async { final methodElement = await _createQueryMethodElement(''' @Query('SELECT * FROM Person WHERE id = :id') - Future findPersonByIdAndName(int id, String name); + Future findPersonByIdAndName(int id, String name); '''); final actual = () => @@ -303,6 +323,36 @@ void main() { .queryArgumentsAndMethodParametersDoNotMatch; expect(actual, throwsInvalidGenerationSourceError(error)); }); + + test( + 'throws when method returns Future of non-nullable type for single item query', + () async { + final methodElement = await _createQueryMethodElement(''' + @Query('SELECT * FROM Person WHERE id = :id') + Future findPersonById(int id); + '''); + + final actual = () => + QueryMethodProcessor(methodElement, [...entities, ...views], {}) + .process(); + + expect(actual, throwsProcessorError()); + }); + + test( + 'throws when method returns Stream of non-nullable type for single item query', + () async { + final methodElement = await _createQueryMethodElement(''' + @Query('SELECT * FROM Person WHERE id = :id') + Stream findPersonById(int id); + '''); + + final actual = () => + QueryMethodProcessor(methodElement, [...entities, ...views], {}) + .process(); + + expect(actual, throwsProcessorError()); + }); }); } diff --git a/floor_generator/test/processor/queryable_processor_test.dart b/floor_generator/test/processor/queryable_processor_test.dart index 24e65aaa..1cc6ebca 100644 --- a/floor_generator/test/processor/queryable_processor_test.dart +++ b/floor_generator/test/processor/queryable_processor_test.dart @@ -418,7 +418,7 @@ void main() { final String bar; - Person(this.id, this.name, {this.bar}); + Person(this.id, this.name, {required this.bar}); } '''); @@ -435,13 +435,12 @@ void main() { final int id; final String name; - - @ColumnInfo(nullable: false) + final bool bar; - - final bool foo - Person(this.id, this.name, {this.bar, this.foo}); + final bool? foo; + + Person(this.id, this.name, {required this.bar, this.foo}); } '''); @@ -461,7 +460,7 @@ void main() { final String bar; - Person({this.id, this.name, this.bar}); + Person({required this.id, required this.name, required this.bar}); } '''); @@ -479,7 +478,7 @@ void main() { final String name; - final String bar; + final String? bar; Person(this.id, this.name, [this.bar]); } @@ -488,18 +487,18 @@ void main() { final actual = TestProcessor(classElement).process().constructor; const expected = - "Person(row['id'] as int, row['name'] as String, row['bar'] as String)"; + "Person(row['id'] as int, row['name'] as String, row['bar'] as String?)"; expect(actual, equals(expected)); }); test('generate constructor with optional arguments', () async { final classElement = await createClassElement(''' class Person { - final int id; + final int? id; - final String name; + final String? name; - final String bar; + final String? bar; Person([this.id, this.name, this.bar]); } @@ -508,9 +507,59 @@ void main() { final actual = TestProcessor(classElement).process().constructor; const expected = - "Person(row['id'] as int, row['name'] as String, row['bar'] as String)"; + "Person(row['id'] as int?, row['name'] as String?, row['bar'] as String?)"; expect(actual, equals(expected)); }); + + group('nullability', () { + test('generates constructor with only nullable types', () async { + final classElement = await createClassElement(''' + class Person { + final int? id; + + final double? doubleId; + + final String? name; + + final bool? bar; + + final Uint8List? blob; + + Person(this.id, this.doubleId, this.name, this.bar, this.blob); + } + '''); + + final actual = TestProcessor(classElement).process().constructor; + + const expected = + "Person(row['id'] as int?, row['doubleId'] as double?, row['name'] as String?, row['bar'] == null ? null : (row['bar'] as int) != 0, row['blob'] as Uint8List?)"; + expect(actual, equals(expected)); + }); + + test('generates constructor with only non-nullable types', () async { + final classElement = await createClassElement(''' + class Person { + final int id; + + final double doubleId; + + final String name; + + final bool bar; + + final Uint8List blob; + + Person(this.id, this.doubleId, this.name, this.bar, this.blob); + } + '''); + + final actual = TestProcessor(classElement).process().constructor; + + const expected = + "Person(row['id'] as int, row['doubleId'] as double, row['name'] as String, (row['bar'] as int) != 0, row['blob'] as Uint8List)"; + expect(actual, equals(expected)); + }); + }); }); group('@Ignore', () { @@ -522,7 +571,7 @@ void main() { final String name; @ignore - String foo; + String? foo; Person(this.id, this.name); } @@ -545,7 +594,7 @@ void main() { final String name; @ignore - String foo; + String? foo; Person(this.id, this.name, [this.foo = 'foo']); } @@ -588,7 +637,7 @@ class TestQueryable extends Queryable { class TestProcessor extends QueryableProcessor { TestProcessor( ClassElement classElement, [ - Set typeConverters, + Set? typeConverters, ]) : super(classElement, typeConverters ?? {}); @override diff --git a/floor_generator/test/processor/transaction_method_processor_test.dart b/floor_generator/test/processor/transaction_method_processor_test.dart index 46236b24..9979599e 100644 --- a/floor_generator/test/processor/transaction_method_processor_test.dart +++ b/floor_generator/test/processor/transaction_method_processor_test.dart @@ -1,3 +1,4 @@ +// ignore_for_file: import_of_legacy_library_into_null_safe import 'package:analyzer/dart/element/element.dart'; import 'package:build_test/build_test.dart'; import 'package:floor_generator/processor/error/transaction_method_processor_error.dart'; diff --git a/floor_generator/test/processor/type_converter_processor_test.dart b/floor_generator/test/processor/type_converter_processor_test.dart index c88323a1..8e1f2b8d 100644 --- a/floor_generator/test/processor/type_converter_processor_test.dart +++ b/floor_generator/test/processor/type_converter_processor_test.dart @@ -59,7 +59,7 @@ void main() { TypeConverterScope.dao, ).process(); - expect(actual, throwsInvalidGenerationSourceError()); + expect(actual, throwsProcessorError()); }); }); } diff --git a/floor_generator/test/processor/update_method_processor_test.dart b/floor_generator/test/processor/update_method_processor_test.dart index 3e6abeb0..dfc0ad2b 100644 --- a/floor_generator/test/processor/update_method_processor_test.dart +++ b/floor_generator/test/processor/update_method_processor_test.dart @@ -1,3 +1,4 @@ +// ignore_for_file: import_of_legacy_library_into_null_safe import 'package:floor_generator/processor/update_method_processor.dart'; import 'package:source_gen/source_gen.dart'; import 'package:test/test.dart'; diff --git a/floor_generator/test/test_utils.dart b/floor_generator/test/test_utils.dart index 63042694..789e20d6 100644 --- a/floor_generator/test/test_utils.dart +++ b/floor_generator/test/test_utils.dart @@ -1,3 +1,4 @@ +// ignore_for_file: import_of_legacy_library_into_null_safe import 'dart:io'; import 'package:analyzer/dart/element/element.dart'; @@ -10,6 +11,7 @@ import 'package:floor_annotation/floor_annotation.dart' as annotations; import 'package:floor_generator/misc/type_utils.dart'; import 'package:floor_generator/processor/dao_processor.dart'; import 'package:floor_generator/processor/entity_processor.dart'; +import 'package:floor_generator/processor/error/processor_error.dart'; import 'package:floor_generator/processor/view_processor.dart'; import 'package:floor_generator/value_object/dao.dart'; import 'package:floor_generator/value_object/entity.dart'; @@ -33,16 +35,7 @@ Future resolveCompilationUnit(final String sourceFile) async { } Future getDartType(final dynamic value) async { - final source = ''' - library test; - import 'dart:typed_data'; - - final value = $value; - '''; - return resolveSource(source, (item) async { - final libraryReader = LibraryReader(await item.findLibraryByName('test')); - return (libraryReader.allElements.elementAt(1) as VariableElement).type; - }); + return getDartTypeFromDeclaration('final value = $value'); } Future getDartTypeFromString(final String value) { @@ -98,6 +91,19 @@ Future getDartTypeWithName(String value) async { }); } +Future getDartTypeFromDeclaration(final String declaration) async { + final source = ''' + library test; + import 'dart:typed_data'; + + $declaration; + '''; + return resolveSource(source, (item) async { + final libraryReader = LibraryReader(await item.findLibraryByName('test')); + return (libraryReader.allElements.elementAt(1) as VariableElement).type; + }); +} + final _dartfmt = DartFormatter(); String _format(final String source) { @@ -112,7 +118,7 @@ String _format(final String source) { void useDartfmt() => EqualsDart.format = _format; Matcher throwsInvalidGenerationSourceError([ - final InvalidGenerationSourceError error, + final InvalidGenerationSourceError? error, ]) { const typeMatcher = TypeMatcher(); if (error == null) { @@ -127,6 +133,22 @@ Matcher throwsInvalidGenerationSourceError([ } } +Matcher throwsProcessorError([ + final ProcessorError? error, +]) { + const typeMatcher = TypeMatcher(); + if (error == null) { + return throwsA(typeMatcher); + } else { + return throwsA( + typeMatcher + .having((e) => e.message, 'message', error.message) + .having((e) => e.todo, 'todo', error.todo) + .having((e) => e.element, 'element', error.element), + ); + } +} + Future createDao(final String methodSignature) async { final library = await resolveSource(''' library test; @@ -166,6 +188,7 @@ Future createClassElement(final String clazz) async { final library = await resolveSource(''' library test; + import 'dart:typed_data'; import 'package:floor_annotation/floor_annotation.dart'; $clazz diff --git a/floor_generator/test/value_object/fts_test.dart b/floor_generator/test/value_object/fts_test.dart index a0c7f3d8..9c649d42 100644 --- a/floor_generator/test/value_object/fts_test.dart +++ b/floor_generator/test/value_object/fts_test.dart @@ -6,7 +6,7 @@ void main() { test('Fts3 Definition', () { final Fts fts = Fts3( 'simple', - null, + [], ); final usingOptionActual = fts.usingOption; diff --git a/floor_generator/test/writer/dao_writer_test.dart b/floor_generator/test/writer/dao_writer_test.dart index bbf63d1a..370bea74 100644 --- a/floor_generator/test/writer/dao_writer_test.dart +++ b/floor_generator/test/writer/dao_writer_test.dart @@ -1,3 +1,4 @@ +// ignore_for_file: import_of_legacy_library_into_null_safe import 'package:build_test/build_test.dart'; import 'package:code_builder/code_builder.dart'; import 'package:floor_annotation/floor_annotation.dart' as annotations; @@ -7,10 +8,12 @@ import 'package:floor_generator/processor/entity_processor.dart'; import 'package:floor_generator/processor/view_processor.dart'; import 'package:floor_generator/value_object/dao.dart'; import 'package:floor_generator/value_object/entity.dart'; +import 'package:floor_generator/value_object/primary_key.dart'; import 'package:floor_generator/writer/dao_writer.dart'; import 'package:source_gen/source_gen.dart'; import 'package:test/test.dart'; +import '../mocks.dart'; import '../test_utils.dart'; void main() { @@ -46,19 +49,19 @@ void main() { database, 'Person', (Person item) => - {'id': item.id, 'name': item.name}), + {'id': item.id, 'name': item.name}), _personUpdateAdapter = UpdateAdapter( database, 'Person', ['id'], (Person item) => - {'id': item.id, 'name': item.name}), + {'id': item.id, 'name': item.name}), _personDeletionAdapter = DeletionAdapter( database, 'Person', ['id'], (Person item) => - {'id': item.id, 'name': item.name}); + {'id': item.id, 'name': item.name}); final sqflite.DatabaseExecutor database; @@ -74,7 +77,7 @@ void main() { @override Future> findAllPersons() async { - return _queryAdapter.queryList('SELECT * FROM person', mapper: (Map row) => Person(row['id'] as int, row['name'] as String)); + return _queryAdapter.queryList('SELECT * FROM person', mapper: (Map row) => Person(row['id'] as int, row['name'] as String)); } @override @@ -125,21 +128,21 @@ void main() { database, 'Person', (Person item) => - {'id': item.id, 'name': item.name}, + {'id': item.id, 'name': item.name}, changeListener), _personUpdateAdapter = UpdateAdapter( database, 'Person', ['id'], (Person item) => - {'id': item.id, 'name': item.name}, + {'id': item.id, 'name': item.name}, changeListener), _personDeletionAdapter = DeletionAdapter( database, 'Person', ['id'], (Person item) => - {'id': item.id, 'name': item.name}, + {'id': item.id, 'name': item.name}, changeListener); final sqflite.DatabaseExecutor database; @@ -156,7 +159,7 @@ void main() { @override Stream> findAllPersonsAsStream() { - return _queryAdapter.queryListStream('SELECT * FROM person', queryableName: 'Person', isView: false, mapper: (Map row) => Person(row['id'] as int, row['name'] as String)); + return _queryAdapter.queryListStream('SELECT * FROM person', queryableName: 'Person', isView: false, mapper: (Map row) => Person(row['id'] as int, row['name'] as String)); } @override @@ -202,21 +205,21 @@ void main() { database, 'Person', (Person item) => - {'id': item.id, 'name': item.name}, + {'id': item.id, 'name': item.name}, changeListener), _personUpdateAdapter = UpdateAdapter( database, 'Person', ['id'], (Person item) => - {'id': item.id, 'name': item.name}, + {'id': item.id, 'name': item.name}, changeListener), _personDeletionAdapter = DeletionAdapter( database, 'Person', ['id'], (Person item) => - {'id': item.id, 'name': item.name}, + {'id': item.id, 'name': item.name}, changeListener); final sqflite.DatabaseExecutor database; @@ -263,16 +266,16 @@ void main() { '''); // simulate DB is aware of another streamed Entity and no View final otherEntity = Entity( - null, // classElement, - 'Dog', // name, - [], // fields, - null, // primaryKey, - [], // foreignKeys, - [], // indices, - false, // withoutRowid, - '', // constructor - '', // valueMapping - null, // fts + MockClassElement(), + 'Dog', + [], + PrimaryKey([], false), + [], + [], + false, + '', + '', + null, ); final actual = DaoWriter(dao, {otherEntity}, false).write(); @@ -283,19 +286,19 @@ void main() { database, 'Person', (Person item) => - {'id': item.id, 'name': item.name}), + {'id': item.id, 'name': item.name}), _personUpdateAdapter = UpdateAdapter( database, 'Person', ['id'], (Person item) => - {'id': item.id, 'name': item.name}), + {'id': item.id, 'name': item.name}), _personDeletionAdapter = DeletionAdapter( database, 'Person', ['id'], (Person item) => - {'id': item.id, 'name': item.name}); + {'id': item.id, 'name': item.name}); final sqflite.DatabaseExecutor database; @@ -349,21 +352,21 @@ void main() { database, 'Person', (Person item) => - {'id': item.id, 'name': item.name}, + {'id': item.id, 'name': item.name}, changeListener), _personUpdateAdapter = UpdateAdapter( database, 'Person', ['id'], (Person item) => - {'id': item.id, 'name': item.name}, + {'id': item.id, 'name': item.name}, changeListener), _personDeletionAdapter = DeletionAdapter( database, 'Person', ['id'], (Person item) => - {'id': item.id, 'name': item.name}, + {'id': item.id, 'name': item.name}, changeListener); final sqflite.DatabaseExecutor database; diff --git a/floor_generator/test/writer/database_builder_writer_test.dart b/floor_generator/test/writer/database_builder_writer_test.dart index bf9f9990..7f8caa7a 100644 --- a/floor_generator/test/writer/database_builder_writer_test.dart +++ b/floor_generator/test/writer/database_builder_writer_test.dart @@ -1,3 +1,4 @@ +// ignore_for_file: import_of_legacy_library_into_null_safe import 'package:code_builder/code_builder.dart'; import 'package:floor_generator/writer/database_builder_writer.dart'; import 'package:test/test.dart'; @@ -16,11 +17,11 @@ void main() { class _$FooBarBuilder { _$FooBarBuilder(this.name); - final String name; + final String? name; final List _migrations = []; - Callback _callback; + Callback? _callback; /// Adds migrations to the builder. _$FooBarBuilder addMigrations(List migrations) { @@ -37,7 +38,7 @@ void main() { /// Creates the database and initializes it. Future build() async { final path = name != null - ? await sqfliteDatabaseFactory.getDatabasePath(name) + ? await sqfliteDatabaseFactory.getDatabasePath(name!) : ':memory:'; final database = _$FooBar(); database.database = await database.open( diff --git a/floor_generator/test/writer/database_writer_test.dart b/floor_generator/test/writer/database_writer_test.dart index bb89312e..8e900148 100644 --- a/floor_generator/test/writer/database_writer_test.dart +++ b/floor_generator/test/writer/database_writer_test.dart @@ -1,3 +1,4 @@ +// ignore_for_file: import_of_legacy_library_into_null_safe import 'package:build_test/build_test.dart'; import 'package:code_builder/code_builder.dart'; import 'package:floor_generator/processor/database_processor.dart'; @@ -31,12 +32,12 @@ void main() { expect(actual, equalsDart(r''' class _$TestDatabase extends TestDatabase { - _$TestDatabase([StreamController listener]) { + _$TestDatabase([StreamController? listener]) { changeListener = listener ?? StreamController.broadcast(); } Future open(String path, List migrations, - [Callback callback]) async { + [Callback? callback]) async { final databaseOptions = sqflite.OpenDatabaseOptions( version: 1, onConfigure: (database) async { @@ -53,7 +54,7 @@ void main() { }, onCreate: (database, version) async { await database.execute( - 'CREATE TABLE IF NOT EXISTS `Person` (`id` INTEGER, `name` TEXT, PRIMARY KEY (`id`))'); + 'CREATE TABLE IF NOT EXISTS `Person` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, PRIMARY KEY (`id`))'); await callback?.onCreate?.call(database, version); }, @@ -72,9 +73,9 @@ void main() { @Entity(tableName: 'custom_table_name') class Person { @PrimaryKey(autoGenerate: true) - final int id; + final int? id; - @ColumnInfo(name: 'custom_name', nullable: false) + @ColumnInfo(name: 'custom_name') final String name; Person(this.id, this.name); @@ -85,12 +86,12 @@ void main() { expect(actual, equalsDart(r''' class _$TestDatabase extends TestDatabase { - _$TestDatabase([StreamController listener]) { + _$TestDatabase([StreamController? listener]) { changeListener = listener ?? StreamController.broadcast(); } Future open(String path, List migrations, - [Callback callback]) async { + [Callback? callback]) async { final databaseOptions = sqflite.OpenDatabaseOptions( version: 1, onConfigure: (database) async { @@ -147,12 +148,12 @@ void main() { expect(actual, equalsDart(r""" class _$TestDatabase extends TestDatabase { - _$TestDatabase([StreamController listener]) { + _$TestDatabase([StreamController? listener]) { changeListener = listener ?? StreamController.broadcast(); } Future open(String path, List migrations, - [Callback callback]) async { + [Callback? callback]) async { final databaseOptions = sqflite.OpenDatabaseOptions( version: 1, onConfigure: (database) async { @@ -169,7 +170,7 @@ void main() { }, onCreate: (database, version) async { await database.execute( - 'CREATE TABLE IF NOT EXISTS `Person` (`id` INTEGER, `name` TEXT, PRIMARY KEY (`id`))'); + 'CREATE TABLE IF NOT EXISTS `Person` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, PRIMARY KEY (`id`))'); await database.execute( '''CREATE VIEW IF NOT EXISTS `names` AS SELECT custom_name as name FROM person'''); diff --git a/floor_generator/test/writer/deletion_method_writer_test.dart b/floor_generator/test/writer/deletion_method_writer_test.dart index 64fbcf31..a6fa150e 100644 --- a/floor_generator/test/writer/deletion_method_writer_test.dart +++ b/floor_generator/test/writer/deletion_method_writer_test.dart @@ -1,3 +1,4 @@ +// ignore_for_file: import_of_legacy_library_into_null_safe import 'package:code_builder/code_builder.dart'; import 'package:floor_generator/value_object/deletion_method.dart'; import 'package:floor_generator/writer/deletion_method_writer.dart'; diff --git a/floor_generator/test/writer/floor_writer_test.dart b/floor_generator/test/writer/floor_writer_test.dart index 59be02d9..7f5ffcab 100644 --- a/floor_generator/test/writer/floor_writer_test.dart +++ b/floor_generator/test/writer/floor_writer_test.dart @@ -1,3 +1,4 @@ +// ignore_for_file: import_of_legacy_library_into_null_safe import 'package:code_builder/code_builder.dart'; import 'package:floor_generator/writer/floor_writer.dart'; import 'package:test/test.dart'; diff --git a/floor_generator/test/writer/insert_method_writer_test.dart b/floor_generator/test/writer/insert_method_writer_test.dart index 94484024..ad2357a8 100644 --- a/floor_generator/test/writer/insert_method_writer_test.dart +++ b/floor_generator/test/writer/insert_method_writer_test.dart @@ -1,3 +1,4 @@ +// ignore_for_file: import_of_legacy_library_into_null_safe import 'package:code_builder/code_builder.dart'; import 'package:floor_generator/value_object/insertion_method.dart'; import 'package:floor_generator/writer/insertion_method_writer.dart'; diff --git a/floor_generator/test/writer/query_method_writer_test.dart b/floor_generator/test/writer/query_method_writer_test.dart index aee2e324..7890bcd0 100644 --- a/floor_generator/test/writer/query_method_writer_test.dart +++ b/floor_generator/test/writer/query_method_writer_test.dart @@ -1,3 +1,4 @@ +// ignore_for_file: import_of_legacy_library_into_null_safe import 'package:build_test/build_test.dart'; import 'package:code_builder/code_builder.dart'; import 'package:floor_annotation/floor_annotation.dart' as annotations; @@ -44,7 +45,7 @@ void main() { expect(actual, equalsDart(r''' @override Future deletePersonById(int id) async { - await _queryAdapter.queryNoReturn('DELETE FROM Person WHERE id = ?', arguments: [id]); + await _queryAdapter.queryNoReturn('DELETE FROM Person WHERE id = ?', arguments: [id]); } ''')); }); @@ -52,15 +53,15 @@ void main() { test('query item', () async { final queryMethod = await _createQueryMethod(''' @Query('SELECT * FROM Person WHERE id = :id') - Future findById(int id); + Future findById(int id); '''); final actual = QueryMethodWriter(queryMethod).write(); expect(actual, equalsDart(r''' @override - Future findById(int id) async { - return _queryAdapter.query('SELECT * FROM Person WHERE id = ?', arguments: [id], mapper: (Map row) => Person(row['id'] as int, row['name'] as String)); + Future findById(int id) async { + return _queryAdapter.query('SELECT * FROM Person WHERE id = ?', arguments: [id], mapper: (Map row) => Person(row['id'] as int, row['name'] as String)); } ''')); }); @@ -75,7 +76,7 @@ void main() { ); final queryMethod = await ''' @Query('SELECT * FROM Order WHERE id = :id') - Future findById(int id); + Future findById(int id); ''' .asOrderQueryMethod({typeConverter}); @@ -83,8 +84,8 @@ void main() { expect(actual, equalsDart(r''' @override - Future findById(int id) async { - return _queryAdapter.query('SELECT * FROM Order WHERE id = ?', arguments: [id], mapper: (Map row) => Order(row['id'] as int, _dateTimeConverter.decode(row['dateTime'] as int))); + Future findById(int id) async { + return _queryAdapter.query('SELECT * FROM Order WHERE id = ?', arguments: [id], mapper: (Map row) => Order(row['id'] as int, _dateTimeConverter.decode(row['dateTime'] as int))); } ''')); }); @@ -99,7 +100,7 @@ void main() { final queryMethod = await ''' @TypeConverters([DateTimeConverter]) @Query('SELECT * FROM Order WHERE dateTime = :dateTime') - Future findByDateTime(DateTime dateTime); + Future findByDateTime(DateTime dateTime); ''' .asOrderQueryMethod({typeConverter}); @@ -107,8 +108,8 @@ void main() { expect(actual, equalsDart(r''' @override - Future findByDateTime(DateTime dateTime) async { - return _queryAdapter.query('SELECT * FROM Order WHERE dateTime = ?', arguments: [_dateTimeConverter.encode(dateTime)], mapper: (Map row) => Order(row['id'] as int, _externalTypeConverter.decode(row['dateTime'] as int))); + Future findByDateTime(DateTime dateTime) async { + return _queryAdapter.query('SELECT * FROM Order WHERE dateTime = ?', arguments: [_dateTimeConverter.encode(dateTime)], mapper: (Map row) => Order(row['id'] as int, _externalTypeConverter.decode(row['dateTime'] as int))); } ''')); }); @@ -123,7 +124,7 @@ void main() { ); final queryMethod = await ''' @Query('SELECT * FROM Order WHERE dateTime = :dateTime') - Future findByDateTime(@TypeConverters([DateTimeConverter]) DateTime dateTime); + Future findByDateTime(@TypeConverters([DateTimeConverter]) DateTime dateTime); ''' .asOrderQueryMethod({typeConverter}); @@ -131,8 +132,8 @@ void main() { expect(actual, equalsDart(r''' @override - Future findByDateTime(DateTime dateTime) async { - return _queryAdapter.query('SELECT * FROM Order WHERE dateTime = ?', arguments: [_dateTimeConverter.encode(dateTime)], mapper: (Map row) => Order(row['id'] as int, _externalTypeConverter.decode(row['dateTime'] as int))); + Future findByDateTime(DateTime dateTime) async { + return _queryAdapter.query('SELECT * FROM Order WHERE dateTime = ?', arguments: [_dateTimeConverter.encode(dateTime)], mapper: (Map row) => Order(row['id'] as int, _externalTypeConverter.decode(row['dateTime'] as int))); } ''')); }); @@ -157,7 +158,7 @@ void main() { @override Future> findByDates(List dates) async { final valueList0 = dates.map((value) => "'${_dateTimeConverter.encode(value)}'").join(', '); - return _queryAdapter.queryList('SELECT * FROM Order WHERE date IN ($valueList0)', mapper: (Map row) => Order(row['id'] as int, _dateTimeConverter.decode(row['dateTime'] as int))); + return _queryAdapter.queryList('SELECT * FROM Order WHERE date IN ($valueList0)', mapper: (Map row) => Order(row['id'] as int, _dateTimeConverter.decode(row['dateTime'] as int))); } ''')); }); @@ -174,7 +175,7 @@ void main() { expect(actual, equalsDart(r''' @override Future> findWithFlag(bool flag) async { - return _queryAdapter.queryList('SELECT * FROM Person WHERE flag = ?', arguments: [flag == null ? null : (flag ? 1 : 0)], mapper: (Map row) => Person(row['id'] as int, row['name'] as String)); + return _queryAdapter.queryList('SELECT * FROM Person WHERE flag = ?', arguments: [flag ? 1 : 0], mapper: (Map row) => Person(row['id'] as int, row['name'] as String)); } ''')); }); @@ -182,15 +183,15 @@ void main() { test('query item multiple parameters', () async { final queryMethod = await _createQueryMethod(''' @Query('SELECT * FROM Person WHERE id = :id AND name = :name') - Future findById(int id, String name); + Future findById(int id, String name); '''); final actual = QueryMethodWriter(queryMethod).write(); expect(actual, equalsDart(r''' @override - Future findById(int id, String name) async { - return _queryAdapter.query('SELECT * FROM Person WHERE id = ? AND name = ?', arguments: [id, name], mapper: (Map row) => Person(row['id'] as int, row['name'] as String)); + Future findById(int id, String name) async { + return _queryAdapter.query('SELECT * FROM Person WHERE id = ? AND name = ?', arguments: [id, name], mapper: (Map row) => Person(row['id'] as int, row['name'] as String)); } ''')); }); @@ -206,7 +207,7 @@ void main() { expect(actual, equalsDart(''' @override Future> findAll() async { - return _queryAdapter.queryList('SELECT * FROM Person', mapper: (Map row) => Person(row['id'] as int, row['name'] as String)); + return _queryAdapter.queryList('SELECT * FROM Person', mapper: (Map row) => Person(row['id'] as int, row['name'] as String)); } ''')); }); @@ -214,15 +215,15 @@ void main() { test('query item stream', () async { final queryMethod = await _createQueryMethod(''' @Query('SELECT * FROM Person WHERE id = :id') - Stream findByIdAsStream(int id); + Stream findByIdAsStream(int id); '''); final actual = QueryMethodWriter(queryMethod).write(); expect(actual, equalsDart(r''' @override - Stream findByIdAsStream(int id) { - return _queryAdapter.queryStream('SELECT * FROM Person WHERE id = ?', arguments: [id], queryableName: 'Person', isView: false, mapper: (Map row) => Person(row['id'] as int, row['name'] as String)); + Stream findByIdAsStream(int id) { + return _queryAdapter.queryStream('SELECT * FROM Person WHERE id = ?', arguments: [id], queryableName: 'Person', isView: false, mapper: (Map row) => Person(row['id'] as int, row['name'] as String)); } ''')); }); @@ -238,7 +239,7 @@ void main() { expect(actual, equalsDart(r''' @override Stream> findAllAsStream() { - return _queryAdapter.queryListStream('SELECT * FROM Person', queryableName: 'Person', isView: false, mapper: (Map row) => Person(row['id'] as int, row['name'] as String)); + return _queryAdapter.queryListStream('SELECT * FROM Person', queryableName: 'Person', isView: false, mapper: (Map row) => Person(row['id'] as int, row['name'] as String)); } ''')); }); @@ -254,7 +255,7 @@ void main() { expect(actual, equalsDart(r''' @override Stream> findAllAsStream() { - return _queryAdapter.queryListStream('SELECT * FROM Name', queryableName: 'Name', isView: true, mapper: (Map row) => Name(row['name'] as String)); + return _queryAdapter.queryListStream('SELECT * FROM Name', queryableName: 'Name', isView: true, mapper: (Map row) => Name(row['name'] as String)); } ''')); }); @@ -271,7 +272,7 @@ void main() { @override Future> findWithIds(List ids) async { final valueList0 = ids.map((value) => "'$value'").join(', '); - return _queryAdapter.queryList('SELECT * FROM Person WHERE id IN ($valueList0)', mapper: (Map row) => Person(row['id'] as int, row['name'] as String)); + return _queryAdapter.queryList('SELECT * FROM Person WHERE id IN ($valueList0)', mapper: (Map row) => Person(row['id'] as int, row['name'] as String)); } ''')); }); @@ -288,7 +289,7 @@ void main() { @override Future> findWithIds(List ids) async { final valueList0 = ids.map((value) => "'$value'").join(', '); - return _queryAdapter.queryList('SELECT * FROM Person WHERE id IN($valueList0)', mapper: (Map row) => Person(row['id'] as int, row['name'] as String)); + return _queryAdapter.queryList('SELECT * FROM Person WHERE id IN($valueList0)', mapper: (Map row) => Person(row['id'] as int, row['name'] as String)); } ''')); }); @@ -306,7 +307,7 @@ void main() { Future> findWithIds(List ids, List idx) async { final valueList0 = ids.map((value) => "'$value'").join(', '); final valueList1 = idx.map((value) => "'$value'").join(', '); - return _queryAdapter.queryList('SELECT * FROM Person WHERE id IN ($valueList0) AND id IN ($valueList1)', mapper: (Map row) => Person(row['id'] as int, row['name'] as String)); + return _queryAdapter.queryList('SELECT * FROM Person WHERE id IN ($valueList0) AND id IN ($valueList1)', mapper: (Map row) => Person(row['id'] as int, row['name'] as String)); } ''')); }); @@ -314,7 +315,7 @@ void main() { test('query with unsupported type throws', () async { final queryMethod = await _createQueryMethod(''' @Query('SELECT * FROM Person WHERE id = :person') - Future findById(Person person); + Future findById(Person person); '''); final actual = () => QueryMethodWriter(queryMethod).write(); diff --git a/floor_generator/test/writer/transaction_method_writer_test.dart b/floor_generator/test/writer/transaction_method_writer_test.dart index 83960745..5356b32d 100644 --- a/floor_generator/test/writer/transaction_method_writer_test.dart +++ b/floor_generator/test/writer/transaction_method_writer_test.dart @@ -1,3 +1,4 @@ +// ignore_for_file: import_of_legacy_library_into_null_safe import 'package:code_builder/code_builder.dart'; import 'package:floor_generator/value_object/transaction_method.dart'; import 'package:floor_generator/writer/transaction_method_writer.dart'; diff --git a/floor_generator/test/writer/type_converter_field_writer_test.dart b/floor_generator/test/writer/type_converter_field_writer_test.dart index 006b0572..715b8cbd 100644 --- a/floor_generator/test/writer/type_converter_field_writer_test.dart +++ b/floor_generator/test/writer/type_converter_field_writer_test.dart @@ -1,3 +1,4 @@ +// ignore_for_file: import_of_legacy_library_into_null_safe import 'package:code_builder/code_builder.dart'; import 'package:floor_generator/writer/type_converter_field_writer.dart'; import 'package:test/test.dart'; diff --git a/floor_generator/test/writer/update_method_writer_test.dart b/floor_generator/test/writer/update_method_writer_test.dart index 8f2be131..371d6dc9 100644 --- a/floor_generator/test/writer/update_method_writer_test.dart +++ b/floor_generator/test/writer/update_method_writer_test.dart @@ -1,3 +1,4 @@ +// ignore_for_file: import_of_legacy_library_into_null_safe import 'package:code_builder/code_builder.dart'; import 'package:floor_generator/value_object/update_method.dart'; import 'package:floor_generator/writer/update_method_writer.dart'; diff --git a/mkdocs.yml b/mkdocs.yml index 4ba1628b..139d61cf 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -40,6 +40,7 @@ nav: - Database Views: database-views.md - Data Access Objects: daos.md - Type Converters: type-converters.md + - Null Safety: null-safety.md - Migrations: migrations.md - In Memory Database: in-memory-database.md - Initialization Callback: initialization-callback.md diff --git a/tool/analyze.sh b/tool/analyze.sh new file mode 100644 index 00000000..7f2fc080 --- /dev/null +++ b/tool/analyze.sh @@ -0,0 +1,18 @@ +#!/bin/bash + +cd .. + +cd floor +flutter analyze +cd .. + +cd floor_annotation +dartanalyzer --fatal-infos --fatal-warnings . +cd .. + +cd floor_generator +dartanalyzer --fatal-infos --fatal-warnings . +cd .. + +cd example +flutter analyze