Start generator refactoring

Remove global shared state in favor of sessions that are only valid for a single build step.
This commit is contained in:
Simon Binder 2019-07-18 18:01:27 +02:00
parent 33f1732ad1
commit c5e9e8cfc8
No known key found for this signature in database
GPG Key ID: 7891917E4147B8C0
20 changed files with 200 additions and 127 deletions

View File

@ -864,7 +864,6 @@ class TableWithoutPKData extends DataClass
stringType.mapFromDatabaseResponse(data['${effectivePrefix}custom'])),
);
}
factory TableWithoutPKData.fromJson(Map<String, dynamic> json,
{ValueSerializer serializer = const ValueSerializer.defaults()}) {
return TableWithoutPKData(

View File

@ -1,7 +1,6 @@
import 'package:build/build.dart';
import 'package:moor_generator/src/dao_generator.dart';
import 'package:moor_generator/src/options.dart';
import 'package:moor_generator/src/shared_state.dart';
import 'package:moor_generator/src/state/options.dart';
import 'package:source_gen/source_gen.dart';
import 'package:moor_generator/src/moor_generator.dart';
@ -9,7 +8,12 @@ Builder moorBuilder(BuilderOptions options) {
final writeFromString =
options.config['write_from_json_string_constructor'] as bool ?? false;
final parsedOptions = MoorOptions(writeFromString);
final state = SharedState(parsedOptions);
return SharedPartBuilder([MoorGenerator(state), DaoGenerator(state)], 'moor');
return SharedPartBuilder(
[
MoorGenerator(parsedOptions),
DaoGenerator(parsedOptions),
],
'moor',
);
}

View File

@ -1,8 +1,9 @@
import 'package:analyzer/dart/element/element.dart';
import 'package:build/build.dart';
import 'package:moor_generator/src/parser/sql/sql_parser.dart';
import 'package:moor_generator/src/shared_state.dart';
import 'package:moor/moor.dart';
import 'package:moor_generator/src/state/generator_state.dart';
import 'package:moor_generator/src/state/options.dart';
import 'package:moor_generator/src/writer/query_writer.dart';
import 'package:moor_generator/src/writer/result_set_writer.dart';
import 'package:source_gen/source_gen.dart';
@ -10,18 +11,19 @@ import 'package:source_gen/source_gen.dart';
import 'model/sql_query.dart';
class DaoGenerator extends GeneratorForAnnotation<UseDao> {
final SharedState state;
final MoorOptions options;
DaoGenerator(this.state);
DaoGenerator(this.options);
@override
generateForAnnotatedElement(
Element element, ConstantReader annotation, BuildStep buildStep) async {
final state = useState(() => GeneratorState(options));
final session = state.startSession(buildStep);
final tableTypes =
annotation.peek('tables').listValue.map((obj) => obj.toTypeValue());
final parsedTables = await Stream.fromIterable(tableTypes)
.asyncMap((type) => state.parseType(type, element))
.toList();
final parsedTables = await session.parseTables(tableTypes, element);
final queries = annotation.peek('queries')?.mapValue ?? {};
if (element is! ClassElement) {
@ -50,7 +52,7 @@ class DaoGenerator extends GeneratorForAnnotation<UseDao> {
}
if (queries.isNotEmpty) {
final parser = SqlParser(state, parsedTables, queries)..parse();
final parser = SqlParser(session, parsedTables, queries)..parse();
resolvedQueries = parser.foundQueries;
}

View File

@ -2,9 +2,16 @@ import 'package:moor_generator/src/model/specified_column.dart';
import 'package:analyzer/dart/element/element.dart';
import 'package:recase/recase.dart';
/// A parsed table, declared in code by extending `Table` and referencing that
/// table in `@UseMoor` or `@UseDao`.
class SpecifiedTable {
/// The [ClassElement] for the class that declares this table.
final ClassElement fromClass;
/// The columns declared in this table.
final List<SpecifiedColumn> columns;
/// The name of this table when stored in the database
final String sqlName;
/// The name for the data class associated with this table

View File

@ -2,9 +2,8 @@ import 'package:moor/moor.dart';
import 'package:analyzer/dart/element/element.dart';
import 'package:build/build.dart';
import 'package:moor_generator/src/model/specified_database.dart';
import 'package:moor_generator/src/model/specified_table.dart';
import 'package:moor_generator/src/options.dart';
import 'package:moor_generator/src/shared_state.dart';
import 'package:moor_generator/src/state/generator_state.dart';
import 'package:moor_generator/src/state/options.dart';
import 'package:moor_generator/src/writer/database_writer.dart';
import 'package:source_gen/source_gen.dart';
@ -12,14 +11,15 @@ import 'model/sql_query.dart';
import 'parser/sql/sql_parser.dart';
class MoorGenerator extends GeneratorForAnnotation<UseMoor> {
final SharedState state;
MoorOptions get options => state.options;
MoorGenerator(this.state);
final MoorOptions options;
MoorGenerator(this.options);
@override
generateForAnnotatedElement(
Element element, ConstantReader annotation, BuildStep buildStep) async {
final state = useState(() => GeneratorState(options));
final session = state.startSession(buildStep);
final tableTypes =
annotation.peek('tables').listValue.map((obj) => obj.toTypeValue());
final daoTypes = annotation
@ -29,24 +29,20 @@ class MoorGenerator extends GeneratorForAnnotation<UseMoor> {
.toList();
final queries = annotation.peek('queries')?.mapValue ?? {};
final tablesForThisDb = <SpecifiedTable>[];
final tablesForThisDb = await session.parseTables(tableTypes, element);
var resolvedQueries = <SqlQuery>[];
for (var table in tableTypes) {
tablesForThisDb.add(await state.parseType(table, element));
}
if (queries.isNotEmpty) {
final parser = SqlParser(state, tablesForThisDb, queries)..parse();
final parser = SqlParser(session, tablesForThisDb, queries)..parse();
resolvedQueries = parser.foundQueries;
}
if (state.errors.errors.isNotEmpty) {
if (session.errors.errors.isNotEmpty) {
print('Warning: There were some errors while running '
'moor_generator on ${buildStep.inputId.path}:');
for (var error in state.errors.errors) {
for (var error in session.errors.errors) {
print(error.message);
if (error.affectedElement != null) {
@ -54,7 +50,6 @@ class MoorGenerator extends GeneratorForAnnotation<UseMoor> {
print('${span.start.toolString}\n${span.highlight()}');
}
}
state.errors.errors.clear();
}
if (tablesForThisDb.isEmpty) return '';

View File

@ -1,10 +1,10 @@
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/dart/element/type.dart';
import 'package:moor_generator/src/errors.dart';
import 'package:moor_generator/src/state/errors.dart';
import 'package:moor_generator/src/model/specified_column.dart';
import 'package:moor_generator/src/parser/parser.dart';
import 'package:moor_generator/src/shared_state.dart';
import 'package:moor_generator/src/state/session.dart';
import 'package:moor_generator/src/utils/type_utils.dart';
import 'package:recase/recase.dart';
@ -39,7 +39,7 @@ const String _errorMessage = 'This getter does not create a valid column that '
'columns are formed. If you have any questions, feel free to raise an issue.';
class ColumnParser extends ParserBase {
ColumnParser(SharedState state) : super(state);
ColumnParser(GeneratorSession session) : super(session);
SpecifiedColumn parse(MethodDeclaration getter, Element getterElement) {
/*
@ -55,7 +55,7 @@ class ColumnParser extends ParserBase {
final expr = returnExpressionOfMethod(getter);
if (!(expr is FunctionExpressionInvocation)) {
state.errors.add(MoorError(
session.errors.add(MoorError(
affectedElement: getter.declaredElement,
message: _errorMessage,
critical: true,
@ -89,7 +89,7 @@ class ColumnParser extends ParserBase {
switch (methodName) {
case _methodNamed:
if (foundExplicitName != null) {
state.errors.add(
session.errors.add(
MoorError(
critical: false,
affectedElement: getter.declaredElement,
@ -102,7 +102,7 @@ class ColumnParser extends ParserBase {
foundExplicitName =
readStringLiteral(remainingExpr.argumentList.arguments.first, () {
state.errors.add(
session.errors.add(
MoorError(
critical: false,
affectedElement: getter.declaredElement,
@ -138,7 +138,7 @@ class ColumnParser extends ParserBase {
case _methodCustomConstraint:
foundCustomConstraint =
readStringLiteral(remainingExpr.argumentList.arguments.first, () {
state.errors.add(
session.errors.add(
MoorError(
critical: false,
affectedElement: getter.declaredElement,

View File

@ -1,7 +1,7 @@
import 'package:analyzer/dart/ast/ast.dart';
import 'package:moor_generator/src/errors.dart';
import 'package:moor_generator/src/state/errors.dart';
import 'package:moor_generator/src/model/specified_table.dart';
import 'package:moor_generator/src/shared_state.dart';
import 'package:moor_generator/src/state/session.dart';
class Parser {
List<SpecifiedTable> specifiedTables;
@ -10,15 +10,15 @@ class Parser {
}
class ParserBase {
final SharedState state;
final GeneratorSession session;
ParserBase(this.state);
ParserBase(this.session);
Expression returnExpressionOfMethod(MethodDeclaration method) {
final body = method.body;
if (!(body is ExpressionFunctionBody)) {
state.errors.add(MoorError(
session.errors.add(MoorError(
affectedElement: method.declaredElement,
critical: true,
message:

View File

@ -1,15 +1,15 @@
import 'package:analyzer/dart/constant/value.dart';
import 'package:moor_generator/src/errors.dart';
import 'package:moor_generator/src/state/errors.dart';
import 'package:moor_generator/src/model/specified_table.dart';
import 'package:moor_generator/src/model/sql_query.dart';
import 'package:moor_generator/src/parser/sql/query_handler.dart';
import 'package:moor_generator/src/parser/sql/type_mapping.dart';
import 'package:moor_generator/src/shared_state.dart';
import 'package:moor_generator/src/state/session.dart';
import 'package:sqlparser/sqlparser.dart' hide ResultColumn;
class SqlParser {
final List<SpecifiedTable> tables;
final SharedState state;
final GeneratorSession session;
final Map<DartObject, DartObject> definedQueries;
final TypeMapper _mapper = TypeMapper();
@ -17,7 +17,7 @@ class SqlParser {
final List<SqlQuery> foundQueries = [];
SqlParser(this.state, this.tables, this.definedQueries);
SqlParser(this.session, this.tables, this.definedQueries);
void _spawnEngine() {
_engine = SqlEngine();
@ -35,14 +35,14 @@ class SqlParser {
try {
context = _engine.analyze(sql);
} catch (e, s) {
state.errors.add(MoorError(
session.errors.add(MoorError(
critical: true,
message: 'Error while trying to parse $sql: $e, $s'));
return;
}
for (var error in context.errors) {
state.errors.add(MoorError(
session.errors.add(MoorError(
message: 'The sql query $sql is invalid: $error',
));
}

View File

@ -1,17 +1,17 @@
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/element/element.dart';
import 'package:moor_generator/src/errors.dart';
import 'package:moor_generator/src/state/errors.dart';
import 'package:moor_generator/src/model/specified_column.dart';
import 'package:moor_generator/src/model/specified_table.dart';
import 'package:moor_generator/src/parser/parser.dart';
import 'package:moor_generator/src/shared_state.dart';
import 'package:moor_generator/src/state/session.dart';
import 'package:moor_generator/src/utils/names.dart';
import 'package:moor_generator/src/utils/type_utils.dart';
import 'package:recase/recase.dart';
import 'package:moor/sqlite_keywords.dart';
class TableParser extends ParserBase {
TableParser(SharedState state) : super(state);
TableParser(GeneratorSession session) : super(session);
Future<SpecifiedTable> parse(ClassElement element) async {
final sqlName = await _parseTableName(element);
@ -53,12 +53,12 @@ class TableParser extends ParserBase {
// we expect something like get tableName => "myTableName", the getter
// must do nothing more complicated
final tableNameDeclaration =
await state.loadElementDeclaration(tableNameGetter);
await session.loadElementDeclaration(tableNameGetter);
final returnExpr = returnExpressionOfMethod(
tableNameDeclaration.node as MethodDeclaration);
final tableName = readStringLiteral(returnExpr, () {
state.errors.add(MoorError(
session.errors.add(MoorError(
critical: true,
message:
'This getter must return a string literal, and do nothing more',
@ -75,11 +75,11 @@ class TableParser extends ParserBase {
return null;
}
final resolved = await state.loadElementDeclaration(primaryKeyGetter);
final resolved = await session.loadElementDeclaration(primaryKeyGetter);
final ast = resolved.node as MethodDeclaration;
final body = ast.body;
if (body is! ExpressionFunctionBody) {
state.errors.add(MoorError(
session.errors.add(MoorError(
affectedElement: primaryKeyGetter,
message: 'This must return a set literal using the => syntax!'));
return null;
@ -103,7 +103,7 @@ class TableParser extends ParserBase {
}
}
} else {
state.errors.add(MoorError(
session.errors.add(MoorError(
affectedElement: primaryKeyGetter,
message: 'This must return a set literal!'));
}
@ -112,13 +112,14 @@ class TableParser extends ParserBase {
}
Future<List<SpecifiedColumn>> _parseColumns(ClassElement element) {
return Stream.fromIterable(element.fields)
.where((field) => isColumn(field.type) && field.getter != null)
.asyncMap((field) async {
final resolved = await state.loadElementDeclaration(field.getter);
final columns = element.fields
.where((field) => isColumn(field.type) && field.getter != null);
return Future.wait(columns.map((field) async {
final resolved = await session.loadElementDeclaration(field.getter);
final node = resolved.node as MethodDeclaration;
return state.columnParser.parse(node, field.getter);
}).toList();
return await session.parseColumn(node, field.getter);
}));
}
}

View File

@ -0,0 +1,18 @@
import 'package:analyzer/dart/element/element.dart';
import 'package:moor_generator/src/model/specified_database.dart';
import 'package:moor_generator/src/state/session.dart';
import 'package:source_gen/source_gen.dart';
class UseMoorParser {
final GeneratorSession session;
UseMoorParser(this.session);
/// If [element] has a `@UseMoor` annotation, parses the database model
/// declared by that class and the referenced tables.
Future<SpecifiedDatabase> parseDatabase(
ClassElement element, ConstantReader annotation) {
final tableTypes =
annotation.peek('tables').listValue.map((obj) => obj.toTypeValue());
}
}

View File

@ -1,53 +0,0 @@
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/dart/element/type.dart';
import 'package:analyzer/dart/analysis/results.dart';
import 'package:moor/moor.dart' show Table;
import 'package:moor_generator/src/parser/column_parser.dart';
import 'package:moor_generator/src/parser/table_parser.dart';
import 'package:source_gen/source_gen.dart';
import 'errors.dart';
import 'model/specified_table.dart';
import 'options.dart';
/// Information that is needed for both the regular generator and the dao
/// generator. Kept in sync so it only needs to be evaluated once.
class SharedState {
final ErrorStore errors = ErrorStore();
final MoorOptions options;
TableParser tableParser;
ColumnParser columnParser;
final tableTypeChecker = const TypeChecker.fromRuntime(Table);
final Map<DartType, Future<SpecifiedTable>> _foundTables = {};
SharedState(this.options) {
tableParser = TableParser(this);
columnParser = ColumnParser(this);
}
Future<ElementDeclarationResult> loadElementDeclaration(
Element element) async {
final resolvedLibrary = await element.library.session
.getResolvedLibraryByElement(element.library);
return resolvedLibrary.getElementDeclaration(element);
}
Future<SpecifiedTable> parseType(DartType type, Element initializedBy) {
return _foundTables.putIfAbsent(type, () {
if (!tableTypeChecker.isAssignableFrom(type.element)) {
errors.add(MoorError(
critical: true,
message: 'The type $type is not a moor table',
affectedElement: initializedBy,
));
return null;
} else {
return tableParser.parse(type.element as ClassElement);
}
});
}
}

View File

@ -0,0 +1,39 @@
import 'package:analyzer/dart/element/type.dart';
import 'package:build/build.dart';
import 'package:moor/moor.dart';
import 'package:moor_generator/src/model/specified_table.dart';
import 'package:moor_generator/src/state/session.dart';
import 'package:source_gen/source_gen.dart';
import 'options.dart';
GeneratorState _state;
/// Uses the created instance of the generator state or creates one via the
/// [create] callback if necessary.
GeneratorState useState(GeneratorState Function() create) {
return _state ??= create();
}
class GeneratorState {
final MoorOptions options;
final Map<DartType, Future<SpecifiedTable>> _foundTables = {};
final tableTypeChecker = const TypeChecker.fromRuntime(Table);
GeneratorState(this.options);
GeneratorSession startSession(BuildStep step) {
return GeneratorSession(this, step);
}
/// Parses the [SpecifiedTable] from a [type]. As this operation is very
/// expensive, we always try to only perform it once.
///
/// The [resolve] function is responsible for performing the actual analysis
/// and it will be called when the [type] has not yet been resolved.
Future<SpecifiedTable> parseTable(
DartType type, Future<SpecifiedTable> Function() resolve) {
return _foundTables.putIfAbsent(type, resolve);
}
}

View File

@ -0,0 +1,61 @@
import 'package:analyzer/dart/analysis/results.dart';
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/dart/element/type.dart';
import 'package:build/build.dart';
import 'package:moor_generator/src/model/specified_column.dart';
import 'package:moor_generator/src/model/specified_table.dart';
import 'package:moor_generator/src/parser/column_parser.dart';
import 'package:moor_generator/src/parser/table_parser.dart';
import 'errors.dart';
import 'generator_state.dart';
import 'options.dart';
class GeneratorSession {
final GeneratorState state;
final ErrorStore errors = ErrorStore();
final BuildStep step;
TableParser _tableParser;
ColumnParser _columnParser;
MoorOptions get options => state.options;
GeneratorSession(this.state, this.step) {
_tableParser = TableParser(this);
_columnParser = ColumnParser(this);
}
Future<ElementDeclarationResult> loadElementDeclaration(
Element element) async {
final resolvedLibrary = await element.library.session
.getResolvedLibraryByElement(element.library);
return resolvedLibrary.getElementDeclaration(element);
}
/// Resolves a [SpecifiedTable] for the class of each [DartType] in [types].
/// The [initializedBy] element should be the piece of code that caused the
/// parsing (e.g. the database class that is annotated with `@UseMoor`). This
/// will allow for more descriptive error messages.
Future<List<SpecifiedTable>> parseTables(
Iterable<DartType> types, Element initializedBy) {
return Future.wait(types.map((type) {
if (!state.tableTypeChecker.isAssignableFrom(type.element)) {
errors.add(MoorError(
critical: true,
message: 'The type $type is not a moor table',
affectedElement: initializedBy,
));
return null;
} else {
return _tableParser.parse(type.element as ClassElement);
}
}));
}
Future<SpecifiedColumn> parseColumn(MethodDeclaration m, Element e) {
return Future.value(_columnParser.parse(m, e));
}
}

View File

@ -1,6 +1,6 @@
import 'package:moor_generator/src/model/specified_column.dart';
import 'package:moor_generator/src/model/specified_table.dart';
import 'package:moor_generator/src/options.dart';
import 'package:moor_generator/src/state/options.dart';
import 'package:recase/recase.dart';
const _hashCombine = '\$mrjc';

View File

@ -1,5 +1,5 @@
import 'package:moor_generator/src/model/sql_query.dart';
import 'package:moor_generator/src/options.dart';
import 'package:moor_generator/src/state/options.dart';
import 'package:moor_generator/src/writer/query_writer.dart';
import 'package:moor_generator/src/writer/result_set_writer.dart';
import 'package:recase/recase.dart';

View File

@ -1,6 +1,6 @@
import 'package:moor_generator/src/model/specified_column.dart';
import 'package:moor_generator/src/model/specified_table.dart';
import 'package:moor_generator/src/options.dart';
import 'package:moor_generator/src/state/options.dart';
import 'package:moor_generator/src/utils/string_escaper.dart';
import 'package:moor_generator/src/writer/data_class_writer.dart';
import 'package:moor_generator/src/writer/update_companion_writer.dart';

View File

@ -1,5 +1,5 @@
import 'package:moor_generator/src/model/specified_table.dart';
import 'package:moor_generator/src/options.dart';
import 'package:moor_generator/src/state/options.dart';
class UpdateCompanionWriter {
final SpecifiedTable table;

View File

@ -1,16 +1,17 @@
import 'package:analyzer/dart/element/element.dart';
import 'package:moor_generator/src/model/specified_column.dart';
import 'package:moor_generator/src/model/specified_table.dart';
import 'package:moor_generator/src/options.dart';
import 'package:moor_generator/src/parser/column_parser.dart';
import 'package:moor_generator/src/state/generator_state.dart';
import 'package:moor_generator/src/state/options.dart';
import 'package:moor_generator/src/parser/table_parser.dart';
import 'package:moor_generator/src/shared_state.dart';
import 'package:moor_generator/src/state/session.dart';
import 'package:test_api/test_api.dart';
import 'package:build_test/build_test.dart';
void main() async {
LibraryElement testLib;
SharedState state;
GeneratorState state;
GeneratorSession session;
setUpAll(() async {
testLib = await resolveSource(r'''
@ -52,13 +53,12 @@ void main() async {
});
setUp(() {
state = SharedState(const MoorOptions.defaults())
..columnParser = ColumnParser(state)
..tableParser = TableParser(state);
state = useState(() => GeneratorState(const MoorOptions.defaults()));
session = state.startSession(null);
});
Future<SpecifiedTable> parse(String name) {
return TableParser(state).parse(testLib.getType(name));
return TableParser(session).parse(testLib.getType(name));
}
group('SQL table name', () {
@ -73,9 +73,9 @@ void main() async {
});
test('should not parse for complex methods', () async {
await TableParser(state).parse(testLib.getType('WrongName'));
await TableParser(session).parse(testLib.getType('WrongName'));
expect(state.errors.errors, isNotEmpty);
expect(session.errors.errors, isNotEmpty);
});
});