Parse entrypoints in new analyzer

This commit is contained in:
Simon Binder 2022-09-17 00:16:53 +02:00
parent 090a0053f1
commit a6caae9d8f
No known key found for this signature in database
GPG Key ID: 7891917E4147B8C0
11 changed files with 412 additions and 9 deletions

View File

@ -1,4 +1,4 @@
// This field is analyzed by drift_dev to easily obtain common types.
export 'runtime/types/converters.dart' show TypeConverter, JsonTypeConverter;
export 'dsl/dsl.dart' show Table;
export 'dsl/dsl.dart' show Table, DriftDatabase, DriftAccessor;

View File

@ -0,0 +1,182 @@
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/element/type.dart';
import 'package:collection/collection.dart';
import '../../driver/error.dart';
import '../../results/results.dart';
import '../intermediate_state.dart';
import '../resolver.dart';
import 'helper.dart';
class DartAccessorResolver
extends LocalElementResolver<DiscoveredBaseAccessor> {
DartAccessorResolver(
super.file, super.discovered, super.resolver, super.state);
@override
Future<BaseDriftAccessor> resolve() async {
final tables = <DriftTable>[];
final views = <DriftView>[];
final includes = <Uri>[];
final queries = <QueryOnAccessor>[];
final annotation = discovered.annotation;
final element = discovered.dartElement;
final rawTables = annotation.getField('tables')!.toListValue()!;
for (final tableType in rawTables) {
final dartType = tableType.toTypeValue();
if (dartType is! InterfaceType) {
reportError(
DriftAnalysisError.forDartElement(
element,
'Could not read table from '
'`${dartType?.getDisplayString(withNullability: true)}`, it needs '
'to reference a table class.',
),
);
continue;
}
final table = await resolveDartReferenceOrReportError<DriftTable>(
dartType.element2,
(msg) => DriftAnalysisError.forDartElement(element, msg));
if (table != null) {
tables.add(table);
}
}
final rawViews = annotation.getField('views')!.toListValue()!;
for (final viewType in rawViews) {
final dartType = viewType.toTypeValue();
if (dartType is! InterfaceType) {
reportError(
DriftAnalysisError.forDartElement(
element,
'Could not read view from '
'`${dartType?.getDisplayString(withNullability: true)}`, it needs '
'to reference a view class.',
),
);
continue;
}
final view = await resolveDartReferenceOrReportError<DriftView>(
dartType.element2,
(msg) => DriftAnalysisError.forDartElement(element, msg));
if (view != null) {
views.add(view);
}
}
for (final include in annotation.getField('include')!.toSetValue()!) {
final value = include.toStringValue()!;
final import = Uri.tryParse(value);
if (import == null) {
reportError(
DriftAnalysisError.forDartElement(
element, '`$value` is not a valid URI to include'),
);
} else {
includes.add(import);
}
}
final rawQueries = annotation.getField('queries')!.toMapValue()!;
rawQueries.forEach((key, value) {
final keyStr = key!.toStringValue()!;
final valueStr = value!.toStringValue()!;
queries.add(QueryOnAccessor(keyStr, valueStr));
});
final declaration = DriftDeclaration.dartElement(element);
if (discovered.isDatabase) {
final accessorTypes = <AnnotatedDartCode>[];
final rawDaos = annotation.getField('daos')!.toListValue()!;
for (final value in rawDaos) {
final type = value.toTypeValue()!;
if (type is! InterfaceType) {
reportError(
DriftAnalysisError.forDartElement(
element,
'Could not read referenced DAO from '
'`$type?.getDisplayString(withNullability: true)}`, it needs '
'to reference an accessor class.',
),
);
continue;
}
accessorTypes.add(AnnotatedDartCode.type(type));
}
return DriftDatabase(
id: discovered.ownId,
declaration: declaration,
declaredTables: tables,
declaredViews: views,
declaredIncludes: includes,
declaredQueries: queries,
schemaVersion: await _readSchemaVersion(),
accessorTypes: accessorTypes,
);
} else {
final dbType = element.allSupertypes
.firstWhereOrNull((i) => i.element2.name == 'DatabaseAccessor');
// inherits from DatabaseAccessor<T>, we want to know which T
final dbImpl = dbType?.typeArguments.single ??
element.library.typeProvider.dynamicType;
if (dbImpl.isDynamic) {
reportError(DriftAnalysisError.forDartElement(
element,
'This class must inherit from DatabaseAccessor<T>, where T is an '
'actual type of a database.',
));
}
return DatabaseAccessor(
id: discovered.ownId,
declaration: declaration,
declaredTables: tables,
declaredViews: views,
declaredIncludes: includes,
declaredQueries: queries,
databaseClass: AnnotatedDartCode.type(dbImpl),
);
}
}
Future<int?> _readSchemaVersion() async {
final element =
discovered.dartElement.thisType.getGetter('schemaVersion')?.variable;
if (element == null) return null;
try {
if (element.isSynthetic) {
// Getter, read from `=>` body if possible.
final expr = returnExpressionOfMethod(await resolver.driver.backend
.loadElementDeclaration(element.getter!) as MethodDeclaration);
if (expr is IntegerLiteral) {
return expr.value;
}
} else {
final astField = await resolver.driver.backend
.loadElementDeclaration(element) as VariableDeclaration;
if (astField.initializer is IntegerLiteral) {
return (astField.initializer as IntegerLiteral).value;
}
}
} catch (e, s) {
resolver.driver.backend.log
.warning('Could not read schemaVersion from $element', e, s);
}
return null;
}
}

