diff --git a/drift_dev/build.yaml b/drift_dev/build.yaml index c3b65d9e..9847abe6 100644 --- a/drift_dev/build.yaml +++ b/drift_dev/build.yaml @@ -20,19 +20,12 @@ builders: build_to: cache applies_builders: ["drift_dev:cleanup"] - analysis: - import: "package:drift_dev/integrations/build.dart" - builder_factories: ["analyzer"] - build_extensions: - ".drift": [".drift.drift_module.json"] - ".dart": [".dart.drift_module.json"] - build_to: cache - required_inputs: [".drift_prep.json"] - drift_dev: import: "package:drift_dev/integrations/build.dart" - builder_factories: ["driftBuilder"] - build_extensions: {".dart": [".drift.g.part"]} + builder_factories: ["analyzer", "driftBuilder"] + build_extensions: + ".dart": [".drift.g.part", ".dart.drift_module.json"] + ".drift": [".drift.drift_module.json"] auto_apply: dependents build_to: cache required_inputs: [".drift_prep.json"] diff --git a/drift_dev/lib/src/analysis/driver/driver.dart b/drift_dev/lib/src/analysis/driver/driver.dart index 14cf9fe6..9e58b198 100644 --- a/drift_dev/lib/src/analysis/driver/driver.dart +++ b/drift_dev/lib/src/analysis/driver/driver.dart @@ -1,4 +1,3 @@ -import 'package:meta/meta.dart'; import 'package:sqlparser/sqlparser.dart'; import '../../analyzer/options.dart'; @@ -35,7 +34,6 @@ class DriftAnalysisDriver { return _knownTypes ??= await KnownDriftTypes.resolve(this); } - @visibleForTesting Future prepareFileForAnalysis(Uri uri) async { var known = cache.knownFiles[uri] ?? cache.notifyFileChanged(uri); @@ -76,7 +74,12 @@ class DriftAnalysisDriver { for (final discovered in state.discovery!.locallyDefinedElements) { if (!state.elementIsAnalyzed(discovered.ownId)) { final resolver = DriftResolver(this); - await resolver.resolveDiscovered(discovered); + + try { + await resolver.resolveDiscovered(discovered); + } catch (e, s) { + backend.log.warning('Could not analyze ${discovered.ownId}', e, s); + } } } } diff --git a/drift_dev/lib/src/analysis/resolver/dart/table.dart b/drift_dev/lib/src/analysis/resolver/dart/table.dart index 6bf22ebe..8fe42cbf 100644 --- a/drift_dev/lib/src/analysis/resolver/dart/table.dart +++ b/drift_dev/lib/src/analysis/resolver/dart/table.dart @@ -18,7 +18,7 @@ class DartTableResolver extends LocalElementResolver { @override Future resolve() async { - final element = discovered.element; + final element = discovered.dartElement; final pendingColumns = (await _parseColumns(element)).toList(); final columns = [for (final column in pendingColumns) column.column]; @@ -27,18 +27,7 @@ class DartTableResolver extends LocalElementResolver { final dataClassInfo = _readDataClassInformation(columns, element); - final table = DriftTable( - discovered.ownId, - DriftDeclaration.dartElement(element), - columns: columns, - nameOfRowClass: dataClassInfo.enforcedName, - existingRowClass: dataClassInfo.existingClass, - customParentClass: dataClassInfo.extending, - baseDartName: element.name, - primaryKeyFromTableConstraint: primaryKey, - uniqueKeysFromTableConstraint: uniqueKeys ?? const [], - withoutRowId: await _overrideWithoutRowId(element) ?? false, - ); + final references = {}; // Resolve local foreign key references in pending columns for (final column in pendingColumns) { @@ -49,9 +38,29 @@ class DartTableResolver extends LocalElementResolver { (e) => e.nameInDart == column.referencesColumnInSameTable); ref.otherColumn = referencedColumn; + } else { + for (final constraint in column.column.constraints) { + if (constraint is ForeignKeyReference) { + references.add(constraint.otherColumn.owner); + } + } } } + final table = DriftTable( + discovered.ownId, + DriftDeclaration.dartElement(element), + columns: columns, + references: references.toList(), + nameOfRowClass: dataClassInfo.enforcedName, + existingRowClass: dataClassInfo.existingClass, + customParentClass: dataClassInfo.extending, + baseDartName: element.name, + primaryKeyFromTableConstraint: primaryKey, + uniqueKeysFromTableConstraint: uniqueKeys ?? const [], + withoutRowId: await _overrideWithoutRowId(element) ?? false, + ); + if (primaryKey != null && columns.any((c) => c.constraints.any((e) => e is PrimaryKeyColumn))) { reportError(DriftAnalysisError.forDartElement( diff --git a/drift_dev/lib/src/analysis/resolver/intermediate_state.dart b/drift_dev/lib/src/analysis/resolver/intermediate_state.dart index d7016043..1b28b499 100644 --- a/drift_dev/lib/src/analysis/resolver/intermediate_state.dart +++ b/drift_dev/lib/src/analysis/resolver/intermediate_state.dart @@ -15,8 +15,13 @@ class DiscoveredDriftView extends DiscoveredElement { DiscoveredDriftView(super.ownId, this.createView); } -class DiscoveredDartTable extends DiscoveredElement { - final ClassElement element; +abstract class DiscoveredDartElement + extends DiscoveredElement { + final DE dartElement; - DiscoveredDartTable(super.ownId, this.element); + DiscoveredDartElement(super.ownId, this.dartElement); +} + +class DiscoveredDartTable extends DiscoveredDartElement { + DiscoveredDartTable(super.ownId, super.dartElement); } diff --git a/drift_dev/lib/src/analysis/resolver/resolver.dart b/drift_dev/lib/src/analysis/resolver/resolver.dart index 739257ca..07a94ec8 100644 --- a/drift_dev/lib/src/analysis/resolver/resolver.dart +++ b/drift_dev/lib/src/analysis/resolver/resolver.dart @@ -1,4 +1,5 @@ import 'package:analyzer/dart/element/element.dart'; +import 'package:collection/collection.dart'; import '../driver/driver.dart'; import '../driver/error.dart'; @@ -95,9 +96,20 @@ class DriftResolver { Future resolveDartReference( DriftElementId owner, Element element) async { final uri = await driver.backend.uriOfDart(element.library!); - final id = DriftElementId(uri, element.name!); + final state = await driver.prepareFileForAnalysis(uri); - return resolveReferencedElement(owner, id); + final discovered = state.discovery?.locallyDefinedElements + .whereType() + .firstWhereOrNull((c) => c.dartElement == element); + + if (discovered != null) { + return resolveReferencedElement(owner, discovered.ownId); + } else { + return InvalidReferenceResult( + InvalidReferenceError.noElementWichSuchName, + 'The referenced element is not understood by drift.', + ); + } } Future resolveReference( diff --git a/drift_dev/test/analysis/resolver/dart/foreign_key_test.dart b/drift_dev/test/analysis/resolver/dart/foreign_key_test.dart new file mode 100644 index 00000000..02108b48 --- /dev/null +++ b/drift_dev/test/analysis/resolver/dart/foreign_key_test.dart @@ -0,0 +1,143 @@ +import 'package:drift_dev/src/analysis/results/results.dart'; +import 'package:sqlparser/sqlparser.dart'; +import 'package:test/test.dart'; + +import '../../test_utils.dart'; + +void main() { + group('reports a warning', () { + test('when the table is not a class type', () async { + final backend = TestBackend.inTest({ + 'a|lib/main.dart': ''' +import 'package:drift/drift.dart'; + +class Foo extends Table { + TextColumn get foo => text().references(dynamic, #what)(); +} +''' + }); + + final file = + await backend.driver.fullyAnalyze(Uri.parse('package:a/main.dart')); + expect(file.errorsDuringDiscovery, isEmpty); + + final result = file.analysis.values.single; + expect(result.result, isA()); + expect(result.errorsDuringAnalysis, [ + isDriftError('`dynamic` is not a class!').withSpan('dynamic'), + ]); + }); + + test('when the table is not a symbol literal', () async { + final backend = TestBackend.inTest({ + 'a|lib/main.dart': ''' +import 'package:drift/drift.dart'; + +const column = #other; + +class Foo extends Table { + TextColumn get foo => text().references(Table, column)(); +} +''' + }); + + final file = + await backend.driver.fullyAnalyze(Uri.parse('package:a/main.dart')); + expect(file.errorsDuringDiscovery, isEmpty); + + final result = file.analysis.values.single; + expect(result.result, isA()); + expect(result.errorsDuringAnalysis, [ + isDriftError(contains('This should be a symbol literal')) + .withSpan('column'), + ]); + }); + + test('when the referenced table does not exist', () async { + final backend = TestBackend.inTest({ + 'a|lib/main.dart': ''' +import 'package:drift/drift.dart'; + +class OtherTable { + // not a table! +} + +class Foo extends Table { + TextColumn get foo => text().references(OtherTable, #column)(); +} +''' + }); + + final file = + await backend.driver.fullyAnalyze(Uri.parse('package:a/main.dart')); + expect(file.errorsDuringDiscovery, isEmpty); + + final result = file.analysis.values.single; + expect(result.result, isA()); + expect(result.errorsDuringAnalysis, [ + isDriftError('The referenced element is not understood by drift.') + .withSpan('OtherTable'), + ]); + }); + }); + + test('resolves reference', () async { + final backend = TestBackend.inTest({ + 'a|lib/main.dart': ''' +import 'package:drift/drift.dart'; + +class OtherTable extends Table { + TextColumn get column => text()(); +} + +class Foo extends Table { + TextColumn get foo => text().references(OtherTable, #column, + onUpdate: KeyAction.restrict, onDelete: KeyAction.cascade)(); +} +''' + }); + + final uri = Uri.parse('package:a/main.dart'); + final file = await backend.driver.fullyAnalyze(uri); + final otherTable = + file.analysis[DriftElementId(uri, 'other_table')]!.result as DriftTable; + final foo = file.analysis[DriftElementId(uri, 'foo')]!.result as DriftTable; + + expect(foo.references, [otherTable]); + + final column = foo.columns.single; + final feature = column.constraints.whereType().first; + + expect(feature.otherColumn.nameInDart, 'column'); + expect(feature.otherColumn.owner, otherTable); + expect(feature.onUpdate, ReferenceAction.restrict); + expect(feature.onDelete, ReferenceAction.cascade); + }); + + test('resolves self-references', () async { + final backend = TestBackend.inTest({ + 'a|lib/main.dart': ''' +import 'package:drift/drift.dart'; + +class Foo extends Table { + IntColumn get id => integer().autoIncrement()(); + IntColumn get parentId => integer().nullable().references(Foo, #id)(); +} +''' + }); + + final file = + await backend.driver.fullyAnalyze(Uri.parse('package:a/main.dart')); + final table = file.analysis.values.single.result as DriftTable; + + expect(table.references, isEmpty); + + final id = table.columns[0]; + final parentId = table.columns[1]; + + expect( + parentId.constraints, + contains(isA() + .having((e) => e.otherColumn, 'otherColumn', id))); + }); +} diff --git a/drift_dev/test/analysis/resolver/dart/table_test.dart b/drift_dev/test/analysis/resolver/dart/table_test.dart index b4e4f940..4306537f 100644 --- a/drift_dev/test/analysis/resolver/dart/table_test.dart +++ b/drift_dev/test/analysis/resolver/dart/table_test.dart @@ -9,7 +9,7 @@ void main() { late TestBackend backend; setUpAll(() { - backend = TestBackend.inTest({ + backend = TestBackend({ 'a|lib/main.dart': ''' import 'package:drift/drift.dart'; diff --git a/drift_dev/test/analysis/test_utils.dart b/drift_dev/test/analysis/test_utils.dart index f6d5cf60..1742085d 100644 --- a/drift_dev/test/analysis/test_utils.dart +++ b/drift_dev/test/analysis/test_utils.dart @@ -31,7 +31,8 @@ class TestBackend extends DriftBackend { AnalysisContext? _dartContext; - TestBackend(Map sourceContents, DriftOptions options) + TestBackend(Map sourceContents, + {DriftOptions options = const DriftOptions.defaults()}) : sourceContents = { for (final entry in sourceContents.entries) AssetId.parse(entry.key).uri.toString(): entry.value, @@ -41,7 +42,7 @@ class TestBackend extends DriftBackend { factory TestBackend.inTest(Map sourceContents, {DriftOptions options = const DriftOptions.defaults()}) { - final backend = TestBackend(sourceContents, options); + final backend = TestBackend(sourceContents, options: options); addTearDown(backend.dispose); return backend;