mirror of https://github.com/AMT-Cheif/drift.git
Parse entrypoints in new analyzer
This commit is contained in:
parent
090a0053f1
commit
a6caae9d8f
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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');
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
export 'column.dart';
|
||||
export 'dart.dart';
|
||||
export 'database.dart';
|
||||
export 'element.dart';
|
||||
export 'index.dart';
|
||||
export 'query.dart';
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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,
|
||||
};
|
Loading…
Reference in New Issue