mirror of https://github.com/AMT-Cheif/drift.git
Resolve Dart expressions in moor files
This commit is contained in:
parent
a17448683e
commit
8819245685
|
@ -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
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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 (
|
||||
|
|
|
@ -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> {
|
||||
|
|
|
@ -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"
|
|
@ -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']);
|
||||
}
|
||||
|
|
|
@ -174,7 +174,7 @@ class ColumnParser {
|
|||
UsedTypeConverter converter;
|
||||
if (createdTypeConverter != null && typeConverterRuntime != null) {
|
||||
converter = UsedTypeConverter(
|
||||
expression: createdTypeConverter,
|
||||
expression: createdTypeConverter.toSource(),
|
||||
mappedType: typeConverterRuntime,
|
||||
sqlType: columnType);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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;');
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()));
|
||||
|
|
Loading…
Reference in New Issue