Start migration of writer code

This commit is contained in:
Simon Binder 2019-09-02 17:26:30 +02:00
parent 6e89a319ad
commit ad8bdba4b8
No known key found for this signature in database
GPG Key ID: 7891917E4147B8C0
29 changed files with 364 additions and 824 deletions

View File

@ -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;

View File

@ -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);
}

View File

@ -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);

View File

@ -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 {

View File

@ -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<CreateTable> 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 =

View File

@ -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<ParsedMoorFile> parseAndAnalyze() {
final results =
SqlEngine(useMoorExtensions: true).parseMultiple(task.content);
final createdReaders = <CreateTableReader>[];
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 ? <ParsingError>[] : 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);
}
}

View File

@ -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<DartTask> startDartTask(BackendTask backendTask) async {
final library = await backendTask.resolveDart(backendTask.entrypoint);
Future<DartTask> startDartTask(BackendTask backendTask, {String uri}) async {
final input = uri ?? backendTask.entrypoint;
final library = await backendTask.resolveDart(input);
return DartTask(this, backendTask, library);
}
Future<MoorTask> 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<R extends ParsedFile> {
/// Session used to parse a Dart file and extract table information.
class DartTask extends FileTask<ParsedDartFile> {
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<ParsedDartFile> 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<SpecifiedDatabase> parseDatabase(
ClassElement element, ConstantReader annotation) {
return UseMoorParser(this).parseDatabase(element, annotation);
}
/// Parses a [SpecifiedDao] from a class declaration that has a `UseDao`
/// [annotation].
Future<SpecifiedDao> 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<List<SpecifiedTable>> parseTables(
Iterable<DartType> 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<List<SpecifiedTable>> resolveIncludes(Iterable<String> paths) {
return Stream.fromFutures(
paths.map((path) => session.startMoorTask(backendTask, uri: path)))
.asyncMap((task) => task.compute())
.expand((file) => file.declaredTables)
.toList();
}
Future<List<SqlQuery>> parseQueries(
Map<DartObject, DartObject> fromAnnotation,
List<SpecifiedTable> 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<ParsedMoorFile> {
final List<String> content;
final String content;
final TypeMapper mapper = TypeMapper();
MoorTask(BackendTask task, MoorSession session, this.content)
: super(task, session);
@override
FutureOr<ParsedMoorFile> compute() {
// TODO: implement compute
return null;
final parser = MoorParser(this);
return parser.parseAndAnalyze();
}
}

View File

@ -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;

View File

@ -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<SpecifiedTable> tables;
final GeneratorSession session;
final FileTask task;
final Map<DartObject, DartObject> definedQueries;
final TypeMapper _mapper = TypeMapper();
@ -18,7 +18,7 @@ class SqlParser {
final List<SqlQuery> 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',
));
}

View File

@ -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<String> readMoor(String path) {
return step.readAsString(_resolve(path));
}
@override
Future<LibraryElement> resolveDart(String path) {
return step.resolver.libraryFor(_resolve(path));
}
}

View File

@ -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);

View File

@ -1,3 +1,5 @@
part of 'moor_builder.dart';
class MoorOptions {
final bool generateFromJsonStringConstructor;

View File

@ -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<String> 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 = <ColumnFeature>[];
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();
}
}

View File

@ -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<MoorParsingResult> analyze() {
final results = SqlEngine().parseMultiple(content);
final createdTables = <CreateTable>[];
final errors = <MoorParsingError>[];
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 ? <ParsingError>[] : 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<MoorParsingError> 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);
}
}

View File

@ -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<SpecifiedTable> 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;
}
}

View File

@ -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<SpecifiedTable> 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<String> _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<Set<SpecifiedColumn>> _readPrimaryKey(
ClassElement element, List<SpecifiedColumn> 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 = <SpecifiedColumn>{};
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<List<SpecifiedColumn>> _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);
}));
}
}

View File

@ -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<MoorError> errors = [];
void add(MoorError error) => errors.add(error);
bool get hasCriticalError => errors.any((e) => e.critical);
}

View File

@ -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<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

@ -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<ElementDeclarationResult> 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<SpecifiedDatabase> parseDatabase(
ClassElement element, ConstantReader annotation) {
return UseMoorParser(this).parseDatabase(element, annotation);
}
/// Parses a [SpecifiedDao] from a class declaration that has a `UseDao`
/// [annotation].
Future<SpecifiedDao> 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<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);
}
})).then((list) => List.from(list)); // make growable
}
Future<List<SpecifiedTable>> resolveIncludes(Iterable<String> paths) async {
final mapper = TypeMapper();
final foundTables = <SpecifiedTable>[];
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<SpecifiedColumn> parseColumn(MethodDeclaration m, Element e) {
return Future.value(_columnParser.parse(m, e));
}
Future<List<SqlQuery>> parseQueries(
Map<DartObject, DartObject> fromAnnotation,
List<SpecifiedTable> 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);
}
}

View File

@ -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'

View File

@ -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<String> _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<T>`, 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<List<${_select.resultClassName}>> ${query.name}(');
_writeParameters(buffer);
buffer..write(') {\n')..write('return ${_nameOfCreationMethod()}(');
_writeUseParameters(buffer);
buffer.write(').get();\n}\n');
void _writeOneTimeReader() {
_buffer.write('Future<List<${_select.resultClassName}>> ${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<List<${_select.resultClassName}>> $methodName(');
_writeParameters(buffer, dontOverrideEngine: true);
buffer..write(') {\n')..write('return ${_nameOfCreationMethod()}(');
_writeUseParameters(buffer, dontUseEngine: true);
buffer.write(').watch();\n}\n');
_buffer.write('Stream<List<${_select.resultClassName}>> $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<int> test() {
return customUpdate('', variables: [], updates: {});
@ -157,24 +171,23 @@ class QueryWriter {
*/
final implName = _update.isInsert ? 'customInsert' : 'customUpdate';
buffer.write('Future<int> ${query.name}(');
_writeParameters(buffer);
buffer.write(') {\n');
_buffer.write('Future<int> ${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(<startIndex>, <amount>);
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('}');
}
}

View File

@ -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;

View File

@ -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(

View File

@ -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;

View File

@ -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<Scope> 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<DartScope> 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;
}
}