diff --git a/moor_generator/lib/src/analyzer/dart/parser.dart b/moor_generator/lib/src/analyzer/dart/parser.dart index fb21528d..c6aa77e6 100644 --- a/moor_generator/lib/src/analyzer/dart/parser.dart +++ b/moor_generator/lib/src/analyzer/dart/parser.dart @@ -7,14 +7,19 @@ import 'package:moor/sqlite_keywords.dart'; import 'package:moor_generator/src/analyzer/errors.dart'; import 'package:moor_generator/src/analyzer/session.dart'; import 'package:moor_generator/src/model/specified_column.dart'; +import 'package:moor_generator/src/model/specified_dao.dart'; +import 'package:moor_generator/src/model/specified_database.dart'; import 'package:moor_generator/src/model/specified_table.dart'; import 'package:moor_generator/src/model/used_type_converter.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:source_gen/source_gen.dart'; part 'column_parser.dart'; part 'table_parser.dart'; +part 'use_dao_parser.dart'; +part 'use_moor_parser.dart'; class MoorDartParser { final DartTask task; diff --git a/moor_generator/lib/src/parser/use_dao_parser.dart b/moor_generator/lib/src/analyzer/dart/use_dao_parser.dart similarity index 59% rename from moor_generator/lib/src/parser/use_dao_parser.dart rename to moor_generator/lib/src/analyzer/dart/use_dao_parser.dart index 603d5366..796cee79 100644 --- a/moor_generator/lib/src/parser/use_dao_parser.dart +++ b/moor_generator/lib/src/analyzer/dart/use_dao_parser.dart @@ -1,12 +1,9 @@ -import 'package:analyzer/dart/element/element.dart'; -import 'package:moor_generator/src/model/specified_dao.dart'; -import 'package:moor_generator/src/state/session.dart'; -import 'package:source_gen/source_gen.dart'; +part of 'parser.dart'; class UseDaoParser { - final GeneratorSession session; + final DartTask dartTask; - UseDaoParser(this.session); + UseDaoParser(this.dartTask); /// If [element] has a `@UseDao` annotation, parses the database model /// declared by that class and the referenced tables. @@ -24,11 +21,11 @@ class UseDaoParser { ?.map((e) => e.toStringValue()) ?? {}; - final parsedTables = await session.parseTables(tableTypes, element); - parsedTables.addAll(await session.resolveIncludes(includes)); + final parsedTables = await dartTask.parseTables(tableTypes, element); + parsedTables.addAll(await dartTask.resolveIncludes(includes)); final parsedQueries = - await session.parseQueries(queryStrings, parsedTables); + await dartTask.parseQueries(queryStrings, parsedTables); return SpecifiedDao(element, parsedTables, parsedQueries); } diff --git a/moor_generator/lib/src/parser/use_moor_parser.dart b/moor_generator/lib/src/analyzer/dart/use_moor_parser.dart similarity index 63% rename from moor_generator/lib/src/parser/use_moor_parser.dart rename to moor_generator/lib/src/analyzer/dart/use_moor_parser.dart index e15bde2f..80ec18f3 100644 --- a/moor_generator/lib/src/parser/use_moor_parser.dart +++ b/moor_generator/lib/src/analyzer/dart/use_moor_parser.dart @@ -1,13 +1,9 @@ -import 'package:analyzer/dart/element/element.dart'; -import 'package:analyzer/dart/element/type.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'; +part of 'parser.dart'; class UseMoorParser { - final GeneratorSession session; + final DartTask task; - UseMoorParser(this.session); + UseMoorParser(this.task); /// If [element] has a `@UseMoor` annotation, parses the database model /// declared by that class and the referenced tables. @@ -25,11 +21,10 @@ class UseMoorParser { ?.map((e) => e.toStringValue()) ?? {}; - final parsedTables = await session.parseTables(tableTypes, element); - parsedTables.addAll(await session.resolveIncludes(includes)); + final parsedTables = await task.parseTables(tableTypes, element); + parsedTables.addAll(await task.resolveIncludes(includes)); - final parsedQueries = - await session.parseQueries(queryStrings, parsedTables); + final parsedQueries = await task.parseQueries(queryStrings, parsedTables); final daoTypes = _readDaoTypes(annotation); return SpecifiedDatabase(element, parsedTables, daoTypes, parsedQueries); diff --git a/moor_generator/lib/src/analyzer/errors.dart b/moor_generator/lib/src/analyzer/errors.dart index 9c599117..8673eaf6 100644 --- a/moor_generator/lib/src/analyzer/errors.dart +++ b/moor_generator/lib/src/analyzer/errors.dart @@ -1,22 +1,34 @@ import 'package:analyzer/dart/element/element.dart'; import 'package:collection/collection.dart'; +import 'package:meta/meta.dart'; +import 'package:source_span/source_span.dart'; /// Base class for errors that can be presented to an user. class MoorError { final Severity severity; + final String message; - MoorError(this.severity); + MoorError({@required this.severity, this.message}); } class ErrorInDartCode extends MoorError { - final String message; final Element affectedElement; ErrorInDartCode( - {this.message, + {String message, this.affectedElement, Severity severity = Severity.warning}) - : super(severity); + : super(severity: severity, message: message); +} + +class ErrorInMoorFile extends MoorError { + final FileSpan span; + + ErrorInMoorFile( + {@required this.span, + String message, + Severity severity = Severity.warning}) + : super(message: message, severity: severity); } class ErrorSink { diff --git a/moor_generator/lib/src/parser/moor/parsed_moor_file.dart b/moor_generator/lib/src/analyzer/moor/create_table_reader.dart similarity index 82% rename from moor_generator/lib/src/parser/moor/parsed_moor_file.dart rename to moor_generator/lib/src/analyzer/moor/create_table_reader.dart index aabdae85..21bf136d 100644 --- a/moor_generator/lib/src/parser/moor/parsed_moor_file.dart +++ b/moor_generator/lib/src/analyzer/moor/create_table_reader.dart @@ -1,40 +1,17 @@ +import 'package:moor_generator/src/analyzer/sql_queries/type_mapping.dart'; import 'package:moor_generator/src/model/specified_column.dart'; import 'package:moor_generator/src/model/specified_table.dart'; import 'package:moor_generator/src/model/used_type_converter.dart'; -import 'package:moor_generator/src/parser/sql/type_mapping.dart'; import 'package:moor_generator/src/utils/names.dart'; import 'package:moor_generator/src/utils/string_escaper.dart'; import 'package:recase/recase.dart'; import 'package:sqlparser/sqlparser.dart'; -/* -We're in the process of defining what a .moor file could actually look like. -At the moment, we only support "CREATE TABLE" statements: -``` // content of a .moor file -CREATE TABLE users ( - id INTEGER NOT NULL PRIMARY KEY AUTO_INCREMENT, - name VARCHAR(100) NOT NULL, -) -``` - -In the future, we'd also like to support -- import statements between moor files -- import statements from moor files referencing tables declared via the Dart DSL -- declaring statements in these files, similar to how compiled statements work - with the annotation. - */ - -class ParsedMoorFile { - final List declaredTables; - - ParsedMoorFile(this.declaredTables); -} - -class CreateTable { +class CreateTableReader { /// The AST of this `CREATE TABLE` statement. final ParseResult ast; - CreateTable(this.ast); + CreateTableReader(this.ast); SpecifiedTable extractTable(TypeMapper mapper) { final table = diff --git a/moor_generator/lib/src/analyzer/moor/parser.dart b/moor_generator/lib/src/analyzer/moor/parser.dart new file mode 100644 index 00000000..50d2d8f9 --- /dev/null +++ b/moor_generator/lib/src/analyzer/moor/parser.dart @@ -0,0 +1,45 @@ +import 'package:moor_generator/src/analyzer/errors.dart'; +import 'package:moor_generator/src/analyzer/moor/create_table_reader.dart'; +import 'package:moor_generator/src/analyzer/results.dart'; +import 'package:moor_generator/src/analyzer/session.dart'; +import 'package:sqlparser/sqlparser.dart'; + +class MoorParser { + final MoorTask task; + + MoorParser(this.task); + + Future parseAndAnalyze() { + final results = + SqlEngine(useMoorExtensions: true).parseMultiple(task.content); + + final createdReaders = []; + + for (var parsedStmt in results) { + if (parsedStmt.rootNode is CreateTableStatement) { + createdReaders.add(CreateTableReader(parsedStmt)); + } else { + task.reportError(ErrorInMoorFile( + span: parsedStmt.rootNode.span, + message: 'At the moment, only CREATE TABLE statements are supported' + 'in .moor files')); + } + } + + // all results have the same list of errors + final sqlErrors = results.isEmpty ? [] : results.first.errors; + + for (var error in sqlErrors) { + task.reportError(ErrorInMoorFile( + span: error.token.span, + message: error.message, + )); + } + + final createdTables = + createdReaders.map((r) => r.extractTable(task.mapper)).toList(); + final parsedFile = ParsedMoorFile(createdTables); + + return Future.value(parsedFile); + } +} diff --git a/moor_generator/lib/src/analyzer/session.dart b/moor_generator/lib/src/analyzer/session.dart index 23cfd185..bbb51cb7 100644 --- a/moor_generator/lib/src/analyzer/session.dart +++ b/moor_generator/lib/src/analyzer/session.dart @@ -1,18 +1,37 @@ import 'dart:async'; +import 'package:analyzer/dart/constant/value.dart'; import 'package:analyzer/dart/element/element.dart'; +import 'package:analyzer/dart/element/type.dart'; +import 'package:moor/moor.dart' show Table; +import 'package:moor_generator/src/analyzer/dart/parser.dart'; import 'package:moor_generator/src/analyzer/errors.dart'; +import 'package:moor_generator/src/analyzer/moor/parser.dart'; import 'package:moor_generator/src/analyzer/results.dart'; +import 'package:moor_generator/src/analyzer/sql_queries/sql_parser.dart'; +import 'package:moor_generator/src/analyzer/sql_queries/type_mapping.dart'; import 'package:moor_generator/src/backends/backend.dart'; +import 'package:moor_generator/src/model/specified_dao.dart'; +import 'package:moor_generator/src/model/specified_database.dart'; +import 'package:moor_generator/src/model/specified_table.dart'; +import 'package:moor_generator/src/model/sql_query.dart'; +import 'package:source_gen/source_gen.dart'; /// Will store cached data about files that have already been analyzed. class MoorSession { MoorSession(); - Future startDartTask(BackendTask backendTask) async { - final library = await backendTask.resolveDart(backendTask.entrypoint); + Future startDartTask(BackendTask backendTask, {String uri}) async { + final input = uri ?? backendTask.entrypoint; + final library = await backendTask.resolveDart(input); return DartTask(this, backendTask, library); } + + Future startMoorTask(BackendTask backendTask, {String uri}) async { + final input = uri ?? backendTask.entrypoint; + final source = await backendTask.readMoor(input); + return MoorTask(backendTask, this, source); + } } /// Used to parse and analyze a single file. @@ -31,27 +50,89 @@ abstract class FileTask { /// Session used to parse a Dart file and extract table information. class DartTask extends FileTask { + static const tableTypeChecker = const TypeChecker.fromRuntime(Table); + final LibraryElement library; + MoorDartParser _parser; + MoorDartParser get parser => _parser; DartTask(MoorSession session, BackendTask task, this.library) - : super(task, session); + : super(task, session) { + _parser = MoorDartParser(this); + } @override FutureOr compute() { // TODO: implement compute return null; } + + /// Parses a [SpecifiedDatabase] from the [ClassElement] which was annotated + /// with `@UseMoor` and the [annotation] reader that reads the `@UseMoor` + /// annotation. + Future parseDatabase( + ClassElement element, ConstantReader annotation) { + return UseMoorParser(this).parseDatabase(element, annotation); + } + + /// Parses a [SpecifiedDao] from a class declaration that has a `UseDao` + /// [annotation]. + Future parseDao( + ClassElement element, ConstantReader annotation) { + return UseDaoParser(this).parseDao(element, annotation); + } + + /// 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> parseTables( + Iterable types, Element initializedBy) { + return Future.wait(types.map((type) { + if (!tableTypeChecker.isAssignableFrom(type.element)) { + reportError(ErrorInDartCode( + severity: Severity.criticalError, + message: 'The type $type is not a moor table', + affectedElement: initializedBy, + )); + return null; + } else { + return parser.parseTable(type.element as ClassElement); + } + })).then((list) => List.from(list)); // make growable + } + + /// Reads all tables declared in sql by a `.moor` file in [paths]. + Future> resolveIncludes(Iterable paths) { + return Stream.fromFutures( + paths.map((path) => session.startMoorTask(backendTask, uri: path))) + .asyncMap((task) => task.compute()) + .expand((file) => file.declaredTables) + .toList(); + } + + Future> parseQueries( + Map fromAnnotation, + List availableTables) { + // no queries declared, so there is no point in starting a sql engine + if (fromAnnotation.isEmpty) return Future.value([]); + + final parser = SqlParser(this, availableTables, fromAnnotation)..parse(); + + return Future.value(parser.foundQueries); + } } class MoorTask extends FileTask { - final List content; + final String content; + final TypeMapper mapper = TypeMapper(); MoorTask(BackendTask task, MoorSession session, this.content) : super(task, session); @override FutureOr compute() { - // TODO: implement compute - return null; + final parser = MoorParser(this); + return parser.parseAndAnalyze(); } } diff --git a/moor_generator/lib/src/parser/sql/affected_tables_visitor.dart b/moor_generator/lib/src/analyzer/sql_queries/affected_tables_visitor.dart similarity index 100% rename from moor_generator/lib/src/parser/sql/affected_tables_visitor.dart rename to moor_generator/lib/src/analyzer/sql_queries/affected_tables_visitor.dart diff --git a/moor_generator/lib/src/parser/sql/query_handler.dart b/moor_generator/lib/src/analyzer/sql_queries/query_handler.dart similarity index 95% rename from moor_generator/lib/src/parser/sql/query_handler.dart rename to moor_generator/lib/src/analyzer/sql_queries/query_handler.dart index bcea44b3..5fcd87d4 100644 --- a/moor_generator/lib/src/parser/sql/query_handler.dart +++ b/moor_generator/lib/src/analyzer/sql_queries/query_handler.dart @@ -1,11 +1,14 @@ import 'package:moor_generator/src/model/sql_query.dart'; import 'package:moor_generator/src/model/used_type_converter.dart'; -import 'package:moor_generator/src/parser/sql/type_mapping.dart'; +import 'package:moor_generator/src/analyzer/sql_queries/type_mapping.dart'; import 'package:moor_generator/src/utils/type_converter_hint.dart'; import 'package:sqlparser/sqlparser.dart' hide ResultColumn; import 'affected_tables_visitor.dart'; +/// Maps an [AnalysisContext] from the sqlparser to a [SqlQuery] from this +/// generator package by determining its type, return columns, variables and so +/// on. class QueryHandler { final String name; final AnalysisContext context; diff --git a/moor_generator/lib/src/parser/sql/sql_parser.dart b/moor_generator/lib/src/analyzer/sql_queries/sql_parser.dart similarity index 72% rename from moor_generator/lib/src/parser/sql/sql_parser.dart rename to moor_generator/lib/src/analyzer/sql_queries/sql_parser.dart index a5c45c43..2fc74859 100644 --- a/moor_generator/lib/src/parser/sql/sql_parser.dart +++ b/moor_generator/lib/src/analyzer/sql_queries/sql_parser.dart @@ -1,16 +1,16 @@ import 'package:analyzer/dart/constant/value.dart'; import 'package:build/build.dart'; -import 'package:moor_generator/src/state/errors.dart'; +import 'package:moor_generator/src/analyzer/errors.dart'; +import 'package:moor_generator/src/analyzer/session.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/state/session.dart'; +import 'package:moor_generator/src/analyzer/sql_queries/query_handler.dart'; +import 'package:moor_generator/src/analyzer/sql_queries/type_mapping.dart'; import 'package:sqlparser/sqlparser.dart' hide ResultColumn; class SqlParser { final List tables; - final GeneratorSession session; + final FileTask task; final Map definedQueries; final TypeMapper _mapper = TypeMapper(); @@ -18,7 +18,7 @@ class SqlParser { final List foundQueries = []; - SqlParser(this.session, this.tables, this.definedQueries); + SqlParser(this.task, this.tables, this.definedQueries); void _spawnEngine() { _engine = SqlEngine(); @@ -36,14 +36,15 @@ class SqlParser { try { context = _engine.analyze(sql); } catch (e, s) { - session.errors.add(MoorError( - critical: true, + task.reportError(MoorError( + severity: Severity.criticalError, message: 'Error while trying to parse $sql: $e, $s')); return; } for (var error in context.errors) { - session.errors.add(MoorError( + task.reportError(MoorError( + severity: Severity.warning, message: 'The sql query $sql is invalid: $error', )); } diff --git a/moor_generator/lib/src/parser/sql/type_mapping.dart b/moor_generator/lib/src/analyzer/sql_queries/type_mapping.dart similarity index 100% rename from moor_generator/lib/src/parser/sql/type_mapping.dart rename to moor_generator/lib/src/analyzer/sql_queries/type_mapping.dart diff --git a/moor_generator/lib/src/backends/build/build_backend.dart b/moor_generator/lib/src/backends/build/build_backend.dart new file mode 100644 index 00000000..f511cd7c --- /dev/null +++ b/moor_generator/lib/src/backends/build/build_backend.dart @@ -0,0 +1,28 @@ +import 'package:analyzer/dart/element/element.dart'; +import 'package:build/build.dart'; +import 'package:moor_generator/src/backends/backend.dart'; + +class BuildBackend extends Backend {} + +class BuildBackendTask extends BackendTask { + final BuildStep step; + + BuildBackendTask(this.step); + + @override + String get entrypoint => step.inputId.path; + + AssetId _resolve(String uri) { + return AssetId.resolve(uri, from: step.inputId); + } + + @override + Future readMoor(String path) { + return step.readAsString(_resolve(path)); + } + + @override + Future resolveDart(String path) { + return step.resolver.libraryFor(_resolve(path)); + } +} diff --git a/moor_generator/lib/src/backends/build/moor_builder.dart b/moor_generator/lib/src/backends/build/moor_builder.dart index e88276f5..66698c07 100644 --- a/moor_generator/lib/src/backends/build/moor_builder.dart +++ b/moor_generator/lib/src/backends/build/moor_builder.dart @@ -1,10 +1,14 @@ import 'package:build/build.dart'; +import 'package:moor_generator/src/backends/build/build_backend.dart'; import 'package:moor_generator/src/dao_generator.dart'; import 'package:moor_generator/src/moor_generator.dart'; -import 'package:moor_generator/src/state/options.dart'; import 'package:source_gen/source_gen.dart'; +part 'options.dart'; + class MoorBuilder extends SharedPartBuilder { + final BuildBackend backend = BuildBackend(); + factory MoorBuilder(BuilderOptions options) { final parsedOptions = MoorOptions.fromBuilder(options.config); diff --git a/moor_generator/lib/src/state/options.dart b/moor_generator/lib/src/backends/build/options.dart similarity index 96% rename from moor_generator/lib/src/state/options.dart rename to moor_generator/lib/src/backends/build/options.dart index 9d4e6665..c3bdf26c 100644 --- a/moor_generator/lib/src/state/options.dart +++ b/moor_generator/lib/src/backends/build/options.dart @@ -1,3 +1,5 @@ +part of 'moor_builder.dart'; + class MoorOptions { final bool generateFromJsonStringConstructor; diff --git a/moor_generator/lib/src/parser/column_parser.dart b/moor_generator/lib/src/parser/column_parser.dart deleted file mode 100644 index a02ddc65..00000000 --- a/moor_generator/lib/src/parser/column_parser.dart +++ /dev/null @@ -1,223 +0,0 @@ -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/model/used_type_converter.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/state/session.dart'; -import 'package:moor_generator/src/utils/type_utils.dart'; -import 'package:recase/recase.dart'; - -const String startInt = 'integer'; -const String startString = 'text'; -const String startBool = 'boolean'; -const String startDateTime = 'dateTime'; -const String startBlob = 'blob'; -const String startReal = 'real'; - -final Set starters = { - startInt, - startString, - startBool, - startDateTime, - startBlob, - startReal, -}; - -const String _methodNamed = 'named'; -const String _methodReferences = 'references'; -const String _methodAutoIncrement = 'autoIncrement'; -const String _methodWithLength = 'withLength'; -const String _methodNullable = 'nullable'; -const String _methodCustomConstraint = 'customConstraint'; -const String _methodDefault = 'withDefault'; -const String _methodMap = 'map'; - -const String _errorMessage = 'This getter does not create a valid column that ' - 'can be parsed by moor. Please refer to the readme from moor to see how ' - 'columns are formed. If you have any questions, feel free to raise an issue.'; - -class ColumnParser extends ParserBase { - ColumnParser(GeneratorSession session) : super(session); - - SpecifiedColumn parse(MethodDeclaration getter, Element getterElement) { - /* - These getters look like this: ... get id => integer().autoIncrement()(); - The last () is a FunctionExpressionInvocation, the entries before that - (here autoIncrement and integer) are MethodInvocations. - We go through each of the method invocations until we hit one that starts - the chain (integer, text, boolean, etc.). From each method in the chain, - we can extract what it means for the column (name, auto increment, PK, - constraints...). - */ - - final expr = returnExpressionOfMethod(getter); - - if (!(expr is FunctionExpressionInvocation)) { - session.errors.add(MoorError( - affectedElement: getter.declaredElement, - message: _errorMessage, - critical: true, - )); - - return null; - } - - var remainingExpr = - (expr as FunctionExpressionInvocation).function as MethodInvocation; - - String foundStartMethod; - String foundExplicitName; - String foundCustomConstraint; - Expression foundDefaultExpression; - Expression createdTypeConverter; - DartType typeConverterRuntime; - var nullable = false; - - final foundFeatures = []; - - while (true) { - final methodName = remainingExpr.methodName.name; - - if (starters.contains(methodName)) { - foundStartMethod = methodName; - break; - } - - switch (methodName) { - case _methodNamed: - if (foundExplicitName != null) { - session.errors.add( - MoorError( - critical: false, - affectedElement: getter.declaredElement, - message: - "You're setting more than one name here, the first will " - 'be used', - ), - ); - } - - foundExplicitName = - readStringLiteral(remainingExpr.argumentList.arguments.first, () { - session.errors.add( - MoorError( - critical: false, - affectedElement: getter.declaredElement, - message: - 'This table name is cannot be resolved! Please only use ' - 'a constant string as parameter for .named().', - ), - ); - }); - break; - case _methodReferences: - break; - case _methodWithLength: - final args = remainingExpr.argumentList; - final minArg = findNamedArgument(args, 'min'); - final maxArg = findNamedArgument(args, 'max'); - - foundFeatures.add(LimitingTextLength.withLength( - min: readIntLiteral(minArg, () {}), - max: readIntLiteral(maxArg, () {}), - )); - break; - case _methodAutoIncrement: - foundFeatures.add(AutoIncrement()); - // a column declared as auto increment is always a primary key - foundFeatures.add(const PrimaryKey()); - break; - case _methodNullable: - nullable = true; - break; - case _methodCustomConstraint: - foundCustomConstraint = - readStringLiteral(remainingExpr.argumentList.arguments.first, () { - session.errors.add( - MoorError( - critical: false, - affectedElement: getter.declaredElement, - message: - 'This constraint is cannot be resolved! Please only use ' - 'a constant string as parameter for .customConstraint().', - ), - ); - }); - break; - case _methodDefault: - final args = remainingExpr.argumentList; - final expression = args.arguments.single; - foundDefaultExpression = expression; - break; - case _methodMap: - final args = remainingExpr.argumentList; - final expression = args.arguments.single; - - // the map method has a parameter type that resolved to the runtime - // type of the custom object - final type = remainingExpr.typeArgumentTypes.single; - - createdTypeConverter = expression; - typeConverterRuntime = type; - break; - } - - // We're not at a starting method yet, so we need to go deeper! - final inner = (remainingExpr.target) as MethodInvocation; - remainingExpr = inner; - } - - ColumnName name; - if (foundExplicitName != null) { - name = ColumnName.explicitly(foundExplicitName); - } else { - name = ColumnName.implicitly(ReCase(getter.name.name).snakeCase); - } - - final columnType = _startMethodToColumnType(foundStartMethod); - - UsedTypeConverter converter; - if (createdTypeConverter != null && typeConverterRuntime != null) { - converter = UsedTypeConverter( - expression: createdTypeConverter, - mappedType: typeConverterRuntime, - sqlType: columnType); - } - - return SpecifiedColumn( - type: columnType, - dartGetterName: getter.name.name, - name: name, - overriddenJsonName: _readJsonKey(getterElement), - customConstraints: foundCustomConstraint, - nullable: nullable, - features: foundFeatures, - defaultArgument: foundDefaultExpression?.toSource(), - typeConverter: converter); - } - - ColumnType _startMethodToColumnType(String startMethod) { - return const { - startBool: ColumnType.boolean, - startString: ColumnType.text, - startInt: ColumnType.integer, - startDateTime: ColumnType.datetime, - startBlob: ColumnType.blob, - startReal: ColumnType.real, - }[startMethod]; - } - - String _readJsonKey(Element getter) { - final annotations = getter.metadata; - final object = annotations.singleWhere((e) { - final value = e.computeConstantValue(); - return isFromMoor(value.type) && value.type.name == 'JsonKey'; - }, orElse: () => null); - - if (object == null) return null; - - return object.constantValue.getField('key').toStringValue(); - } -} diff --git a/moor_generator/lib/src/parser/moor/moor_analyzer.dart b/moor_generator/lib/src/parser/moor/moor_analyzer.dart deleted file mode 100644 index 342c27b4..00000000 --- a/moor_generator/lib/src/parser/moor/moor_analyzer.dart +++ /dev/null @@ -1,63 +0,0 @@ -import 'package:moor_generator/src/parser/moor/parsed_moor_file.dart'; -import 'package:source_span/source_span.dart'; -import 'package:sqlparser/sqlparser.dart'; - -/// Parses and analyzes the experimental `.moor` files containing sql -/// statements. -class MoorAnalyzer { - /// Content of the `.moor` file we're analyzing. - final String content; - - MoorAnalyzer(this.content); - - Future analyze() { - final results = SqlEngine().parseMultiple(content); - - final createdTables = []; - final errors = []; - - for (var parsedStmt in results) { - if (parsedStmt.rootNode is CreateTableStatement) { - createdTables.add(CreateTable(parsedStmt)); - } else { - errors.add( - MoorParsingError( - parsedStmt.rootNode.span, - message: - 'At the moment, only CREATE TABLE statements are supported in .moor files', - ), - ); - } - } - - // all results have the same list of errors - final sqlErrors = results.isEmpty ? [] : results.first.errors; - - for (var error in sqlErrors) { - errors.add(MoorParsingError(error.token.span, message: error.message)); - } - - final parsedFile = ParsedMoorFile(createdTables); - - return Future.value(MoorParsingResult(parsedFile, errors)); - } -} - -class MoorParsingResult { - final ParsedMoorFile parsedFile; - final List errors; - - MoorParsingResult(this.parsedFile, this.errors); -} - -class MoorParsingError { - final FileSpan span; - final String message; - - MoorParsingError(this.span, {this.message}); - - @override - String toString() { - return span.message(message, color: true); - } -} diff --git a/moor_generator/lib/src/parser/parser.dart b/moor_generator/lib/src/parser/parser.dart deleted file mode 100644 index b2c112df..00000000 --- a/moor_generator/lib/src/parser/parser.dart +++ /dev/null @@ -1,64 +0,0 @@ -import 'package:analyzer/dart/ast/ast.dart'; -import 'package:moor_generator/src/state/errors.dart'; -import 'package:moor_generator/src/model/specified_table.dart'; -import 'package:moor_generator/src/state/session.dart'; - -class Parser { - List specifiedTables; - - void init() async {} -} - -class ParserBase { - final GeneratorSession session; - - ParserBase(this.session); - - Expression returnExpressionOfMethod(MethodDeclaration method) { - final body = method.body; - - if (!(body is ExpressionFunctionBody)) { - session.errors.add(MoorError( - affectedElement: method.declaredElement, - critical: true, - message: - 'This method must have an expression body (use => instead of {return ...})')); - return null; - } - - return (method.body as ExpressionFunctionBody).expression; - } - - String readStringLiteral(Expression expression, void onError()) { - if (!(expression is StringLiteral)) { - onError(); - } else { - final value = (expression as StringLiteral).stringValue; - if (value == null) { - onError(); - } else { - return value; - } - } - - return null; - } - - int readIntLiteral(Expression expression, void onError()) { - if (!(expression is IntegerLiteral)) { - onError(); - // ignore: avoid_returning_null - return null; - } else { - return (expression as IntegerLiteral).value; - } - } - - Expression findNamedArgument(ArgumentList args, String argName) { - final argument = args.arguments.singleWhere( - (e) => e is NamedExpression && e.name.label.name == argName, - orElse: () => null) as NamedExpression; - - return argument?.expression; - } -} diff --git a/moor_generator/lib/src/parser/table_parser.dart b/moor_generator/lib/src/parser/table_parser.dart deleted file mode 100644 index 0f552c80..00000000 --- a/moor_generator/lib/src/parser/table_parser.dart +++ /dev/null @@ -1,129 +0,0 @@ -import 'package:analyzer/dart/ast/ast.dart'; -import 'package:analyzer/dart/element/element.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/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(GeneratorSession session) : super(session); - - Future parse(ClassElement element) async { - final sqlName = await _parseTableName(element); - if (sqlName == null) return null; - - final columns = await _parseColumns(element); - - final table = SpecifiedTable( - fromClass: element, - columns: columns, - sqlName: escapeIfNeeded(sqlName), - dartTypeName: _readDartTypeName(element), - primaryKey: await _readPrimaryKey(element, columns), - ); - - var index = 0; - for (var converter in table.converters) { - converter - ..index = index++ - ..table = table; - } - - return table; - } - - String _readDartTypeName(ClassElement element) { - final nameAnnotation = element.metadata.singleWhere( - (e) => e.computeConstantValue().type.name == 'DataClassName', - orElse: () => null); - - if (nameAnnotation == null) { - return dataClassNameForClassName(element.name); - } else { - return nameAnnotation.constantValue.getField('name').toStringValue(); - } - } - - Future _parseTableName(ClassElement element) async { - // todo allow override via a field (final String tableName = '') as well - - final tableNameGetter = element.getGetter('tableName'); - if (tableNameGetter == null) { - // class does not override tableName. So just use the dart class name - // instead. Will use placed_orders for a class called PlacedOrders - return ReCase(element.name).snakeCase; - } - - // we expect something like get tableName => "myTableName", the getter - // must do nothing more complicated - final tableNameDeclaration = - await session.loadElementDeclaration(tableNameGetter); - final returnExpr = returnExpressionOfMethod( - tableNameDeclaration.node as MethodDeclaration); - - final tableName = readStringLiteral(returnExpr, () { - session.errors.add(MoorError( - critical: true, - message: - 'This getter must return a string literal, and do nothing more', - affectedElement: tableNameGetter)); - }); - - return tableName; - } - - Future> _readPrimaryKey( - ClassElement element, List columns) async { - final primaryKeyGetter = element.getGetter('primaryKey'); - if (primaryKeyGetter == null) { - return null; - } - - final resolved = await session.loadElementDeclaration(primaryKeyGetter); - final ast = resolved.node as MethodDeclaration; - final body = ast.body; - if (body is! ExpressionFunctionBody) { - session.errors.add(MoorError( - affectedElement: primaryKeyGetter, - message: 'This must return a set literal using the => syntax!')); - return null; - } - final expression = (body as ExpressionFunctionBody).expression; - final parsedPrimaryKey = {}; - - if (expression is SetOrMapLiteral) { - for (var entry in expression.elements) { - if (entry is Identifier) { - final column = columns - .singleWhere((column) => column.dartGetterName == entry.name); - parsedPrimaryKey.add(column); - } else { - print('Unexpected entry in expression.elements: $entry'); - } - } - } else { - session.errors.add(MoorError( - affectedElement: primaryKeyGetter, - message: 'This must return a set literal!')); - } - - return parsedPrimaryKey; - } - - Future> _parseColumns(ClassElement element) { - 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 await session.parseColumn(node, field.getter); - })); - } -} diff --git a/moor_generator/lib/src/state/errors.dart b/moor_generator/lib/src/state/errors.dart deleted file mode 100644 index 81b47928..00000000 --- a/moor_generator/lib/src/state/errors.dart +++ /dev/null @@ -1,17 +0,0 @@ -import 'package:analyzer/dart/element/element.dart'; - -class MoorError { - final bool critical; - final String message; - final Element affectedElement; - - MoorError({this.critical = false, this.message, this.affectedElement}); -} - -class ErrorStore { - final List errors = []; - - void add(MoorError error) => errors.add(error); - - bool get hasCriticalError => errors.any((e) => e.critical); -} diff --git a/moor_generator/lib/src/state/generator_state.dart b/moor_generator/lib/src/state/generator_state.dart deleted file mode 100644 index 40c651e5..00000000 --- a/moor_generator/lib/src/state/generator_state.dart +++ /dev/null @@ -1,39 +0,0 @@ -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> _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 parseTable( - DartType type, Future Function() resolve) { - return _foundTables.putIfAbsent(type, resolve); - } -} diff --git a/moor_generator/lib/src/state/session.dart b/moor_generator/lib/src/state/session.dart deleted file mode 100644 index 6bb65486..00000000 --- a/moor_generator/lib/src/state/session.dart +++ /dev/null @@ -1,129 +0,0 @@ -import 'package:analyzer/dart/analysis/results.dart'; -import 'package:analyzer/dart/ast/ast.dart'; -import 'package:analyzer/dart/constant/value.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_dao.dart'; -import 'package:moor_generator/src/model/specified_database.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/column_parser.dart'; -import 'package:moor_generator/src/parser/moor/moor_analyzer.dart'; -import 'package:moor_generator/src/parser/sql/sql_parser.dart'; -import 'package:moor_generator/src/parser/sql/type_mapping.dart'; -import 'package:moor_generator/src/parser/table_parser.dart'; -import 'package:moor_generator/src/parser/use_dao_parser.dart'; -import 'package:moor_generator/src/parser/use_moor_parser.dart'; -import 'package:source_gen/source_gen.dart'; - -import 'errors.dart'; -import 'generator_state.dart'; -import 'options.dart'; -import 'writer.dart'; - -class GeneratorSession { - final GeneratorState state; - final ErrorStore errors = ErrorStore(); - final BuildStep step; - - final Writer writer = Writer(); - - TableParser _tableParser; - ColumnParser _columnParser; - - MoorOptions get options => state.options; - - GeneratorSession(this.state, this.step) { - _tableParser = TableParser(this); - _columnParser = ColumnParser(this); - } - - Future loadElementDeclaration( - Element element) async { - final resolvedLibrary = await element.library.session - .getResolvedLibraryByElement(element.library); - - return resolvedLibrary.getElementDeclaration(element); - } - - /// Parses a [SpecifiedDatabase] from the [ClassElement] which was annotated - /// with `@UseMoor` and the [annotation] reader that reads the `@UseMoor` - /// annotation. - Future parseDatabase( - ClassElement element, ConstantReader annotation) { - return UseMoorParser(this).parseDatabase(element, annotation); - } - - /// Parses a [SpecifiedDao] from a class declaration that has a `UseDao` - /// [annotation]. - Future parseDao( - ClassElement element, ConstantReader annotation) { - return UseDaoParser(this).parseDao(element, annotation); - } - - /// 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> parseTables( - Iterable 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); - } - })).then((list) => List.from(list)); // make growable - } - - Future> resolveIncludes(Iterable paths) async { - final mapper = TypeMapper(); - final foundTables = []; - - for (var path in paths) { - final asset = AssetId.resolve(path, from: step.inputId); - String content; - try { - content = await step.readAsString(asset); - } catch (e) { - errors.add(MoorError( - critical: true, - message: 'The included file $path could not be found')); - } - - final parsed = await MoorAnalyzer(content).analyze(); - foundTables.addAll( - parsed.parsedFile.declaredTables.map((t) => t.extractTable(mapper))); - - for (var parseError in parsed.errors) { - errors.add(MoorError(message: "Can't parse sql in $path: $parseError")); - } - } - - return foundTables; - } - - /// Parses a column from a getter [e] declared inside a table class and its - /// resolved AST node [m]. - Future parseColumn(MethodDeclaration m, Element e) { - return Future.value(_columnParser.parse(m, e)); - } - - Future> parseQueries( - Map fromAnnotation, - List availableTables) { - // no queries declared, so there is no point in starting a sql engine - if (fromAnnotation.isEmpty) return Future.value([]); - - final parser = SqlParser(this, availableTables, fromAnnotation)..parse(); - - return Future.value(parser.foundQueries); - } -} diff --git a/moor_generator/lib/src/writer/database_writer.dart b/moor_generator/lib/src/writer/database_writer.dart index 174bcbc8..537ea97e 100644 --- a/moor_generator/lib/src/writer/database_writer.dart +++ b/moor_generator/lib/src/writer/database_writer.dart @@ -1,11 +1,8 @@ import 'package:moor_generator/src/model/sql_query.dart'; -import 'package:moor_generator/src/state/session.dart'; -import 'package:moor_generator/src/writer/query_writer.dart'; -import 'package:moor_generator/src/writer/result_set_writer.dart'; +import 'package:moor_generator/src/writer/utils/memoized_getter.dart'; import 'package:recase/recase.dart'; import 'package:moor_generator/src/model/specified_database.dart'; import 'package:moor_generator/src/writer/table_writer.dart'; -import 'utils.dart'; class DatabaseWriter { final SpecifiedDatabase db; @@ -19,13 +16,6 @@ class DatabaseWriter { TableWriter(table, session).writeInto(buffer); } - // Write additional classes to hold the result of custom queries - for (final query in db.queries) { - if (query is SqlSelectQuery && query.resultSet.matchingTable == null) { - ResultSetWriter(query).write(buffer); - } - } - // Write the database class final className = '_\$${db.fromClass.name}'; buffer.write('abstract class $className extends GeneratedDatabase {\n' diff --git a/moor_generator/lib/src/writer/query_writer.dart b/moor_generator/lib/src/writer/queries/query_writer.dart similarity index 68% rename from moor_generator/lib/src/writer/query_writer.dart rename to moor_generator/lib/src/writer/queries/query_writer.dart index bf40cd6d..098d6fb2 100644 --- a/moor_generator/lib/src/writer/query_writer.dart +++ b/moor_generator/lib/src/writer/queries/query_writer.dart @@ -1,9 +1,11 @@ import 'dart:math' show max; +import 'package:moor_generator/src/backends/build/moor_builder.dart'; import 'package:moor_generator/src/model/specified_column.dart'; import 'package:moor_generator/src/model/sql_query.dart'; -import 'package:moor_generator/src/state/session.dart'; import 'package:moor_generator/src/utils/string_escaper.dart'; +import 'package:moor_generator/src/writer/queries/result_set_writer.dart'; +import 'package:moor_generator/src/writer/writer.dart'; import 'package:recase/recase.dart'; import 'package:sqlparser/sqlparser.dart'; @@ -16,13 +18,18 @@ const highestAssignedIndexVar = '\$highestIndex'; /// should be included in a generated database or dao class. class QueryWriter { final SqlQuery query; - final GeneratorSession session; + final Scope scope; SqlSelectQuery get _select => query as SqlSelectQuery; UpdatingQuery get _update => query as UpdatingQuery; + MoorOptions get options => scope.writer.options; + StringBuffer _buffer; + final Set _writtenMappingMethods; - QueryWriter(this.query, this.session, this._writtenMappingMethods); + QueryWriter(this.query, this.scope, this._writtenMappingMethods) { + _buffer = scope.leaf(); + } /// The expanded sql that we insert into queries whenever an array variable /// appears. For the query "SELECT * FROM t WHERE x IN ?", we generate @@ -36,19 +43,25 @@ class QueryWriter { return 'expanded${v.dartParameterName}'; } - void writeInto(StringBuffer buffer) { + void writeInto() { if (query is SqlSelectQuery) { - _writeSelect(buffer); + final select = query as SqlSelectQuery; + if (select.resultSet.matchingTable == null) { + // query needs its own result set - write that now + final buffer = scope.findScopeOfLevel(DartScope.library).leaf(); + ResultSetWriter(select).write(buffer); + } + _writeSelect(); } else if (query is UpdatingQuery) { - _writeUpdatingQuery(buffer); + _writeUpdatingQuery(); } } - void _writeSelect(StringBuffer buffer) { - _writeMapping(buffer); - _writeSelectStatementCreator(buffer); - _writeOneTimeReader(buffer); - _writeStreamReader(buffer); + void _writeSelect() { + _writeMapping(); + _writeSelectStatementCreator(); + _writeOneTimeReader(); + _writeStreamReader(); } String _nameOfMappingMethod() { @@ -61,11 +74,11 @@ class QueryWriter { /// Writes a mapping method that turns a "QueryRow" into the desired custom /// return type. - void _writeMapping(StringBuffer buffer) { + void _writeMapping() { // avoid writing mapping methods twice if the same result class is written // more than once. if (!_writtenMappingMethods.contains(_nameOfMappingMethod())) { - buffer + _buffer ..write('${_select.resultClassName} ${_nameOfMappingMethod()}') ..write('(QueryRow row) {\n') ..write('return ${_select.resultClassName}('); @@ -84,35 +97,35 @@ class QueryWriter { code = '$field.mapToDart($code)'; } - buffer.write('$fieldName: $code,'); + _buffer.write('$fieldName: $code,'); } - buffer.write(');\n}\n'); + _buffer.write(');\n}\n'); _writtenMappingMethods.add(_nameOfMappingMethod()); } } /// Writes a method returning a `Selectable`, where `T` is the return type /// of the custom query. - void _writeSelectStatementCreator(StringBuffer buffer) { + void _writeSelectStatementCreator() { final returnType = 'Selectable<${_select.resultClassName}>'; final methodName = _nameOfCreationMethod(); - buffer.write('$returnType $methodName('); - _writeParameters(buffer); - buffer.write(') {\n'); + _buffer.write('$returnType $methodName('); + _writeParameters(); + _buffer.write(') {\n'); - _writeExpandedDeclarations(buffer); - buffer + _writeExpandedDeclarations(); + _buffer ..write('return (operateOn ?? this).') ..write('customSelectQuery(${_queryCode()}, '); - _writeVariables(buffer); - buffer.write(', '); - _writeReadsFrom(buffer); + _writeVariables(); + _buffer.write(', '); + _writeReadsFrom(); - buffer.write(').map('); - buffer.write(_nameOfMappingMethod()); - buffer.write(');\n}\n'); + _buffer.write(').map('); + _buffer.write(_nameOfMappingMethod()); + _buffer.write(');\n}\n'); } /* @@ -122,34 +135,35 @@ class QueryWriter { } */ - void _writeOneTimeReader(StringBuffer buffer) { - buffer.write('Future> ${query.name}('); - _writeParameters(buffer); - buffer..write(') {\n')..write('return ${_nameOfCreationMethod()}('); - _writeUseParameters(buffer); - buffer.write(').get();\n}\n'); + void _writeOneTimeReader() { + _buffer.write('Future> ${query.name}('); + _writeParameters(); + _buffer..write(') {\n')..write('return ${_nameOfCreationMethod()}('); + _writeUseParameters(); + _buffer.write(').get();\n}\n'); } - void _writeStreamReader(StringBuffer buffer) { + void _writeStreamReader() { final upperQueryName = ReCase(query.name).pascalCase; String methodName; // turning the query name into pascal case will remove underscores, add the // "private" modifier back in if needed - if (session.options.fixPrivateWatchMethods && query.name.startsWith('_')) { + if (scope.writer.options.fixPrivateWatchMethods && + query.name.startsWith('_')) { methodName = '_watch$upperQueryName'; } else { methodName = 'watch$upperQueryName'; } - buffer.write('Stream> $methodName('); - _writeParameters(buffer, dontOverrideEngine: true); - buffer..write(') {\n')..write('return ${_nameOfCreationMethod()}('); - _writeUseParameters(buffer, dontUseEngine: true); - buffer.write(').watch();\n}\n'); + _buffer.write('Stream> $methodName('); + _writeParameters(dontOverrideEngine: true); + _buffer..write(') {\n')..write('return ${_nameOfCreationMethod()}('); + _writeUseParameters(dontUseEngine: true); + _buffer.write(').watch();\n}\n'); } - void _writeUpdatingQuery(StringBuffer buffer) { + void _writeUpdatingQuery() { /* Future test() { return customUpdate('', variables: [], updates: {}); @@ -157,24 +171,23 @@ class QueryWriter { */ final implName = _update.isInsert ? 'customInsert' : 'customUpdate'; - buffer.write('Future ${query.name}('); - _writeParameters(buffer); - buffer.write(') {\n'); + _buffer.write('Future ${query.name}('); + _writeParameters(); + _buffer.write(') {\n'); - _writeExpandedDeclarations(buffer); - buffer + _writeExpandedDeclarations(); + _buffer ..write('return (operateOn ?? this).') ..write('$implName(${_queryCode()},'); - _writeVariables(buffer); - buffer.write(','); - _writeUpdates(buffer); + _writeVariables(); + _buffer.write(','); + _writeUpdates(); - buffer..write(',);\n}\n'); + _buffer..write(',);\n}\n'); } - void _writeParameters(StringBuffer buffer, - {bool dontOverrideEngine = false}) { + void _writeParameters({bool dontOverrideEngine = false}) { final paramList = query.variables.map((v) { var dartType = dartTypeNames[v.type]; if (v.isArray) { @@ -183,13 +196,13 @@ class QueryWriter { return '$dartType ${v.dartParameterName}'; }).join(', '); - buffer.write(paramList); + _buffer.write(paramList); // write named optional parameter to configure the query engine used to // execute the statement, if (!dontOverrideEngine) { - if (query.variables.isNotEmpty) buffer.write(', '); - buffer.write('{@Deprecated(${asDartLiteral(queryEngineWarningDesc)}) ' + if (query.variables.isNotEmpty) _buffer.write(', '); + _buffer.write('{@Deprecated(${asDartLiteral(queryEngineWarningDesc)}) ' 'QueryEngine operateOn}'); } } @@ -197,11 +210,11 @@ class QueryWriter { /// Writes code that uses the parameters as declared by [_writeParameters], /// assuming that for each parameter, a variable with the same name exists /// in the current scope. - void _writeUseParameters(StringBuffer into, {bool dontUseEngine = false}) { - into.write(query.variables.map((v) => v.dartParameterName).join(', ')); + void _writeUseParameters({bool dontUseEngine = false}) { + _buffer.write(query.variables.map((v) => v.dartParameterName).join(', ')); if (!dontUseEngine) { - if (query.variables.isNotEmpty) into.write(', '); - into.write('operateOn: operateOn'); + if (query.variables.isNotEmpty) _buffer.write(', '); + _buffer.write('operateOn: operateOn'); } } @@ -217,7 +230,7 @@ class QueryWriter { // "vars" variable twice. To do this, a local var called "$currentVarIndex" // keeps track of the highest variable number assigned. - void _writeExpandedDeclarations(StringBuffer buffer) { + void _writeExpandedDeclarations() { var indexCounterWasDeclared = false; var highestIndexBeforeArray = 0; @@ -228,12 +241,12 @@ class QueryWriter { // add +1 because that's going to be the first index of the expanded // array final firstVal = highestIndexBeforeArray + 1; - buffer.write('var $highestAssignedIndexVar = $firstVal;'); + _buffer.write('var $highestAssignedIndexVar = $firstVal;'); indexCounterWasDeclared = true; } // final expandedvar1 = $expandVar(, ); - buffer + _buffer ..write('final ') ..write(_expandedName(variable)) ..write(' = ') @@ -244,7 +257,7 @@ class QueryWriter { ..write('.length);\n'); // increase highest index for the next array - buffer + _buffer ..write('$highestAssignedIndexVar += ') ..write(variable.dartParameterName) ..write('.length;'); @@ -256,8 +269,8 @@ class QueryWriter { } } - void _writeVariables(StringBuffer buffer) { - buffer..write('variables: ['); + void _writeVariables() { + _buffer..write('variables: ['); for (var variable in query.variables) { // for a regular variable: Variable.withInt(x), @@ -266,15 +279,15 @@ class QueryWriter { final name = variable.dartParameterName; if (variable.isArray) { - buffer.write('for (var \$ in $name) $constructor(\$)'); + _buffer.write('for (var \$ in $name) $constructor(\$)'); } else { - buffer.write('$constructor($name)'); + _buffer.write('$constructor($name)'); } - buffer.write(','); + _buffer.write(','); } - buffer..write(']'); + _buffer..write(']'); } /// Returns a Dart string literal representing the query after variables have @@ -295,29 +308,29 @@ class QueryWriter { .singleWhere((f) => f.variable.resolvedIndex == sqlVar.resolvedIndex); if (!moorVar.isArray) continue; - // write everything that comes before this var into the buffer + // write everything that comes before this var into the_buffer final currentIndex = sqlVar.firstPosition; final queryPart = query.sql.substring(lastIndex, currentIndex); - buffer.write(escapeForDart(queryPart)); + _buffer.write(escapeForDart(queryPart)); lastIndex = sqlVar.lastPosition; // write the ($expandedVar) par - buffer.write('(\$${_expandedName(moorVar)})'); + _buffer.write('(\$${_expandedName(moorVar)})'); } // write the final part after the last variable, plus the ending ' - buffer..write(escapeForDart(query.sql.substring(lastIndex)))..write("'"); + _buffer..write(escapeForDart(query.sql.substring(lastIndex)))..write("'"); return buffer.toString(); } - void _writeReadsFrom(StringBuffer buffer) { + void _writeReadsFrom() { final from = _select.readsFrom.map((t) => t.tableFieldName).join(', '); - buffer..write('readsFrom: {')..write(from)..write('}'); + _buffer..write('readsFrom: {')..write(from)..write('}'); } - void _writeUpdates(StringBuffer buffer) { + void _writeUpdates() { final from = _update.updates.map((t) => t.tableFieldName).join(', '); - buffer..write('updates: {')..write(from)..write('}'); + _buffer..write('updates: {')..write(from)..write('}'); } } diff --git a/moor_generator/lib/src/writer/result_set_writer.dart b/moor_generator/lib/src/writer/queries/result_set_writer.dart similarity index 93% rename from moor_generator/lib/src/writer/result_set_writer.dart rename to moor_generator/lib/src/writer/queries/result_set_writer.dart index 39fcdd4e..88ea7d63 100644 --- a/moor_generator/lib/src/writer/result_set_writer.dart +++ b/moor_generator/lib/src/writer/queries/result_set_writer.dart @@ -1,6 +1,7 @@ import 'package:moor_generator/src/model/specified_column.dart'; import 'package:moor_generator/src/model/sql_query.dart'; +/// Writes a class holding the result of an sql query into Dart. class ResultSetWriter { final SqlSelectQuery query; diff --git a/moor_generator/lib/src/writer/data_class_writer.dart b/moor_generator/lib/src/writer/tables/data_class_writer.dart similarity index 97% rename from moor_generator/lib/src/writer/data_class_writer.dart rename to moor_generator/lib/src/writer/tables/data_class_writer.dart index 1f70aa7b..a6eabb55 100644 --- a/moor_generator/lib/src/writer/data_class_writer.dart +++ b/moor_generator/lib/src/writer/tables/data_class_writer.dart @@ -1,13 +1,17 @@ import 'package:moor_generator/src/model/specified_table.dart'; -import 'package:moor_generator/src/state/session.dart'; import 'package:moor_generator/src/writer/utils/hash_code.dart'; +import 'package:moor_generator/src/writer/writer.dart'; import 'package:recase/recase.dart'; class DataClassWriter { final SpecifiedTable table; - final GeneratorSession session; + final Scope scope; - DataClassWriter(this.table, this.session); + StringBuffer _buffer; + + DataClassWriter(this.table, this.scope) { + _buffer = scope.leaf(); + } void writeInto(StringBuffer buffer) { buffer.write( diff --git a/moor_generator/lib/src/writer/table_writer.dart b/moor_generator/lib/src/writer/tables/table_writer.dart similarity index 99% rename from moor_generator/lib/src/writer/table_writer.dart rename to moor_generator/lib/src/writer/tables/table_writer.dart index 05996443..74910861 100644 --- a/moor_generator/lib/src/writer/table_writer.dart +++ b/moor_generator/lib/src/writer/tables/table_writer.dart @@ -4,7 +4,7 @@ import 'package:moor_generator/src/state/session.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'; -import 'package:moor_generator/src/writer/utils.dart'; +import 'package:moor_generator/src/writer/memoized_getter.dart'; class TableWriter { final SpecifiedTable table; diff --git a/moor_generator/lib/src/writer/update_companion_writer.dart b/moor_generator/lib/src/writer/tables/update_companion_writer.dart similarity index 100% rename from moor_generator/lib/src/writer/update_companion_writer.dart rename to moor_generator/lib/src/writer/tables/update_companion_writer.dart diff --git a/moor_generator/lib/src/writer/utils.dart b/moor_generator/lib/src/writer/utils/memoized_getter.dart similarity index 100% rename from moor_generator/lib/src/writer/utils.dart rename to moor_generator/lib/src/writer/utils/memoized_getter.dart diff --git a/moor_generator/lib/src/state/writer.dart b/moor_generator/lib/src/writer/writer.dart similarity index 62% rename from moor_generator/lib/src/state/writer.dart rename to moor_generator/lib/src/writer/writer.dart index b55a87dd..1fad3f04 100644 --- a/moor_generator/lib/src/state/writer.dart +++ b/moor_generator/lib/src/writer/writer.dart @@ -1,4 +1,5 @@ import 'package:meta/meta.dart'; +import 'package:moor_generator/src/backends/build/moor_builder.dart'; /// Manages a tree structure which we use to generate code. /// @@ -10,6 +11,9 @@ import 'package:meta/meta.dart'; /// passing a [Scope] we will always be able to write code in a parent scope. class Writer { final Scope _root = Scope(parent: null); + final MoorOptions options; + + Writer(this.options); String writeGenerated() => _leafNodes(_root).join(); @@ -41,8 +45,13 @@ abstract class _Node { /// we just pass a single [StringBuffer] around, this is annoying to manage. class Scope extends _Node { final List<_Node> _children = []; + final DartScope scope; + final Writer writer; - Scope({@required Scope parent}) : super(parent); + Scope({@required Scope parent, Writer writer}) + : scope = parent?.scope?.nextLevel ?? DartScope.library, + writer = writer ?? parent?.writer, + super(parent); Scope get root { var found = this; @@ -52,6 +61,19 @@ class Scope extends _Node { return found; } + Iterable get _thisAndParents sync* { + var scope = this; + do { + yield scope; + scope = scope.parent; + } while (scope != null); + } + + Scope findScopeOfLevel(DartScope level) { + return _thisAndParents + .firstWhere((scope) => scope.scope.isSuperScope(level)); + } + Scope child() { final child = Scope(parent: this); _children.add(child); @@ -70,3 +92,27 @@ class _LeafNode extends _Node { _LeafNode(Scope parent) : super(parent); } + +class DartScope { + static const DartScope library = DartScope._(0); + static const DartScope topLevelMember = DartScope._(1); + static const DartScope inner = DartScope._(2); + + static const List values = [library, topLevelMember, inner]; + + final int _id; + + const DartScope._(this._id); + + DartScope get nextLevel { + if (_id == values.length - 1) { + // already in innermost level + return this; + } + return values[_id + 1]; + } + + bool isSuperScope(DartScope other) { + return other._id >= _id; + } +}