Resolve Dart expressions in moor files

This commit is contained in:
Simon Binder 2019-11-18 13:59:10 +01:00
parent a17448683e
commit 8819245685
No known key found for this signature in database
GPG Key ID: 7891917E4147B8C0
25 changed files with 434 additions and 119 deletions

View File

@ -27,8 +27,8 @@ dev_dependencies:
test: ^1.9.0
mockito: ^4.1.0
#dependency_overrides:
# moor_generator:
# path: ../moor_generator
# sqlparser:
# path: ../sqlparser
dependency_overrides:
moor_generator:
path: ../moor_generator
sqlparser:
path: ../sqlparser

View File

@ -0,0 +1,23 @@
import 'package:moor/moor.dart';
enum SyncType {
locallyCreated,
locallyUpdated,
synchronized,
}
class SyncTypeConverter extends TypeConverter<SyncType, int> {
const SyncTypeConverter();
@override
SyncType mapToDart(int fromDb) {
if (fromDb == null) return null;
return SyncType.values[fromDb];
}
@override
int mapToSql(SyncType value) {
return value?.index;
}
}

View File

@ -1,10 +1,15 @@
import 'package:moor/moor.dart';
import 'converter.dart';
part 'custom_tables.g.dart';
@UseMoor(
include: {'tables.moor'},
queries: {'writeConfig': 'REPLACE INTO config VALUES (:key, :value)'},
queries: {
'writeConfig': 'REPLACE INTO config (config_key, config_value) '
'VALUES (:key, :value)'
},
)
class CustomTablesDb extends _$CustomTablesDb {
CustomTablesDb(QueryExecutor e) : super(e) {

View File

@ -494,16 +494,20 @@ class WithConstraints extends Table
class Config extends DataClass implements Insertable<Config> {
final String configKey;
final String configValue;
Config({@required this.configKey, this.configValue});
final SyncType syncState;
Config({@required this.configKey, this.configValue, this.syncState});
factory Config.fromData(Map<String, dynamic> data, GeneratedDatabase db,
{String prefix}) {
final effectivePrefix = prefix ?? '';
final stringType = db.typeSystem.forDartType<String>();
final intType = db.typeSystem.forDartType<int>();
return Config(
configKey: stringType
.mapFromDatabaseResponse(data['${effectivePrefix}config_key']),
configValue: stringType
.mapFromDatabaseResponse(data['${effectivePrefix}config_value']),
syncState: ConfigTable.$converter0.mapToDart(intType
.mapFromDatabaseResponse(data['${effectivePrefix}sync_state'])),
);
}
factory Config.fromJson(Map<String, dynamic> json,
@ -511,6 +515,7 @@ class Config extends DataClass implements Insertable<Config> {
return Config(
configKey: serializer.fromJson<String>(json['config_key']),
configValue: serializer.fromJson<String>(json['config_value']),
syncState: serializer.fromJson<SyncType>(json['sync_state']),
);
}
factory Config.fromJsonString(String encodedJson,
@ -523,6 +528,7 @@ class Config extends DataClass implements Insertable<Config> {
return {
'config_key': serializer.toJson<String>(configKey),
'config_value': serializer.toJson<String>(configValue),
'sync_state': serializer.toJson<SyncType>(syncState),
};
}
@ -535,48 +541,62 @@ class Config extends DataClass implements Insertable<Config> {
configValue: configValue == null && nullToAbsent
? const Value.absent()
: Value(configValue),
syncState: syncState == null && nullToAbsent
? const Value.absent()
: Value(syncState),
);
}
Config copyWith({String configKey, String configValue}) => Config(
Config copyWith({String configKey, String configValue, SyncType syncState}) =>
Config(
configKey: configKey ?? this.configKey,
configValue: configValue ?? this.configValue,
syncState: syncState ?? this.syncState,
);
@override
String toString() {
return (StringBuffer('Config(')
..write('configKey: $configKey, ')
..write('configValue: $configValue')
..write('configValue: $configValue, ')
..write('syncState: $syncState')
..write(')'))
.toString();
}
@override
int get hashCode => $mrjf($mrjc(configKey.hashCode, configValue.hashCode));
int get hashCode => $mrjf($mrjc(
configKey.hashCode, $mrjc(configValue.hashCode, syncState.hashCode)));
@override
bool operator ==(other) =>
identical(this, other) ||
(other is Config &&
other.configKey == this.configKey &&
other.configValue == this.configValue);
other.configValue == this.configValue &&
other.syncState == this.syncState);
}
class ConfigCompanion extends UpdateCompanion<Config> {
final Value<String> configKey;
final Value<String> configValue;
final Value<SyncType> syncState;
const ConfigCompanion({
this.configKey = const Value.absent(),
this.configValue = const Value.absent(),
this.syncState = const Value.absent(),
});
ConfigCompanion.insert({
@required String configKey,
this.configValue = const Value.absent(),
this.syncState = const Value.absent(),
}) : configKey = Value(configKey);
ConfigCompanion copyWith(
{Value<String> configKey, Value<String> configValue}) {
{Value<String> configKey,
Value<String> configValue,
Value<SyncType> syncState}) {
return ConfigCompanion(
configKey: configKey ?? this.configKey,
configValue: configValue ?? this.configValue,
syncState: syncState ?? this.syncState,
);
}
}
@ -603,8 +623,16 @@ class ConfigTable extends Table with TableInfo<ConfigTable, Config> {
$customConstraints: '');
}
final VerificationMeta _syncStateMeta = const VerificationMeta('syncState');
GeneratedIntColumn _syncState;
GeneratedIntColumn get syncState => _syncState ??= _constructSyncState();
GeneratedIntColumn _constructSyncState() {
return GeneratedIntColumn('sync_state', $tableName, true,
$customConstraints: '');
}
@override
List<GeneratedColumn> get $columns => [configKey, configValue];
List<GeneratedColumn> get $columns => [configKey, configValue, syncState];
@override
ConfigTable get asDslTable => this;
@override
@ -627,6 +655,7 @@ class ConfigTable extends Table with TableInfo<ConfigTable, Config> {
} else if (configValue.isRequired && isInserting) {
context.missing(_configValueMeta);
}
context.handle(_syncStateMeta, const VerificationResult.success());
return context;
}
@ -647,6 +676,11 @@ class ConfigTable extends Table with TableInfo<ConfigTable, Config> {
if (d.configValue.present) {
map['config_value'] = Variable<String, StringType>(d.configValue.value);
}
if (d.syncState.present) {
final converter = ConfigTable.$converter0;
map['sync_state'] =
Variable<int, IntType>(converter.mapToSql(d.syncState.value));
}
return map;
}
@ -655,6 +689,7 @@ class ConfigTable extends Table with TableInfo<ConfigTable, Config> {
return ConfigTable(_db, alias);
}
static TypeConverter<SyncType, int> $converter0 = const SyncTypeConverter();
@override
final bool dontWriteConstraints = true;
}
@ -916,6 +951,7 @@ abstract class _$CustomTablesDb extends GeneratedDatabase {
return Config(
configKey: row.readString('config_key'),
configValue: row.readString('config_value'),
syncState: ConfigTable.$converter0.mapToDart(row.readInt('sync_state')),
);
}
@ -955,6 +991,7 @@ abstract class _$CustomTablesDb extends GeneratedDatabase {
rowid: row.readInt('rowid'),
configKey: row.readString('config_key'),
configValue: row.readString('config_value'),
syncState: ConfigTable.$converter0.mapToDart(row.readInt('sync_state')),
);
}
@ -968,7 +1005,7 @@ abstract class _$CustomTablesDb extends GeneratedDatabase {
Future<int> writeConfig(String key, String value) {
return customInsert(
'REPLACE INTO config VALUES (:key, :value)',
'REPLACE INTO config (config_key, config_value) VALUES (:key, :value)',
variables: [Variable.withString(key), Variable.withString(value)],
updates: {config},
);
@ -983,19 +1020,24 @@ class ReadRowIdResult {
final int rowid;
final String configKey;
final String configValue;
final SyncType syncState;
ReadRowIdResult({
this.rowid,
this.configKey,
this.configValue,
this.syncState,
});
@override
int get hashCode => $mrjf(
$mrjc(rowid.hashCode, $mrjc(configKey.hashCode, configValue.hashCode)));
int get hashCode => $mrjf($mrjc(
rowid.hashCode,
$mrjc(configKey.hashCode,
$mrjc(configValue.hashCode, syncState.hashCode))));
@override
bool operator ==(other) =>
identical(this, other) ||
(other is ReadRowIdResult &&
other.rowid == this.rowid &&
other.configKey == this.configKey &&
other.configValue == this.configValue);
other.configValue == this.configValue &&
other.syncState == this.syncState);
}

View File

@ -1,3 +1,5 @@
import 'converter.dart';
CREATE TABLE no_ids (
payload BLOB NOT NULL
) WITHOUT ROWID;
@ -17,7 +19,8 @@ CREATE TABLE with_constraints (
create table config (
config_key TEXT not null primary key,
config_value TEXT
config_value TEXT,
sync_state INTEGER MAPPED BY `const SyncTypeConverter()`
) AS "Config";
CREATE TABLE mytable (

View File

@ -1151,7 +1151,8 @@ class $TableWithoutPKTable extends TableWithoutPK
return $TableWithoutPKTable(_db, alias);
}
static CustomConverter $converter0 = const CustomConverter();
static TypeConverter<MyCustomObject, String> $converter0 =
const CustomConverter();
}
class PureDefault extends DataClass implements Insertable<PureDefault> {

View File

@ -1,8 +1,22 @@
builders:
preparing_builder:
import: "package:moor_generator/moor_generator.dart"
builder_factories: ["preparingBuilder"]
build_extensions: {".moor": [".temp.dart", ".dart_in_moor"]}
build_to: cache
auto_apply: dependents
applies_builders: ["moor_generator|moor_cleanup"]
moor_generator:
import: "package:moor_generator/moor_generator.dart"
builder_factories: ["moorBuilder"]
build_extensions: {".dart": [".moor.g.part"]}
auto_apply: dependents
build_to: cache
required_inputs: [".dart_in_moor"]
applies_builders: ["source_gen|combining_builder"]
post_process_builders:
moor_cleanup:
import: "package:moor_generator/moor_generator.dart"
builder_factory: "moorCleanup"

View File

@ -1,4 +1,11 @@
import 'package:build/build.dart';
import 'package:moor_generator/src/backends/build/moor_builder.dart';
import 'package:moor_generator/src/backends/build/preprocess_builder.dart';
Builder preparingBuilder(BuilderOptions options) => PreprocessBuilder();
Builder moorBuilder(BuilderOptions options) => MoorBuilder(options);
PostProcessBuilder moorCleanup(BuilderOptions options) {
return const FileDeletingBuilder(['.temp.dart']);
}

View File

@ -174,7 +174,7 @@ class ColumnParser {
UsedTypeConverter converter;
if (createdTypeConverter != null && typeConverterRuntime != null) {
converter = UsedTypeConverter(
expression: createdTypeConverter,
expression: createdTypeConverter.toSource(),
mappedType: typeConverterRuntime,
sqlType: columnType);
}

View File

@ -21,13 +21,6 @@ class TableParser {
);
table.declaration = TableDeclaration(table, base.step.file, element, null);
var index = 0;
for (var converter in table.converters) {
converter
..index = index++
..table = table;
}
return table;
}

View File

@ -1,3 +1,4 @@
import 'package:analyzer/dart/element/type.dart';
import 'package:moor_generator/src/analyzer/runner/steps.dart';
import 'package:moor_generator/src/analyzer/sql_queries/meta/declarations.dart';
import 'package:moor_generator/src/analyzer/sql_queries/type_mapping.dart';
@ -12,11 +13,11 @@ import 'package:sqlparser/sqlparser.dart';
class CreateTableReader {
/// The AST of this `CREATE TABLE` statement.
final CreateTableStatement stmt;
final Step step;
final ParseMoorStep step;
CreateTableReader(this.stmt, this.step);
SpecifiedTable extractTable(TypeMapper mapper) {
Future<SpecifiedTable> extractTable(TypeMapper mapper) async {
final table = SchemaFromCreateTable(moorExtensions: true).read(stmt);
final foundColumns = <String, SpecifiedColumn>{};
@ -50,8 +51,9 @@ class CreateTableReader {
}
if (constraint is MappedBy) {
converter = _readTypeConverter(constraint);
// don't write MAPPED BY constraints when creating the table
converter = await _readTypeConverter(moorType, constraint);
// don't write MAPPED BY constraints when creating the table, they're
// a convenience feature by the compiler
continue;
}
if (constraint is JsonKey) {
@ -121,8 +123,21 @@ class CreateTableReader {
TableDeclaration(specifiedTable, step.file, null, table.definition);
}
UsedTypeConverter _readTypeConverter(MappedBy mapper) {
// todo we need to somehow parse the dart expression and check types
Future<UsedTypeConverter> _readTypeConverter(
ColumnType sqlType, MappedBy mapper) async {
final code = mapper.mapper.dartCode;
final type = await step.task.backend.resolveTypeOf(step.file.uri, code);
// todo report lint for any of those cases or when resolveTypeOf throws
if (type is! InterfaceType) {
return null;
}
final interfaceType = type as InterfaceType;
// TypeConverter declares a "D mapToDart(S fromDb);". We need to know D
final typeInDart = interfaceType.getMethod('mapToDart').returnType;
return UsedTypeConverter(
expression: code, mappedType: typeInDart, sqlType: sqlType);
}
}

View File

@ -1,45 +0,0 @@
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/element/type.dart';
import 'package:moor_generator/src/analyzer/runner/steps.dart';
/// Resolves the type of Dart expressions given as a string. The
/// [importStatements] are used to discover types.
///
/// The way this works is that we create a fake file for the analyzer. That file
/// has the following content:
/// ```
/// import 'package:moor/moor.dart'; // always imported
/// // all import statements
///
/// var expr = $expression;
/// ```
///
/// We can then obtain the type of an expression by reading the inferred type
/// of the top-level `expr` variable in that source.
class InlineDartResolver {
final List<String> importStatements = [];
final ParseMoorStep step;
InlineDartResolver(this.step);
Future<DartType> resolveDartTypeOf(String expression) async {
final template = _createDartTemplate(expression);
final unit = await step.task.backend.parseSource(template);
final declaration = unit.declarations.single as TopLevelVariableDeclaration;
return declaration.variables.variables.single.initializer.staticType;
}
String _createDartTemplate(String expression) {
final fakeDart = StringBuffer();
fakeDart.write("import 'package:moor/moor.dart';\n");
for (var import in importStatements) {
fakeDart.write("import '$import';\n");
}
fakeDart.write('var expr = $expression;\n');
return fakeDart.toString();
}
}

View File

@ -11,7 +11,7 @@ class MoorParser {
MoorParser(this.step);
Future<ParsedMoorFile> parseAndAnalyze() {
Future<ParsedMoorFile> parseAndAnalyze() async {
final result =
SqlEngine(useMoorExtensions: true).parseMoorFile(step.content);
final parsedFile = result.rootNode as MoorFile;
@ -23,7 +23,6 @@ class MoorParser {
for (var parsedStmt in parsedFile.statements) {
if (parsedStmt is ImportStatement) {
final importStmt = parsedStmt;
step.inlineDartResolver.importStatements.add(importStmt.importedFile);
importStatements.add(importStmt);
} else if (parsedStmt is CreateTableStatement) {
createdReaders.add(CreateTableReader(parsedStmt, step));
@ -43,7 +42,7 @@ class MoorParser {
final createdTables = <SpecifiedTable>[];
final tableDeclarations = <CreateTableStatement, SpecifiedTable>{};
for (var reader in createdReaders) {
final table = reader.extractTable(step.mapper);
final table = await reader.extractTable(step.mapper);
createdTables.add(table);
tableDeclarations[reader.stmt] = table;
}
@ -59,6 +58,6 @@ class MoorParser {
decl.file = analyzedFile;
}
return Future.value(analyzedFile);
return analyzedFile;
}
}

View File

@ -7,7 +7,6 @@ import 'package:moor_generator/src/analyzer/errors.dart';
import 'package:moor_generator/src/analyzer/moor/table_handler.dart';
import 'package:moor_generator/src/analyzer/runner/file_graph.dart';
import 'package:moor_generator/src/analyzer/runner/results.dart';
import 'package:moor_generator/src/analyzer/moor/inline_dart_resolver.dart';
import 'package:moor_generator/src/analyzer/moor/parser.dart';
import 'package:moor_generator/src/analyzer/sql_queries/sql_parser.dart';
import 'package:moor_generator/src/analyzer/sql_queries/type_mapping.dart';

View File

@ -3,11 +3,8 @@ part of '../steps.dart';
class ParseMoorStep extends Step {
final String content;
final TypeMapper mapper = TypeMapper();
/* late final */ InlineDartResolver inlineDartResolver;
ParseMoorStep(Task task, FoundFile file, this.content) : super(task, file) {
inlineDartResolver = InlineDartResolver(this);
}
ParseMoorStep(Task task, FoundFile file, this.content) : super(task, file);
Future<ParsedMoorFile> parseFile() {
final parser = MoorParser(this);

View File

@ -1,5 +1,5 @@
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/dart/element/type.dart';
import 'package:logging/logging.dart';
import 'package:moor_generator/src/analyzer/session.dart';
@ -27,9 +27,13 @@ abstract class BackendTask {
Logger get log;
Future<LibraryElement> resolveDart(Uri uri);
Future<CompilationUnit> parseSource(String dart);
Future<String> readMoor(Uri uri);
Future<DartType> resolveTypeOf(Uri context, String dartExpression) {
throw UnsupportedError('Resolving dart expressions not supported');
}
/// Checks whether a file at [uri] exists.
Future<bool> exists(Uri uri);

View File

@ -1,10 +1,13 @@
import 'package:analyzer/dart/ast/ast.dart';
import 'dart:convert';
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/dart/element/type.dart';
import 'package:build/build.dart' hide log;
import 'package:build/build.dart' as build show log;
import 'package:logging/logging.dart';
import 'package:moor_generator/src/analyzer/runner/file_graph.dart';
import 'package:moor_generator/src/backends/backend.dart';
import 'package:moor_generator/src/backends/build/serialized_types.dart';
class BuildBackend extends Backend {
BuildBackendTask createTask(BuildStep step) {
@ -21,8 +24,10 @@ class BuildBackend extends Backend {
class BuildBackendTask extends BackendTask {
final BuildStep step;
final BuildBackend backend;
final TypeDeserializer typeDeserializer;
BuildBackendTask(this.step, this.backend);
BuildBackendTask(this.step, this.backend)
: typeDeserializer = TypeDeserializer(step);
@override
Uri get entrypoint => step.inputId.uri;
@ -41,11 +46,6 @@ class BuildBackendTask extends BackendTask {
return step.resolver.libraryFor(_resolve(uri));
}
@override
Future<CompilationUnit> parseSource(String dart) async {
return null;
}
@override
Logger get log => build.log;
@ -54,6 +54,25 @@ class BuildBackendTask extends BackendTask {
return step.canRead(_resolve(uri));
}
@override
Future<DartType> resolveTypeOf(Uri context, String dartExpression) async {
// we try to detect all calls of resolveTypeOf in an earlier builder and
// prepare the result. See PreprocessBuilder for details
final preparedHelperFile =
_resolve(context).changeExtension('.dart_in_moor');
if (!await step.canRead(preparedHelperFile)) {
throw AssetNotFoundException(preparedHelperFile);
}
final content = await step.readAsString(preparedHelperFile);
final json = jsonDecode(content) as Map<String, dynamic>;
final serializedType = json[dartExpression] as Map<String, dynamic>;
return typeDeserializer
.deserialize(SerializedType.fromJson(serializedType));
}
Future finish(FoundFile inputFile) async {
// the result could be cached if it was calculated in a previous build step.
// we need to can canRead so that the build package can calculate the

View File

@ -0,0 +1,101 @@
import 'dart:async';
import 'dart:convert';
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/element/element.dart';
import 'package:build/build.dart';
import 'package:moor_generator/src/backends/build/serialized_types.dart';
import 'package:moor_generator/src/utils/string_escaper.dart';
import 'package:sqlparser/sqlparser.dart';
/// A support builder that runs before the main moor_generator to parse and
/// resolve inline Dart resources in a moor file.
///
/// We use this builder to extract and analyze inline Dart expressions from moor
/// files, which are mainly used for type converters. For instance, let's say
/// we had a moor file like this:
/// ```
/// -- called input.moor
/// import 'package:my_package/converter.dart';
///
/// CREATE TABLE users (
/// preferences TEXT MAPPED BY `const PreferencesConverter()`
/// );
/// ```
/// For that file, the [PreprocessBuilder] would generate a `.dart_in_moor` file
/// which contains information about the static type of all expressions in the
/// moor file. The main generator can then read the `.dart_in_moor` file to
/// resolve those expressions.
class PreprocessBuilder extends Builder {
@override
final Map<String, List<String>> buildExtensions = const {
'.moor': ['.temp.dart', '.dart_in_moor'],
};
@override
FutureOr<void> build(BuildStep buildStep) async {
final input = buildStep.inputId;
final moorFileContent = await buildStep.readAsString(input);
final engine = SqlEngine(useMoorExtensions: true);
final parsed = engine.parseMoorFile(moorFileContent);
final dartLexemes = parsed.tokens
.whereType<InlineDartToken>()
.map((token) => token.dartCode)
.toList();
if (dartLexemes.isEmpty) return; // nothing to do, no Dart in this moor file
final importedFiles = parsed.rootNode.allDescendants
.whereType<ImportStatement>()
.map((stmt) => stmt.importedFile)
.where((import) => import.endsWith('.dart'));
// to analyze the expressions, generate a fake Dart file that declares each
// expression in a `var`, we can then read the static type.
final dartBuffer = StringBuffer();
for (final import in importedFiles) {
dartBuffer.write('import ${asDartLiteral(import)};\n');
}
for (var i = 0; i < dartLexemes.length; i++) {
dartBuffer.write('var ${_nameForDartExpr(i)} = ${dartLexemes[i]};\n');
}
final tempDartAsset = input.changeExtension('.temp.dart');
await buildStep.writeAsString(tempDartAsset, dartBuffer.toString());
// we can now resolve the library we just wrote
final createdLibrary = await buildStep.resolver.libraryFor(tempDartAsset);
final resolveResult = await createdLibrary.session
.getResolvedLibraryByElement(createdLibrary);
final serializer = TypeSerializer(buildStep.resolver);
final codeToType = <String, SerializedType>{};
for (var i = 0; i < dartLexemes.length; i++) {
final member =
_findVariableDefinition(_nameForDartExpr(i), createdLibrary);
final node = resolveResult.getElementDeclaration(member).node
as VariableDeclaration;
final type = node.initializer.staticType;
codeToType[dartLexemes[i]] = await serializer.serialize(type);
}
final outputAsset = input.changeExtension('.dart_in_moor');
await buildStep.writeAsString(outputAsset, jsonEncode(codeToType));
}
String _nameForDartExpr(int i) => 'expr_$i';
TopLevelVariableElement _findVariableDefinition(
String name, LibraryElement element) {
return element.units
.expand((u) => u.topLevelVariables)
.firstWhere((e) => e.name == name);
}
}

View File

@ -0,0 +1,134 @@
import 'dart:async';
import 'package:analyzer/dart/analysis/session.dart';
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/dart/element/nullability_suffix.dart';
import 'package:analyzer/dart/element/type.dart';
import 'package:build/build.dart';
import 'package:source_gen/source_gen.dart';
/// A serialized version of a [DartType].
abstract class SerializedType {
factory SerializedType.fromJson(Map<String, dynamic> json) {
switch (json['type'] as String) {
case 'interface':
return _SerializedInterfaceType.fromJson(json);
}
throw ArgumentError('Unknown type kind: ${json['type']}');
}
SerializedType();
Map<String, dynamic> toJson();
}
// todo handle non-interface types, recursive types
class _SerializedInterfaceType extends SerializedType {
final Uri libraryUri;
final String className;
final List<SerializedType> typeArgs;
_SerializedInterfaceType(this.libraryUri, this.className, this.typeArgs);
factory _SerializedInterfaceType.fromJson(Map<String, dynamic> json) {
final serializedTypes = json['type_args'] as List;
return _SerializedInterfaceType(
Uri.parse(json['library'] as String),
json['class_name'] as String,
serializedTypes
.map((raw) => SerializedType.fromJson(raw as Map<String, dynamic>))
.toList(),
);
}
@override
Map<String, dynamic> toJson() {
return {
'type': 'interface',
'library': libraryUri.toString(),
'class_name': className,
'type_args': [for (var type in typeArgs) type.toJson()],
};
}
}
class TypeSerializer {
final Resolver resolver;
TypeSerializer(this.resolver);
Future<SerializedType> serialize(DartType type) async {
if (type is InterfaceType) {
final dartClass = type.element;
Uri uri;
if (dartClass.librarySource.uri.scheme == 'dart') {
uri = dartClass.librarySource.uri;
} else {
uri = (await resolver.assetIdForElement(dartClass)).uri;
}
final serializedArgs =
await Future.wait(type.typeArguments.map(serialize));
return _SerializedInterfaceType(
uri,
dartClass.name,
serializedArgs,
);
} else {
throw UnsupportedError(
"Couldn't serialize $type, we only support interface types");
}
}
}
class TypeDeserializer {
/// The [BuildStep] used to resolve
final BuildStep buildStep;
/// The analysis session used to read libraries from the Dart SDK which can't
/// be obtained via build apis.
AnalysisSession _lastSession;
TypeDeserializer(this.buildStep);
Future<DartType> deserialize(SerializedType type) async {
if (type is _SerializedInterfaceType) {
final library = await _libraryFromUri(type.libraryUri);
final args = await Future.wait(type.typeArgs.map(deserialize));
return LibraryReader(library).findType(type.className).instantiate(
typeArguments: args, nullabilitySuffix: NullabilitySuffix.star);
}
throw AssertionError('Unhandled type: $type');
}
Future<LibraryElement> _libraryFromUri(Uri uri) async {
if (uri.scheme == 'dart') {
final session = await _obtainSession();
return session.getLibraryByUri(uri.toString());
} else {
final library =
await buildStep.resolver.libraryFor(AssetId.resolve(uri.toString()));
_lastSession ??= library?.session;
return library;
}
}
FutureOr<AnalysisSession> _obtainSession() {
if (_lastSession != null) {
return _lastSession;
} else {
// resolve bogus library that's not going to change often. We can use the
// session from that library. Technically, this is non-hermetic, but the
// build runner will throw everything away after an SDK udpate so it
// should be safe
return _libraryFromUri(Uri.parse('package:moor/sqlite_keywords.dart'))
.then((_) => _lastSession);
}
}
}

View File

@ -1,4 +1,3 @@
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/element/element.dart';
import 'package:logging/logging.dart';
import 'package:moor_generator/src/backends/backend.dart';
@ -26,11 +25,6 @@ class PluginTask extends BackendTask {
@override
final Logger log = Logger.root;
@override
Future<CompilationUnit> parseSource(String dart) {
return null;
}
@override
Future<String> readMoor(Uri uri) async {
final path = driver.absolutePath(uri, base: entrypoint);

View File

@ -86,12 +86,23 @@ class SpecifiedTable {
this.overrideWithoutRowId,
this.overrideTableConstraints,
this.overrideDontWriteConstraints})
: _overriddenName = overriddenName;
: _overriddenName = overriddenName {
_attachToConverters();
}
/// Finds all type converters used in this tables.
Iterable<UsedTypeConverter> get converters =>
columns.map((c) => c.typeConverter).where((t) => t != null);
void _attachToConverters() {
var index = 0;
for (var converter in converters) {
converter
..index = index++
..table = this;
}
}
String get displayName {
if (isFromSql) {
return sqlName;

View File

@ -1,4 +1,3 @@
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/element/type.dart';
import 'package:meta/meta.dart';
import 'package:moor_generator/src/model/specified_column.dart';
@ -9,10 +8,10 @@ class UsedTypeConverter {
int index;
SpecifiedTable table;
/// The [Expression] that will construct the type converter at runtime. The
/// The expression that will construct the type converter at runtime. The
/// type converter constructed will map a [mappedType] to the [sqlType] and
/// vice-versa.
final Expression expression;
final String expression;
/// The type that will be present at runtime.
final DartType mappedType;
@ -20,7 +19,11 @@ class UsedTypeConverter {
/// The type that will be written to the database.
final ColumnType sqlType;
DartType get typeOfConverter => expression.staticType;
/// A suitable typename to store an instance of the type converter used here.
String get displayNameOfConverter {
final sqlDartType = dartTypeNames[sqlType];
return 'TypeConverter<${mappedType.displayName}, $sqlDartType>';
}
/// Type converters are stored as static fields in the table that created
/// them. This will be the field name for this converter.

View File

@ -75,8 +75,8 @@ class TableWriter {
void _writeConvertersAsStaticFields() {
for (var converter in table.converters) {
final typeName = converter.typeOfConverter.displayName;
final code = converter.expression.toSource();
final typeName = converter.displayNameOfConverter;
final code = converter.expression;
_buffer..write('static $typeName ${converter.fieldName} = $code;');
}
}

View File

@ -9,7 +9,7 @@ authors:
maintainer: Simon Binder (@simolus3)
environment:
sdk: '>=2.2.0 <3.0.0'
sdk: '>=2.3.0 <3.0.0'
dependencies:
collection: ^1.14.0
@ -24,13 +24,15 @@ dependencies:
# Dart analysis
analyzer: '>=0.36.4 <0.40.0'
analyzer_plugin: '>=0.1.0 <0.3.0'
analyzer_plugin: # '>=0.1.0 <0.3.0' TODO: remove dependency after https://github.com/dart-lang/sdk/pull/39417
path: /home/simon/programming/dart-sdk/pkg/analyzer_plugin
source_span: ^1.5.5
# Build system
build: ^1.1.0
build_config: '>=0.3.1 <1.0.0'
source_gen: ^0.9.4
build_resolvers: '>=1.3.0' # we don't use its api, but let's require 1.3.0 to resolve arbitrary assets
dev_dependencies:
test: ^1.6.0

View File

@ -1,6 +1,5 @@
import 'dart:async';
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/element/element.dart';
import 'package:build/build.dart';
import 'package:build_test/build_test.dart';
@ -68,11 +67,6 @@ class _TestBackendTask extends BackendTask {
return await backend._resolver.libraryFor(AssetId.resolve(path.toString()));
}
@override
Future<CompilationUnit> parseSource(String dart) {
return null;
}
@override
Future<bool> exists(Uri uri) async {
return backend.fakeContent.containsKey(AssetId.resolve(uri.toString()));