Fix Dart reference analysis in new analyzer

This commit is contained in:
Simon Binder 2022-09-10 17:56:25 +02:00
parent 0f97b42a43
commit eabf2a1e03
No known key found for this signature in database
GPG Key ID: 7891917E4147B8C0
8 changed files with 201 additions and 35 deletions

View File

@ -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"]

View File

@ -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);
}
} }
} }
} }

View File

@ -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))) {

View File

@ -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);
} }

View File

@ -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(

View File

@ -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)));
});
}

View File

@ -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';

View File

@ -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;