mirror of https://github.com/AMT-Cheif/drift.git
Fix Dart reference analysis in new analyzer
This commit is contained in:
parent
0f97b42a43
commit
eabf2a1e03
|
@ -20,19 +20,12 @@ builders:
|
||||||
build_to: cache
|
build_to: cache
|
||||||
applies_builders: ["drift_dev:cleanup"]
|
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:
|
drift_dev:
|
||||||
import: "package:drift_dev/integrations/build.dart"
|
import: "package:drift_dev/integrations/build.dart"
|
||||||
builder_factories: ["driftBuilder"]
|
builder_factories: ["analyzer", "driftBuilder"]
|
||||||
build_extensions: {".dart": [".drift.g.part"]}
|
build_extensions:
|
||||||
|
".dart": [".drift.g.part", ".dart.drift_module.json"]
|
||||||
|
".drift": [".drift.drift_module.json"]
|
||||||
auto_apply: dependents
|
auto_apply: dependents
|
||||||
build_to: cache
|
build_to: cache
|
||||||
required_inputs: [".drift_prep.json"]
|
required_inputs: [".drift_prep.json"]
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
import 'package:meta/meta.dart';
|
|
||||||
import 'package:sqlparser/sqlparser.dart';
|
import 'package:sqlparser/sqlparser.dart';
|
||||||
|
|
||||||
import '../../analyzer/options.dart';
|
import '../../analyzer/options.dart';
|
||||||
|
@ -35,7 +34,6 @@ class DriftAnalysisDriver {
|
||||||
return _knownTypes ??= await KnownDriftTypes.resolve(this);
|
return _knownTypes ??= await KnownDriftTypes.resolve(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@visibleForTesting
|
|
||||||
Future<FileState> prepareFileForAnalysis(Uri uri) async {
|
Future<FileState> prepareFileForAnalysis(Uri uri) async {
|
||||||
var known = cache.knownFiles[uri] ?? cache.notifyFileChanged(uri);
|
var known = cache.knownFiles[uri] ?? cache.notifyFileChanged(uri);
|
||||||
|
|
||||||
|
@ -76,7 +74,12 @@ class DriftAnalysisDriver {
|
||||||
for (final discovered in state.discovery!.locallyDefinedElements) {
|
for (final discovered in state.discovery!.locallyDefinedElements) {
|
||||||
if (!state.elementIsAnalyzed(discovered.ownId)) {
|
if (!state.elementIsAnalyzed(discovered.ownId)) {
|
||||||
final resolver = DriftResolver(this);
|
final resolver = DriftResolver(this);
|
||||||
|
|
||||||
|
try {
|
||||||
await resolver.resolveDiscovered(discovered);
|
await resolver.resolveDiscovered(discovered);
|
||||||
|
} catch (e, s) {
|
||||||
|
backend.log.warning('Could not analyze ${discovered.ownId}', e, s);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,7 +18,7 @@ class DartTableResolver extends LocalElementResolver<DiscoveredDartTable> {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<DriftElement> resolve() async {
|
Future<DriftElement> resolve() async {
|
||||||
final element = discovered.element;
|
final element = discovered.dartElement;
|
||||||
|
|
||||||
final pendingColumns = (await _parseColumns(element)).toList();
|
final pendingColumns = (await _parseColumns(element)).toList();
|
||||||
final columns = [for (final column in pendingColumns) column.column];
|
final columns = [for (final column in pendingColumns) column.column];
|
||||||
|
@ -27,18 +27,7 @@ class DartTableResolver extends LocalElementResolver<DiscoveredDartTable> {
|
||||||
|
|
||||||
final dataClassInfo = _readDataClassInformation(columns, element);
|
final dataClassInfo = _readDataClassInformation(columns, element);
|
||||||
|
|
||||||
final table = DriftTable(
|
final references = <DriftElement>{};
|
||||||
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,
|
|
||||||
);
|
|
||||||
|
|
||||||
// Resolve local foreign key references in pending columns
|
// Resolve local foreign key references in pending columns
|
||||||
for (final column in pendingColumns) {
|
for (final column in pendingColumns) {
|
||||||
|
@ -49,8 +38,28 @@ class DartTableResolver extends LocalElementResolver<DiscoveredDartTable> {
|
||||||
(e) => e.nameInDart == column.referencesColumnInSameTable);
|
(e) => e.nameInDart == column.referencesColumnInSameTable);
|
||||||
|
|
||||||
ref.otherColumn = referencedColumn;
|
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 &&
|
if (primaryKey != null &&
|
||||||
columns.any((c) => c.constraints.any((e) => e is PrimaryKeyColumn))) {
|
columns.any((c) => c.constraints.any((e) => e is PrimaryKeyColumn))) {
|
||||||
|
|
|
@ -15,8 +15,13 @@ class DiscoveredDriftView extends DiscoveredElement {
|
||||||
DiscoveredDriftView(super.ownId, this.createView);
|
DiscoveredDriftView(super.ownId, this.createView);
|
||||||
}
|
}
|
||||||
|
|
||||||
class DiscoveredDartTable extends DiscoveredElement {
|
abstract class DiscoveredDartElement<DE extends Element>
|
||||||
final ClassElement element;
|
extends DiscoveredElement {
|
||||||
|
final DE dartElement;
|
||||||
|
|
||||||
DiscoveredDartTable(super.ownId, this.element);
|
DiscoveredDartElement(super.ownId, this.dartElement);
|
||||||
|
}
|
||||||
|
|
||||||
|
class DiscoveredDartTable extends DiscoveredDartElement<ClassElement> {
|
||||||
|
DiscoveredDartTable(super.ownId, super.dartElement);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import 'package:analyzer/dart/element/element.dart';
|
import 'package:analyzer/dart/element/element.dart';
|
||||||
|
import 'package:collection/collection.dart';
|
||||||
|
|
||||||
import '../driver/driver.dart';
|
import '../driver/driver.dart';
|
||||||
import '../driver/error.dart';
|
import '../driver/error.dart';
|
||||||
|
@ -95,9 +96,20 @@ class DriftResolver {
|
||||||
Future<ResolveReferencedElementResult> resolveDartReference(
|
Future<ResolveReferencedElementResult> resolveDartReference(
|
||||||
DriftElementId owner, Element element) async {
|
DriftElementId owner, Element element) async {
|
||||||
final uri = await driver.backend.uriOfDart(element.library!);
|
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<DiscoveredDartElement>()
|
||||||
|
.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<ResolveReferencedElementResult> resolveReference(
|
Future<ResolveReferencedElementResult> resolveReference(
|
||||||
|
|
|
@ -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<DriftTable>());
|
||||||
|
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<DriftTable>());
|
||||||
|
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<DriftTable>());
|
||||||
|
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<ForeignKeyReference>().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<ForeignKeyReference>()
|
||||||
|
.having((e) => e.otherColumn, 'otherColumn', id)));
|
||||||
|
});
|
||||||
|
}
|
|
@ -9,7 +9,7 @@ void main() {
|
||||||
late TestBackend backend;
|
late TestBackend backend;
|
||||||
|
|
||||||
setUpAll(() {
|
setUpAll(() {
|
||||||
backend = TestBackend.inTest({
|
backend = TestBackend({
|
||||||
'a|lib/main.dart': '''
|
'a|lib/main.dart': '''
|
||||||
import 'package:drift/drift.dart';
|
import 'package:drift/drift.dart';
|
||||||
|
|
||||||
|
|
|
@ -31,7 +31,8 @@ class TestBackend extends DriftBackend {
|
||||||
|
|
||||||
AnalysisContext? _dartContext;
|
AnalysisContext? _dartContext;
|
||||||
|
|
||||||
TestBackend(Map<String, String> sourceContents, DriftOptions options)
|
TestBackend(Map<String, String> sourceContents,
|
||||||
|
{DriftOptions options = const DriftOptions.defaults()})
|
||||||
: sourceContents = {
|
: sourceContents = {
|
||||||
for (final entry in sourceContents.entries)
|
for (final entry in sourceContents.entries)
|
||||||
AssetId.parse(entry.key).uri.toString(): entry.value,
|
AssetId.parse(entry.key).uri.toString(): entry.value,
|
||||||
|
@ -41,7 +42,7 @@ class TestBackend extends DriftBackend {
|
||||||
|
|
||||||
factory TestBackend.inTest(Map<String, String> sourceContents,
|
factory TestBackend.inTest(Map<String, String> sourceContents,
|
||||||
{DriftOptions options = const DriftOptions.defaults()}) {
|
{DriftOptions options = const DriftOptions.defaults()}) {
|
||||||
final backend = TestBackend(sourceContents, options);
|
final backend = TestBackend(sourceContents, options: options);
|
||||||
addTearDown(backend.dispose);
|
addTearDown(backend.dispose);
|
||||||
|
|
||||||
return backend;
|
return backend;
|
||||||
|
|
Loading…
Reference in New Issue