From 2c63c1a64e6288e7fea6bf745fff172d63bdd3cc Mon Sep 17 00:00:00 2001 From: Simon Binder Date: Sun, 22 Jan 2023 22:53:45 +0100 Subject: [PATCH] Update docs for Dart components in SQL Scopes components are enabled by default, so the warning is no longer necessary. --- docs/build.yaml | 51 +- .../snippets/modular/drift/dart_example.dart | 13 + docs/lib/snippets/modular/drift/example.drift | 18 + .../snippets/modular/drift/example.drift.dart | 474 ++++++++++++++++++ docs/pages/docs/Using SQL/drift_files.md | 58 +-- docs/pubspec.yaml | 2 +- docs/templates/blocks/snippet.html | 4 +- .../lib/src/backends/build/drift_builder.dart | 2 +- drift_dev/lib/src/writer/import_manager.dart | 27 +- .../test/writer/import_manager_test.dart | 48 ++ 10 files changed, 631 insertions(+), 66 deletions(-) create mode 100644 docs/lib/snippets/modular/drift/dart_example.dart create mode 100644 docs/lib/snippets/modular/drift/example.drift create mode 100644 docs/lib/snippets/modular/drift/example.drift.dart create mode 100644 drift_dev/test/writer/import_manager_test.dart diff --git a/docs/build.yaml b/docs/build.yaml index 025fdbad..cbd6f714 100644 --- a/docs/build.yaml +++ b/docs/build.yaml @@ -24,28 +24,57 @@ builders: release: true targets: - # We run drift and other builders first, syntax higlighting is more - # accurate if the generated classes exist. - source_gen: + prepare: + auto_apply_builders: false + builders: + ":versions": + enabled: true + drift_dev:preparing_builder: + enabled: true + sources: + - "$package$" + - "lib/versions.json" + - "lib/snippets/**" + - "tool/write_versions.dart" + - "tool/snippets.dart" + - "test/generated/**" + + codegen: + dependencies: [":prepare"] auto_apply_builders: false builders: drift_dev:preparing_builder: + enabled: false # Runs in prepare target + + # Modular drift generation, suitable for standalone snippets that aren't part of a database + drift_dev:analyzer: enabled: true + options: &options + generate_connect_constructor: true + generate_for: + include: &modular + - "lib/snippets/modular/**" + drift_dev:modular: + enabled: true + options: *options + generate_for: + include: *modular + + # Non-modular drift generation. Used for some "getting started" e2e examples. drift_dev:drift_dev: enabled: true - options: - generate_connect_constructor: true + options: *options + generate_for: + exclude: *modular json_serializable: enabled: true sources: - lib/** - test/generated/** - prepare: - dependencies: [":source_gen"] + syntax_highlighting: + dependencies: [":codegen"] builders: - ":versions": - enabled: true ":code_snippets": enabled: true generate_for: @@ -58,13 +87,11 @@ targets: auto_apply_builders: false sources: - "$package$" - - "lib/versions.json" - "lib/snippets/**" - - "tool/write_versions.dart" - "tool/snippets.dart" $default: - dependencies: [":prepare"] + dependencies: [":codegen", ":syntax_highlighting"] builders: built_site: release_options: diff --git a/docs/lib/snippets/modular/drift/dart_example.dart b/docs/lib/snippets/modular/drift/dart_example.dart new file mode 100644 index 00000000..b373a6ec --- /dev/null +++ b/docs/lib/snippets/modular/drift/dart_example.dart @@ -0,0 +1,13 @@ +import 'package:drift/drift.dart'; + +import 'example.drift.dart'; + +class DartExample extends ExampleDrift { + DartExample(GeneratedDatabase attachedDatabase) : super(attachedDatabase); + + // #docregion watchInCategory + Stream> watchInCategory(int category) { + return filterTodos((todos) => todos.category.equals(category)).watch(); + } + // #enddocregion watchInCategory +} diff --git a/docs/lib/snippets/modular/drift/example.drift b/docs/lib/snippets/modular/drift/example.drift new file mode 100644 index 00000000..299d86d7 --- /dev/null +++ b/docs/lib/snippets/modular/drift/example.drift @@ -0,0 +1,18 @@ +CREATE TABLE todos ( + id INT NOT NULL PRIMARY KEY AUTOINCREMENT, + title TEXT NOT NULL, + content TEXT NOT NULL, + category INTEGER REFERENCES categories(id) +); + +CREATE TABLE categories ( + id INT NOT NULL PRIMARY KEY AUTOINCREMENT, + description TEXT NOT NULL +) AS Category; + +-- #docregion filterTodos +filterTodos: SELECT * FROM todos WHERE $predicate; +-- #enddocregion filterTodos +-- #docregion getTodos +getTodos ($predicate = TRUE): SELECT * FROM todos WHERE $predicate; +-- #enddocregion getTodos diff --git a/docs/lib/snippets/modular/drift/example.drift.dart b/docs/lib/snippets/modular/drift/example.drift.dart new file mode 100644 index 00000000..e9f13ed8 --- /dev/null +++ b/docs/lib/snippets/modular/drift/example.drift.dart @@ -0,0 +1,474 @@ +// ignore_for_file: type=lint +import 'package:drift/drift.dart' as i0; +import 'package:drift_docs/snippets/modular/drift/example.drift.dart' as i1; +import 'package:drift/internal/modular.dart' as i2; + +class Todos extends i0.Table with i0.TableInfo { + @override + final i0.GeneratedDatabase attachedDatabase; + final String? _alias; + Todos(this.attachedDatabase, [this._alias]); + static const i0.VerificationMeta _idMeta = const i0.VerificationMeta('id'); + late final i0.GeneratedColumn id = i0.GeneratedColumn( + 'id', aliasedName, false, + hasAutoIncrement: true, + type: i0.DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL PRIMARY KEY AUTOINCREMENT'); + static const i0.VerificationMeta _titleMeta = + const i0.VerificationMeta('title'); + late final i0.GeneratedColumn title = i0.GeneratedColumn( + 'title', aliasedName, false, + type: i0.DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL'); + static const i0.VerificationMeta _contentMeta = + const i0.VerificationMeta('content'); + late final i0.GeneratedColumn content = i0.GeneratedColumn( + 'content', aliasedName, false, + type: i0.DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL'); + static const i0.VerificationMeta _categoryMeta = + const i0.VerificationMeta('category'); + late final i0.GeneratedColumn category = i0.GeneratedColumn( + 'category', aliasedName, true, + type: i0.DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'REFERENCES categories(id)'); + @override + List get $columns => [id, title, content, category]; + @override + String get aliasedName => _alias ?? 'todos'; + @override + String get actualTableName => 'todos'; + @override + i0.VerificationContext validateIntegrity(i0.Insertable instance, + {bool isInserting = false}) { + final context = i0.VerificationContext(); + final data = instance.toColumns(true); + if (data.containsKey('id')) { + context.handle(_idMeta, id.isAcceptableOrUnknown(data['id']!, _idMeta)); + } + if (data.containsKey('title')) { + context.handle( + _titleMeta, title.isAcceptableOrUnknown(data['title']!, _titleMeta)); + } else if (isInserting) { + context.missing(_titleMeta); + } + if (data.containsKey('content')) { + context.handle(_contentMeta, + content.isAcceptableOrUnknown(data['content']!, _contentMeta)); + } else if (isInserting) { + context.missing(_contentMeta); + } + if (data.containsKey('category')) { + context.handle(_categoryMeta, + category.isAcceptableOrUnknown(data['category']!, _categoryMeta)); + } + return context; + } + + @override + Set get $primaryKey => {id}; + @override + i1.Todo map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return i1.Todo( + id: attachedDatabase.typeMapping + .read(i0.DriftSqlType.int, data['${effectivePrefix}id'])!, + title: attachedDatabase.typeMapping + .read(i0.DriftSqlType.string, data['${effectivePrefix}title'])!, + content: attachedDatabase.typeMapping + .read(i0.DriftSqlType.string, data['${effectivePrefix}content'])!, + category: attachedDatabase.typeMapping + .read(i0.DriftSqlType.int, data['${effectivePrefix}category']), + ); + } + + @override + Todos createAlias(String alias) { + return Todos(attachedDatabase, alias); + } + + @override + bool get dontWriteConstraints => true; +} + +class Todo extends i0.DataClass implements i0.Insertable { + final int id; + final String title; + final String content; + final int? category; + const Todo( + {required this.id, + required this.title, + required this.content, + this.category}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = i0.Variable(id); + map['title'] = i0.Variable(title); + map['content'] = i0.Variable(content); + if (!nullToAbsent || category != null) { + map['category'] = i0.Variable(category); + } + return map; + } + + i1.TodosCompanion toCompanion(bool nullToAbsent) { + return i1.TodosCompanion( + id: i0.Value(id), + title: i0.Value(title), + content: i0.Value(content), + category: category == null && nullToAbsent + ? const i0.Value.absent() + : i0.Value(category), + ); + } + + factory Todo.fromJson(Map json, + {i0.ValueSerializer? serializer}) { + serializer ??= i0.driftRuntimeOptions.defaultSerializer; + return Todo( + id: serializer.fromJson(json['id']), + title: serializer.fromJson(json['title']), + content: serializer.fromJson(json['content']), + category: serializer.fromJson(json['category']), + ); + } + @override + Map toJson({i0.ValueSerializer? serializer}) { + serializer ??= i0.driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'title': serializer.toJson(title), + 'content': serializer.toJson(content), + 'category': serializer.toJson(category), + }; + } + + i1.Todo copyWith( + {int? id, + String? title, + String? content, + i0.Value category = const i0.Value.absent()}) => + i1.Todo( + id: id ?? this.id, + title: title ?? this.title, + content: content ?? this.content, + category: category.present ? category.value : this.category, + ); + @override + String toString() { + return (StringBuffer('Todo(') + ..write('id: $id, ') + ..write('title: $title, ') + ..write('content: $content, ') + ..write('category: $category') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(id, title, content, category); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is i1.Todo && + other.id == this.id && + other.title == this.title && + other.content == this.content && + other.category == this.category); +} + +class TodosCompanion extends i0.UpdateCompanion { + final i0.Value id; + final i0.Value title; + final i0.Value content; + final i0.Value category; + const TodosCompanion({ + this.id = const i0.Value.absent(), + this.title = const i0.Value.absent(), + this.content = const i0.Value.absent(), + this.category = const i0.Value.absent(), + }); + TodosCompanion.insert({ + this.id = const i0.Value.absent(), + required String title, + required String content, + this.category = const i0.Value.absent(), + }) : title = i0.Value(title), + content = i0.Value(content); + static i0.Insertable custom({ + i0.Expression? id, + i0.Expression? title, + i0.Expression? content, + i0.Expression? category, + }) { + return i0.RawValuesInsertable({ + if (id != null) 'id': id, + if (title != null) 'title': title, + if (content != null) 'content': content, + if (category != null) 'category': category, + }); + } + + i1.TodosCompanion copyWith( + {i0.Value? id, + i0.Value? title, + i0.Value? content, + i0.Value? category}) { + return i1.TodosCompanion( + id: id ?? this.id, + title: title ?? this.title, + content: content ?? this.content, + category: category ?? this.category, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = i0.Variable(id.value); + } + if (title.present) { + map['title'] = i0.Variable(title.value); + } + if (content.present) { + map['content'] = i0.Variable(content.value); + } + if (category.present) { + map['category'] = i0.Variable(category.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('i1.TodosCompanion(') + ..write('id: $id, ') + ..write('title: $title, ') + ..write('content: $content, ') + ..write('category: $category') + ..write(')')) + .toString(); + } +} + +class Categories extends i0.Table with i0.TableInfo { + @override + final i0.GeneratedDatabase attachedDatabase; + final String? _alias; + Categories(this.attachedDatabase, [this._alias]); + static const i0.VerificationMeta _idMeta = const i0.VerificationMeta('id'); + late final i0.GeneratedColumn id = i0.GeneratedColumn( + 'id', aliasedName, false, + hasAutoIncrement: true, + type: i0.DriftSqlType.int, + requiredDuringInsert: false, + $customConstraints: 'NOT NULL PRIMARY KEY AUTOINCREMENT'); + static const i0.VerificationMeta _descriptionMeta = + const i0.VerificationMeta('description'); + late final i0.GeneratedColumn description = + i0.GeneratedColumn('description', aliasedName, false, + type: i0.DriftSqlType.string, + requiredDuringInsert: true, + $customConstraints: 'NOT NULL'); + @override + List get $columns => [id, description]; + @override + String get aliasedName => _alias ?? 'categories'; + @override + String get actualTableName => 'categories'; + @override + i0.VerificationContext validateIntegrity(i0.Insertable instance, + {bool isInserting = false}) { + final context = i0.VerificationContext(); + final data = instance.toColumns(true); + if (data.containsKey('id')) { + context.handle(_idMeta, id.isAcceptableOrUnknown(data['id']!, _idMeta)); + } + if (data.containsKey('description')) { + context.handle( + _descriptionMeta, + description.isAcceptableOrUnknown( + data['description']!, _descriptionMeta)); + } else if (isInserting) { + context.missing(_descriptionMeta); + } + return context; + } + + @override + Set get $primaryKey => {id}; + @override + i1.Category map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return i1.Category( + id: attachedDatabase.typeMapping + .read(i0.DriftSqlType.int, data['${effectivePrefix}id'])!, + description: attachedDatabase.typeMapping + .read(i0.DriftSqlType.string, data['${effectivePrefix}description'])!, + ); + } + + @override + Categories createAlias(String alias) { + return Categories(attachedDatabase, alias); + } + + @override + bool get dontWriteConstraints => true; +} + +class Category extends i0.DataClass implements i0.Insertable { + final int id; + final String description; + const Category({required this.id, required this.description}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = i0.Variable(id); + map['description'] = i0.Variable(description); + return map; + } + + i1.CategoriesCompanion toCompanion(bool nullToAbsent) { + return i1.CategoriesCompanion( + id: i0.Value(id), + description: i0.Value(description), + ); + } + + factory Category.fromJson(Map json, + {i0.ValueSerializer? serializer}) { + serializer ??= i0.driftRuntimeOptions.defaultSerializer; + return Category( + id: serializer.fromJson(json['id']), + description: serializer.fromJson(json['description']), + ); + } + @override + Map toJson({i0.ValueSerializer? serializer}) { + serializer ??= i0.driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'description': serializer.toJson(description), + }; + } + + i1.Category copyWith({int? id, String? description}) => i1.Category( + id: id ?? this.id, + description: description ?? this.description, + ); + @override + String toString() { + return (StringBuffer('Category(') + ..write('id: $id, ') + ..write('description: $description') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(id, description); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is i1.Category && + other.id == this.id && + other.description == this.description); +} + +class CategoriesCompanion extends i0.UpdateCompanion { + final i0.Value id; + final i0.Value description; + const CategoriesCompanion({ + this.id = const i0.Value.absent(), + this.description = const i0.Value.absent(), + }); + CategoriesCompanion.insert({ + this.id = const i0.Value.absent(), + required String description, + }) : description = i0.Value(description); + static i0.Insertable custom({ + i0.Expression? id, + i0.Expression? description, + }) { + return i0.RawValuesInsertable({ + if (id != null) 'id': id, + if (description != null) 'description': description, + }); + } + + i1.CategoriesCompanion copyWith( + {i0.Value? id, i0.Value? description}) { + return i1.CategoriesCompanion( + id: id ?? this.id, + description: description ?? this.description, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = i0.Variable(id.value); + } + if (description.present) { + map['description'] = i0.Variable(description.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('i1.CategoriesCompanion(') + ..write('id: $id, ') + ..write('description: $description') + ..write(')')) + .toString(); + } +} + +class ExampleDrift extends i2.ModularAccessor { + ExampleDrift(i0.GeneratedDatabase db) : super(db); + i0.Selectable filterTodos(FilterTodos$predicate predicate) { + var $arrayStartIndex = 1; + final generatedpredicate = + $write(predicate(this.todos), startIndex: $arrayStartIndex); + $arrayStartIndex += generatedpredicate.amountOfVariables; + return customSelect('SELECT * FROM todos WHERE ${generatedpredicate.sql}', + variables: [ + ...generatedpredicate.introducedVariables + ], + readsFrom: { + todos, + ...generatedpredicate.watchedTables, + }).asyncMap(todos.mapFromRow); + } + + i0.Selectable getTodos({GetTodos$predicate? predicate}) { + var $arrayStartIndex = 1; + final generatedpredicate = $write( + predicate?.call(this.todos) ?? const i0.CustomExpression('(TRUE)'), + startIndex: $arrayStartIndex); + $arrayStartIndex += generatedpredicate.amountOfVariables; + return customSelect('SELECT * FROM todos WHERE ${generatedpredicate.sql}', + variables: [ + ...generatedpredicate.introducedVariables + ], + readsFrom: { + todos, + ...generatedpredicate.watchedTables, + }).asyncMap(todos.mapFromRow); + } + + i1.Todos get todos => this.resultSet('todos'); +} + +typedef FilterTodos$predicate = i0.Expression Function(i1.Todos todos); +typedef GetTodos$predicate = i0.Expression Function(i1.Todos todos); diff --git a/docs/pages/docs/Using SQL/drift_files.md b/docs/pages/docs/Using SQL/drift_files.md index 1a8affb3..187d7300 100644 --- a/docs/pages/docs/Using SQL/drift_files.md +++ b/docs/pages/docs/Using SQL/drift_files.md @@ -15,6 +15,9 @@ template: layouts/docs/single {% assign drift_tables = "package:drift_docs/snippets/drift_files/tables.drift.excerpt.json" | readString | json_decode %} {% assign small = "package:drift_docs/snippets/drift_files/small_snippets.drift.excerpt.json" | readString | json_decode %} +{% assign newDrift = "package:drift_docs/snippets/modular/drift/example.drift.excerpt.json" | readString | json_decode %} +{% assign newDart = "package:drift_docs/snippets/modular/drift/dart_example.dart.excerpt.json" | readString | json_decode %} + Drift files are a new feature that lets you write all your database code in SQL. But unlike raw SQL strings you might pass to simple database clients, everything in a drift file is verified by drift's powerful SQL analyzer. @@ -291,16 +294,14 @@ would generate a column serialized as "userId" in json. You can make most of both SQL and Dart with "Dart Templates", which is a Dart expression that gets inlined to a query at runtime. To use them, declare a $-variable in a query: -```sql -_filterTodos: SELECT * FROM todos WHERE $predicate; -``` -Drift will generate a `Selectable _filterTodos(Expression predicate)` -method that can be used to construct dynamic filters at runtime: -```dart -Stream> watchInCategory(int category) { - return _filterTodos(todos.category.equals(category)).watch(); -} -``` + +{% include "blocks/snippet" snippets = newDrift name = "filterTodos" %} + +Drift will generate a `Selectable` method with a `predicate` parameter that +can be used to construct dynamic filters at runtime: + +{% include "blocks/snippet" snippets = newDart name = "watchInCategory" %} + This lets you write a single SQL query and dynamically apply a predicate at runtime! This feature works for @@ -314,46 +315,11 @@ This feature works for When used as expression, you can also supply a default value in your query: -```sql -_filterTodos ($predicate = TRUE): SELECT * FROM todos WHERE $predicate; -``` +{% include "blocks/snippet" snippets = newDrift name = "getTodos" %} This will make the `predicate` parameter optional in Dart. It will use the default SQL value (here, `TRUE`) when not explicitly set. -{% block "blocks/alert" title="Using column names in Dart" color="warning" %} -If your query uses table aliases, you'll need to account for that when embedding Dart -expressions in your SQL query. Consider this for instance: - -```sql -findRoutes: SELECT r.* FROM routes r - INNER JOIN points "start" ON "start".id = r."start" - INNER JOIN points "end" ON "end".id = r."end" -WHERE $predicate -``` - -If you want to filter for the `start` point in Dart, you have to use -an explicit [`alias`](https://pub.dev/documentation/drift/latest/drift/DatabaseConnectionUser/alias.html): - -```dart -Future> routesByStart(int startPointId) { - final start = alias(points, 'start'); - return findRoutes(start.id.equals(startPointId)); -} -``` - -You can enable the `scoped_dart_components` [build option]({{ '../Advanced Features/builder_options.md' | pageUrl }}) -and let the generator help you here. -When the option is enabled, drift would generate a `Expression Function(Routes r, Points start, Points end)` as a parameter, which -makes this a lot easier: - -```dart -Future> routesByStart(int startPointId) { - return findRoutes((r, start, end) => start.id.equals(startPointId)); -} -``` -{% endblock %} - ### Type converters You can import and use [type converters]({{ "../Advanced Features/type_converters.md" | pageUrl }}) diff --git a/docs/pubspec.yaml b/docs/pubspec.yaml index 35a0e8d1..18dcec9d 100644 --- a/docs/pubspec.yaml +++ b/docs/pubspec.yaml @@ -14,7 +14,7 @@ dependencies: version: ^0.2.2 code_snippets: hosted: https://simonbinder.eu - version: ^0.0.8 + version: ^0.0.11 # used in snippets http: ^0.13.5 sqlite3: ^1.7.2 diff --git a/docs/templates/blocks/snippet.html b/docs/templates/blocks/snippet.html index b1fbf0e5..18e711b0 100644 --- a/docs/templates/blocks/snippet.html +++ b/docs/templates/blocks/snippet.html @@ -1,4 +1,2 @@ {% assign excerpt = args.name | default: '(full)' %} -
-{{ args.snippets | get: excerpt }}
-
+
{{ args.snippets | get: excerpt }}
diff --git a/drift_dev/lib/src/backends/build/drift_builder.dart b/drift_dev/lib/src/backends/build/drift_builder.dart index 548d03c2..b0b7bce0 100644 --- a/drift_dev/lib/src/backends/build/drift_builder.dart +++ b/drift_dev/lib/src/backends/build/drift_builder.dart @@ -339,7 +339,7 @@ class _DriftBuildRun { ); writer = Writer(options, generationOptions: generationOptions); } else { - final imports = LibraryInputManager(); + final imports = LibraryInputManager(buildStep.allowedOutputs.single.uri); final generationOptions = GenerationOptions( imports: imports, isModular: true, diff --git a/drift_dev/lib/src/writer/import_manager.dart b/drift_dev/lib/src/writer/import_manager.dart index 1d8e73c4..50deec23 100644 --- a/drift_dev/lib/src/writer/import_manager.dart +++ b/drift_dev/lib/src/writer/import_manager.dart @@ -1,3 +1,5 @@ +import 'package:path/path.dart' show url; + import '../utils/string_escaper.dart'; import 'writer.dart'; @@ -16,9 +18,16 @@ class LibraryInputManager extends ImportManager { static final _dartCore = Uri.parse('dart:core'); final Map _importAliases = {}; + + /// The uri of the file being generated. + /// + /// This allows constructing relative imports for assets that aren't in + /// `lib/`. + final Uri? _outputUri; + TextEmitter? emitter; - LibraryInputManager(); + LibraryInputManager([this._outputUri]); void linkToWriter(Writer writer) { emitter = writer.leaf(); @@ -33,8 +42,20 @@ class LibraryInputManager extends ImportManager { return _importAliases.putIfAbsent(definitionUri, () { final alias = 'i${_importAliases.length}'; - emitter?.writeln( - 'import ${asDartLiteral(definitionUri.toString())} as $alias;'); + final importedScheme = definitionUri.scheme; + String importLiteral; + + if (importedScheme != 'package' && + importedScheme != 'dart' && + importedScheme == _outputUri?.scheme) { + // Not a package nor a dart import, use a relative import instead + importLiteral = url.relative(definitionUri.path, + from: url.dirname(_outputUri!.path)); + } else { + importLiteral = definitionUri.toString(); + } + + emitter?.writeln('import ${asDartLiteral(importLiteral)} as $alias;'); return alias; }); } diff --git a/drift_dev/test/writer/import_manager_test.dart b/drift_dev/test/writer/import_manager_test.dart new file mode 100644 index 00000000..b70cad10 --- /dev/null +++ b/drift_dev/test/writer/import_manager_test.dart @@ -0,0 +1,48 @@ +import 'package:build/build.dart'; +import 'package:drift_dev/src/analysis/options.dart'; +import 'package:drift_dev/src/analysis/results/results.dart'; +import 'package:drift_dev/src/writer/import_manager.dart'; +import 'package:drift_dev/src/writer/writer.dart'; +import 'package:test/test.dart'; + +void main() { + group('LibraryInputManager', () { + final sourceUri = AssetId('a', 'example/main.dart').uri; + + late LibraryInputManager imports; + late Writer writer; + + setUp(() { + imports = LibraryInputManager(sourceUri); + final generationOptions = + GenerationOptions(imports: imports, isModular: true); + writer = Writer(const DriftOptions.defaults(), + generationOptions: generationOptions); + imports.linkToWriter(writer); + }); + + test('does not generate prefix for dart:core', () { + expect(imports.prefixFor(Uri.parse('dart:core'), 'String'), isNull); + }); + + test('writes imports', () { + expect(imports.prefixFor(AnnotatedDartCode.dartAsync, 'Future'), 'i0'); + expect(imports.prefixFor(AnnotatedDartCode.drift, 'GeneratedDatabase'), + 'i1'); + expect(imports.prefixFor(AnnotatedDartCode.dartAsync, 'Stream'), 'i0'); + + expect(writer.writeGenerated(), ''' +import 'dart:async' as i0; +import 'package:drift/drift.dart' as i1; +'''); + }); + + test('can write imports for files outside of lib', () { + final uri = AssetId('a', 'example/imported.dart').uri; + expect(imports.prefixFor(uri, 'Test'), 'i0'); + + expect( + writer.writeGenerated(), contains("import 'imported.dart' as i0;")); + }); + }); +}