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

View File

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

View File

@ -18,7 +18,7 @@ class DartTableResolver extends LocalElementResolver<DiscoveredDartTable> {
@override
Future<DriftElement> 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<DiscoveredDartTable> {
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 = <DriftElement>{};
// Resolve local foreign key references in pending columns
for (final column in pendingColumns) {
@ -49,9 +38,29 @@ class DartTableResolver extends LocalElementResolver<DiscoveredDartTable> {
(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(

View File

@ -15,8 +15,13 @@ class DiscoveredDriftView extends DiscoveredElement {
DiscoveredDriftView(super.ownId, this.createView);
}
class DiscoveredDartTable extends DiscoveredElement {
final ClassElement element;
abstract class DiscoveredDartElement<DE extends 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:collection/collection.dart';
import '../driver/driver.dart';
import '../driver/error.dart';
@ -95,9 +96,20 @@ class DriftResolver {
Future<ResolveReferencedElementResult> 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<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(

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;
setUpAll(() {
backend = TestBackend.inTest({
backend = TestBackend({
'a|lib/main.dart': '''
import 'package:drift/drift.dart';

View File

@ -31,7 +31,8 @@ class TestBackend extends DriftBackend {
AnalysisContext? _dartContext;
TestBackend(Map<String, String> sourceContents, DriftOptions options)
TestBackend(Map<String, String> 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<String, String> sourceContents,
{DriftOptions options = const DriftOptions.defaults()}) {
final backend = TestBackend(sourceContents, options);
final backend = TestBackend(sourceContents, options: options);
addTearDown(backend.dispose);
return backend;