View File

@ -9,6 +9,8 @@ import '../../driver/driver.dart';
class KnownDriftTypes {
final ClassElement tableElement;
final InterfaceType tableType;
final InterfaceType driftDatabase;
final InterfaceType driftAccessor;
final InterfaceElement typeConverter;
final InterfaceElement jsonTypeConverter;
@ -17,6 +19,8 @@ class KnownDriftTypes {
this.tableType,
this.typeConverter,
this.jsonTypeConverter,
this.driftDatabase,
this.driftAccessor,
);
/// Constructs the set of known drift types from a helper library, which is
@ -24,15 +28,16 @@ class KnownDriftTypes {
factory KnownDriftTypes._fromLibrary(LibraryElement helper) {
final exportNamespace = helper.exportNamespace;
final tableElement = exportNamespace.get('Table') as ClassElement;
final dbElement = exportNamespace.get('DriftDatabase') as ClassElement;
final daoElement = exportNamespace.get('DriftAccessor') as ClassElement;
return KnownDriftTypes._(
tableElement,
tableElement.instantiate(
typeArguments: const [],
nullabilitySuffix: NullabilitySuffix.none,
),
tableElement.defaultInstantiation,
exportNamespace.get('TypeConverter') as InterfaceElement,
exportNamespace.get('JsonTypeConverter') as InterfaceElement,
dbElement.defaultInstantiation,
daoElement.defaultInstantiation,
);
}
@ -65,8 +70,7 @@ class KnownDriftTypes {
}
}
Expression? returnExpressionOfMethod(MethodDeclaration method,
{bool reportErrorOnFailure = true}) {
Expression? returnExpressionOfMethod(MethodDeclaration method) {
final body = method.body;
if (body is! ExpressionFunctionBody) {
@ -131,6 +135,11 @@ extension IsFromDrift on Element {
}
}
extension on InterfaceElement {
InterfaceType get defaultInstantiation => instantiate(
typeArguments: const [], nullabilitySuffix: NullabilitySuffix.none);
}
extension TypeUtils on DartType {
String? get nameIfInterfaceType {
final $this = this;

View File

@ -1,6 +1,7 @@
import 'package:analyzer/dart/ast/ast.dart' as dart;
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/dart/element/visitor.dart';
import 'package:drift/drift.dart';
import 'package:recase/recase.dart';
import 'package:source_gen/source_gen.dart';
import 'package:sqlparser/sqlparser.dart' hide AnalysisError;
@ -139,7 +140,8 @@ class DiscoverStep {
class _FindDartElements extends RecursiveElementVisitor<void> {
final DiscoverStep _discoverStep;
final LibraryElement _library;
final TypeChecker _isTable;
final TypeChecker _isTable, _isDatabase, _isDao;
final List<Future<void>> _pendingWork = [];
@ -148,7 +150,9 @@ class _FindDartElements extends RecursiveElementVisitor<void> {
_FindDartElements(
this._discoverStep, this._library, KnownDriftTypes knownTypes)
: _isTable = TypeChecker.fromStatic(knownTypes.tableType);
: _isTable = TypeChecker.fromStatic(knownTypes.tableType),
_isDatabase = TypeChecker.fromStatic(knownTypes.driftDatabase),
_isDao = TypeChecker.fromStatic(knownTypes.driftAccessor);
Future<void> find() async {
visitLibraryElement(_library);
@ -164,6 +168,18 @@ class _FindDartElements extends RecursiveElementVisitor<void> {
found.add(DiscoveredDartTable(id, element));
}));
} else {
// Check if this class declares a database or a database accessor.
final firstDb = _isDatabase.firstAnnotationOf(element);
final firstDao = _isDao.firstAnnotationOf(element);
final id = _discoverStep._id(element.name);
if (firstDb != null) {
found.add(DiscoveredBaseAccessor(id, element, firstDb, true));
} else if (firstDao != null) {
found.add(DiscoveredBaseAccessor(id, element, firstDao, false));
}
}
super.visitClassElement(element);

View File

@ -1,3 +1,4 @@
import 'package:analyzer/dart/constant/value.dart';
import 'package:analyzer/dart/element/element.dart';
import 'package:sqlparser/sqlparser.dart';
@ -25,3 +26,13 @@ abstract class DiscoveredDartElement<DE extends Element>
class DiscoveredDartTable extends DiscoveredDartElement<ClassElement> {
DiscoveredDartTable(super.ownId, super.dartElement);
}
class DiscoveredBaseAccessor extends DiscoveredDartElement<ClassElement> {
final bool isDatabase;
final DartObject annotation;
bool get isAccessor => !isDatabase;
DiscoveredBaseAccessor(
super.ownId, super.dartElement, this.annotation, this.isDatabase);
}

View File

@ -6,8 +6,10 @@ import '../driver/error.dart';
import '../driver/state.dart';
import '../results/element.dart';
import 'dart/accessor.dart' as dart_accessor;
import 'dart/table.dart' as dart_table;
import 'drift/index.dart' as drift_index;
import 'drift/query.dart' as drift_query;
import 'drift/table.dart' as drift_table;
import 'drift/trigger.dart' as drift_trigger;
import 'drift/view.dart' as drift_view;
@ -35,6 +37,9 @@ class DriftResolver {
} else if (discovered is DiscoveredDriftIndex) {
resolver = drift_index.DriftIndexResolver(
fileState, discovered, this, elementState);
} else if (discovered is DiscoveredDriftStatement) {
resolver = drift_query.DriftQueryResolver(
fileState, discovered, this, elementState);
} else if (discovered is DiscoveredDriftTrigger) {
resolver = drift_trigger.DriftTriggerResolver(
fileState, discovered, this, elementState);
@ -44,6 +49,9 @@ class DriftResolver {
} else if (discovered is DiscoveredDartTable) {
resolver = dart_table.DartTableResolver(
fileState, discovered, this, elementState);
} else if (discovered is DiscoveredBaseAccessor) {
resolver = dart_accessor.DartAccessorResolver(
fileState, discovered, this, elementState);
} else {
throw UnimplementedError('TODO: Handle $discovered');
}
@ -178,7 +186,22 @@ abstract class LocalElementResolver<T extends DiscoveredElement> {
DriftAnalysisError Function(String msg) createError,
) async {
final result = await resolver.resolveReference(discovered.ownId, reference);
return _handleReferenceResult(result, createError);
}
Future<E?> resolveDartReferenceOrReportError<E extends DriftElement>(
Element reference,
DriftAnalysisError Function(String msg) createError,
) async {
final result =
await resolver.resolveDartReference(discovered.ownId, reference);
return _handleReferenceResult(result, createError);
}
E? _handleReferenceResult<E extends DriftElement>(
ResolveReferencedElementResult result,
DriftAnalysisError Function(String msg) createError,
) {
if (result is ResolvedReferenceFound) {
final element = result.element;
if (element is E) {

View File

@ -157,6 +157,11 @@ class _AddFromDartType extends TypeVisitor<void> {
}
}
@override
void visitRecordType(RecordType type) {
throw UnsupportedError('RecordType to Dart source code');
}
@override
void visitDynamicType(DynamicType type) {
_builder.addText('dynamic');

View File

@ -0,0 +1,103 @@
import 'package:json_annotation/json_annotation.dart';
import 'dart.dart';
import 'element.dart';
import 'table.dart';
import 'query.dart';
import 'view.dart';
part '../../generated/analysis/results/database.g.dart';
/// Abstract base class for databases and DAO declarations.
abstract class BaseDriftAccessor extends DriftElement {
/// All tables that have been declared on this accessor directly.
///
/// This contains the `tables` field from a `DriftDatabase` or `DriftAccessor`
/// annotation, but not tables that are declared in imported files.
final List<DriftTable> declaredTables;
/// All views that have been declared on this accessor directly.
///
/// This contains the `views` field from a `DriftDatabase` or `DriftAccessor`
/// annotation, but not views that are declared in imported files.
final List<DriftView> declaredViews;
/// The `includes` field from the annotation.
final List<Uri> declaredIncludes;
/// All queries declared directly in the annotation.
final List<QueryOnAccessor> declaredQueries;
BaseDriftAccessor({
required DriftElementId id,
required DriftDeclaration declaration,
required this.declaredTables,
required this.declaredViews,
required this.declaredIncludes,
required this.declaredQueries,
}) : super(id, declaration);
@override
Iterable<DriftElement> get references => [
// todo: Track dependencies on includes somehow
...declaredTables,
...declaredViews,
];
}
/// A database, declared via a `DriftDatabase` annotation on a Dart class.
class DriftDatabase extends BaseDriftAccessor {
/// If the source database class overrides `schemaVersion` and returns a
/// simple integer literal, stores that version.
///
/// This is optionally used by the migration tooling to store the schema in a
/// versioned file.
final int? schemaVersion;
final List<AnnotatedDartCode> accessorTypes;
DriftDatabase({
required super.id,
required super.declaration,
required super.declaredTables,
required super.declaredViews,
required super.declaredIncludes,
required super.declaredQueries,
this.schemaVersion,
this.accessorTypes = const [],
});
}
/// A Dart class with a similar API to a database, providing a view over a
/// subset of tables.
class DatabaseAccessor extends BaseDriftAccessor {
/// The database class this dao belongs to.
final AnnotatedDartCode databaseClass;
DatabaseAccessor({
required super.id,
required super.declaration,
required super.declaredTables,
required super.declaredViews,
required super.declaredIncludes,
required super.declaredQueries,
required this.databaseClass,
});
}
/// A query defined on a [BaseDriftAccessor].
///
/// Similar to a [DefinedSqlQuery] defined in a `.drift` file, most of the SQL
/// analysis happens during code generation because intermediate state is hard
/// to serialize and there are little benefits of analyzing queries early.
@JsonSerializable()
class QueryOnAccessor {
final String name;
final String sql;
QueryOnAccessor(this.name, this.sql);
factory QueryOnAccessor.fromJson(Map json) => _$QueryOnAccessorFromJson(json);
Map<String, Object?> toJson() => _$QueryOnAccessorToJson(this);
}

View File

@ -1,5 +1,6 @@
export 'column.dart';
export 'dart.dart';
export 'database.dart';
export 'element.dart';
export 'index.dart';
export 'query.dart';

View File

@ -88,6 +88,36 @@ class ElementSerializer {
'name_of_row_class': element.nameOfRowClass,
'source': serializedSource,
};
} else if (element is BaseDriftAccessor) {
String type;
if (element is DriftDatabase) {
type = 'database';
} else {
type = 'dao';
}
additionalInformation = {
'type': type,
'tables': [
for (final table in element.declaredTables)
_serializeElementReference(table),
],
'views': [
for (final view in element.declaredViews)
_serializeElementReference(view),
],
'includes': [
for (final include in element.declaredIncludes) include.toString()
],
'queries': element.declaredQueries,
if (element is DatabaseAccessor)
'database': element.databaseClass.toJson(),
if (element is DriftDatabase) ...{
'schema_version': element.schemaVersion,
'daos': element.accessorTypes,
}
};
} else {
throw UnimplementedError('Unknown element $element');
}
@ -207,6 +237,11 @@ class _DartTypeSerializer extends TypeVisitor<Map<String, Object?>> {
};
}
@override
Map<String, Object?> visitRecordType(RecordType type) {
throw UnsupportedError('Not yet supported: Record type serialization');
}
@override
Map<String, Object?> visitNeverType(NeverType type) {
return _simple('Never', type.nullabilitySuffix);

View File

@ -0,0 +1,18 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of '../../../analysis/results/database.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
QueryOnAccessor _$QueryOnAccessorFromJson(Map json) => QueryOnAccessor(
json['name'] as String,
json['sql'] as String,
);
Map<String, dynamic> _$QueryOnAccessorToJson(QueryOnAccessor instance) =>
<String, dynamic>{
'name': instance.name,
'sql': instance.sql,
};